-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(core) deno_core::extension! macro to simplify extension registration #18210
Conversation
cli/lsp/tsc.rs
Outdated
deno_core::extension!(deno_tsc, | ||
ops = deno_ops, | ||
config = { | ||
performance: Arc<Performance> | ||
}, | ||
state = |state, performance| { | ||
state.put(State::new( | ||
Arc::new(StateSnapshot::default()), | ||
performance.clone(), | ||
)); | ||
}, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet! This is exactly what I had in mind. Not sure if config
is the name I would go with, but this is the exact idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! We can easily rename config as it's only ever used in the macro. I'm happy to do a bikeshedding phase at the end as the consistent macro layout will make that very easy.
core/extensions.rs
Outdated
macro_rules! ops { | ||
($name:ident, parameters = [ $( $param:ident : $type:ident ),+ ], ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $op_param:ident > )? ),+ $(,)? ]) => { | ||
pub(crate) fn $name < $( $param : $type + 'static ),+ > () -> Vec<$crate::OpDecl> { | ||
let mut v = vec![]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to use a static slice here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine to do a vector in the first pass, just asking for a potential followup cleanup/optimization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that we can use a static slice. I believe we can drastically reduce or eliminate the cloning to populate the state that happens as well.
EDIT: op_xxx::decl()
isn't const
so this isn't possible (yet)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be great to see - even with all the improvements from #18080 there are still visible stack in the flamegraph related to initializing ops. Reducing clones and using static slices should help with that 👍
config = { | ||
sender: TestEventSender, | ||
fail_fast_tracker: FailFastTracker, | ||
filter: TestFilter, | ||
}, | ||
state = |state, sender, fail_fast_tracker, filter| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@littledivy suggested to me that maybe we should have a single struct instead of config
and then state
fn would always receive either 1 (state
) or 2 (state
and config struct) arguments. Any thought @mmastrac?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would work as well. I actually had that in an earlier pass, but I was worried that having to prefix everything w/cfg.
added a lot of noise. I'll circle back at the end on this one as well.
/// * state: a state initialization function, with the signature `fn (&mut OpState, ...) -> ()`, where `...` are parameters matching the fields of the config struct | ||
/// * event_loop_middleware: an event-loop middleware function (see [`ExtensionBuilder::event_loop_middleware`]) | ||
#[macro_export] | ||
macro_rules! extension { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is sophisticated. we need to have expansion tests like in deno_ops
, see https://github.com/eupn/macrotest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@littledivy Do we have a nightly compiler on the CI machines to run the macro expansion tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh umm yeah we don't use nightly. Let's just have https://github.com/dtolnay/trybuild tests, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of using macrotest
more than trybuild
TBH but I think I'd like to split this off into a new PR and experiment a bit. The trybuild
tests don't give much signal beyond the codebase compiling and I think there's some value in both seeing the macro output and failing when things change like your nice ops
tests.
Looks great @mmastrac. I think Divy's suggestions are valid - once we address them this should be good to merge. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be wary of introducing too much meta-programming. This patch is a net reduction of ~150 lines. But it introduces a lot of complex in macro programming. Because there's no runtime benefit, it's not obvious to me this is a clear win.
There's no runtime benefit in this PR, but there will be in a follow up - we will reduce amount of vector allocations significantly when registering extensions on startup and thus make it cheaper to create the main worker instance - there's not gonna be that much difference but I expect to see around 0.5ms improvement in the startup time. |
@bartlomieju The static ops list can done without this macro too. Moreover, there are places where static array won't work. Line 765 in e6fe163
&'static [OpDecl] .
|
The Barring
I'd lean towards the first one, but regardless of which option we choose, it'll require a massive refactoring across the codebase unless we have all the Without this PR landing in every case, we'd basically be making changes to ~20+ op lists across the whole codebase. Either way, having a macro that's not perfect and exactly what we'll want six months from now is better than nothing, as iterating and experimenting becomes 20x cheaper. |
Benchmarked against
This PR cuts startup time by 0.3ms, so IMO it's worth it to land even with additional complexity. It became obvious with Matt's work that there are some unnecessary allocations happening and it's a good forcing function to address them. |
Any ideas as to where that improvement comes from? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Barring const OpDecls being available, we have three options:
I'm not sure I understand. Extensions holding their individual &'static mut [OpDecl]
could just work too.
Without this PR landing in every case, we'd basically be making changes to ~20+ op lists across the whole codebase.
Good point.
|
ops.extend(crate::ops_builtin_v8::init_builtins_v8()); | ||
ext.ops(ops) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One reallocation gone!
unsafely_ignore_certificate_errors: Option<Vec<String>>, | ||
) -> &mut ExtensionBuilder { | ||
let mut ops = ops::init::<P>(); | ||
ops.extend(ops_tls::init::<P>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second reallocation gone 🚀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, fantastic first PR.
Please update the commit message before landing (ie. remove mention of ops_bundle!
)
Right. decl() shouldn't be hard to make Lines 48 to 56 in fe07f6c
It's just the fast_fn that can be converted, it can be made a fixed sized trait. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 2 - Great PR
This would be great to have. I think with that we could probably move most of the extension setup to compile-time which would be pretty sweet. I wasn't able to get a good handle on what that particular change would require as it went into some of the magic in |
This implements two new macros for extension registration to reduce a lot of the boilerplate and open the door for some future improvements:
deno_core::ops!
registers a block of#[op]
s, optionally with type parameters.deno_core::extension!
is used to register an extension, and creates two methods that can be used at runtime/snapshot generation time:init_ops
andinit_ops_and_esm
.An extension may look something like this: