diff options
author | Matt Mastracci <matthew@mastracci.com> | 2023-06-25 16:36:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-25 16:36:09 +0200 |
commit | 8fe9b8a4cc381f9b94ce2caf10c61ddff864bdb4 (patch) | |
tree | 9ae0797ef865f0f0a023052f7dbe433d238561d4 /ops/op2/dispatch_slow.rs | |
parent | 3fe44a50c37b3f045c95a2085b58cfe3f71e8f6a (diff) |
refactor(ops): ops2 supports result in fast path (#19603)
Implements `Result` in fast-calls. Note that the approach here is
slightly different. Rather than store the last result in the `OpState`,
we put it into the `OpCtx` which saves us a lookup and lock in the error
case. We do not have to lock this field as it's guaranteed only one
runtime and thread can ever access it.
The fastcall path for many ops can avoid doing a great deal of work,
even for `Result` return values. In the previous iteration of `ops`, all
`Result`-returning functions would fetch and lock the `OpState`,
regardless of whether it was used or not.
Diffstat (limited to 'ops/op2/dispatch_slow.rs')
-rw-r--r-- | ops/op2/dispatch_slow.rs | 100 |
1 files changed, 71 insertions, 29 deletions
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs index f54a28f1c..f10217a2d 100644 --- a/ops/op2/dispatch_slow.rs +++ b/ops/op2/dispatch_slow.rs @@ -1,3 +1,4 @@ +use super::MacroConfig; // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use super::generator_state::GeneratorState; use super::signature::Arg; @@ -9,11 +10,31 @@ use super::V8MappingError; use proc_macro2::TokenStream; use quote::quote; -pub fn generate_dispatch_slow( +pub(crate) fn generate_dispatch_slow( + config: &MacroConfig, generator_state: &mut GeneratorState, signature: &ParsedSignature, ) -> Result<TokenStream, V8MappingError> { let mut output = TokenStream::new(); + + // Fast ops require the slow op to check op_ctx for the last error + if config.fast && matches!(signature.ret_val, RetVal::Result(_)) { + generator_state.needs_opctx = true; + let throw_exception = throw_exception(generator_state)?; + // If the fast op returned an error, we must throw it rather than doing work. + output.extend(quote!{ + // FASTCALL FALLBACK: This is where we pick up the errors for the slow-call error pickup + // path. There is no code running between this and the other FASTCALL FALLBACK comment, + // except some V8 code required to perform the fallback process. This is why the below call is safe. + + // SAFETY: We guarantee that OpCtx has no mutable references once ops are live and being called, + // allowing us to perform this one little bit of mutable magic. + if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } { + #throw_exception + } + }); + } + for (index, arg) in signature.args.iter().enumerate() { output.extend(extract_arg(generator_state, index)?); output.extend(from_arg(generator_state, index, arg)?); @@ -27,6 +48,12 @@ pub fn generate_dispatch_slow( quote!() }; + let with_opctx = if generator_state.needs_opctx { + with_opctx(generator_state) + } else { + quote!() + }; + let with_retval = if generator_state.needs_retval { with_retval(generator_state) } else { @@ -51,6 +78,7 @@ pub fn generate_dispatch_slow( #with_scope #with_retval #with_args + #with_opctx #output }}) @@ -94,9 +122,11 @@ fn with_opctx(generator_state: &mut GeneratorState) -> TokenStream { deno_core, opctx, fn_args, + needs_args, .. - } = &generator_state; + } = generator_state; + *needs_args = true; quote!(let #opctx = unsafe { &*(#deno_core::v8::Local::<#deno_core::v8::External>::cast(#fn_args.data()).value() as *const #deno_core::_ops::OpCtx) @@ -246,56 +276,68 @@ pub fn return_value_infallible( Ok(res) } -pub fn return_value_result( +fn return_value_result( generator_state: &mut GeneratorState, ret_type: &Arg, ) -> Result<TokenStream, V8MappingError> { let infallible = return_value_infallible(generator_state, ret_type)?; + let exception = throw_exception(generator_state)?; + + let GeneratorState { result, .. } = &generator_state; + + let tokens = quote!( + match #result { + Ok(#result) => { + #infallible + } + Err(err) => { + #exception + } + }; + ); + Ok(tokens) +} + +/// Generates code to throw an exception, adding required additional dependencies as needed. +fn throw_exception( + generator_state: &mut GeneratorState, +) -> Result<TokenStream, V8MappingError> { let maybe_scope = if generator_state.needs_scope { quote!() } else { with_scope(generator_state) }; - let maybe_args = if generator_state.needs_args { + let maybe_opctx = if generator_state.needs_opctx { quote!() } else { - with_fn_args(generator_state) + with_opctx(generator_state) }; - let maybe_opctx = if generator_state.needs_opctx { + let maybe_args = if generator_state.needs_args { quote!() } else { - with_opctx(generator_state) + with_fn_args(generator_state) }; let GeneratorState { deno_core, - result, scope, opctx, .. } = &generator_state; - let tokens = quote!( - match #result { - Ok(#result) => { - #infallible - } - Err(err) => { - #maybe_scope - #maybe_args - #maybe_opctx - let opstate = ::std::cell::RefCell::borrow(&*#opctx.state); - let exception = #deno_core::error::to_v8_error( - #scope, - opstate.get_error_class_fn, - &err, - ); - scope.throw_exception(exception); - return; - } - }; - ); - Ok(tokens) + Ok(quote! { + #maybe_scope + #maybe_args + #maybe_opctx + let opstate = ::std::cell::RefCell::borrow(&*#opctx.state); + let exception = #deno_core::error::to_v8_error( + #scope, + opstate.get_error_class_fn, + &err, + ); + scope.throw_exception(exception); + return; + }) } |