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_fast.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_fast.rs')
-rw-r--r-- | ops/op2/dispatch_fast.rs | 293 |
1 files changed, 189 insertions, 104 deletions
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs index 94140dbf6..5262196f4 100644 --- a/ops/op2/dispatch_fast.rs +++ b/ops/op2/dispatch_fast.rs @@ -11,7 +11,7 @@ use quote::quote; #[allow(unused)] #[derive(Debug, Default, PartialEq, Clone)] -pub(crate) enum FastValue { +pub(crate) enum V8FastCallType { #[default] Void, Bool, @@ -27,66 +27,74 @@ pub(crate) enum FastValue { Uint32Array, Float64Array, SeqOneByteString, + CallbackOptions, } -impl FastValue { +impl V8FastCallType { /// Quote fast value type. - fn quote_rust_type(&self) -> TokenStream { + fn quote_rust_type(&self, deno_core: &TokenStream) -> TokenStream { match self { - FastValue::Void => quote!(()), - FastValue::Bool => quote!(bool), - FastValue::U32 => quote!(u32), - FastValue::I32 => quote!(i32), - FastValue::U64 => quote!(u64), - FastValue::I64 => quote!(i64), - FastValue::F32 => quote!(f32), - FastValue::F64 => quote!(f64), - FastValue::Pointer => quote!(*mut ::std::ffi::c_void), - FastValue::V8Value => unimplemented!("v8::Local<v8::Value>"), - FastValue::Uint8Array - | FastValue::Uint32Array - | FastValue::Float64Array - | FastValue::SeqOneByteString => unreachable!(), + V8FastCallType::Void => quote!(()), + V8FastCallType::Bool => quote!(bool), + V8FastCallType::U32 => quote!(u32), + V8FastCallType::I32 => quote!(i32), + V8FastCallType::U64 => quote!(u64), + V8FastCallType::I64 => quote!(i64), + V8FastCallType::F32 => quote!(f32), + V8FastCallType::F64 => quote!(f64), + V8FastCallType::Pointer => quote!(*mut ::std::ffi::c_void), + V8FastCallType::V8Value => { + quote!(#deno_core::v8::Local<#deno_core::v8::Value>) + } + V8FastCallType::CallbackOptions => { + quote!(*mut #deno_core::v8::fast_api::FastApiCallbackOptions) + } + V8FastCallType::Uint8Array + | V8FastCallType::Uint32Array + | V8FastCallType::Float64Array + | V8FastCallType::SeqOneByteString => unreachable!(), } } /// Quote fast value type's variant. fn quote_ctype(&self) -> TokenStream { match &self { - FastValue::Void => quote!(CType::Void), - FastValue::Bool => quote!(CType::Bool), - FastValue::U32 => quote!(CType::Uint32), - FastValue::I32 => quote!(CType::Int32), - FastValue::U64 => quote!(CType::Uint64), - FastValue::I64 => quote!(CType::Int64), - FastValue::F32 => quote!(CType::Float32), - FastValue::F64 => quote!(CType::Float64), - FastValue::Pointer => quote!(CType::Pointer), - FastValue::V8Value => quote!(CType::V8Value), - FastValue::Uint8Array => unreachable!(), - FastValue::Uint32Array => unreachable!(), - FastValue::Float64Array => unreachable!(), - FastValue::SeqOneByteString => quote!(CType::SeqOneByteString), + V8FastCallType::Void => quote!(CType::Void), + V8FastCallType::Bool => quote!(CType::Bool), + V8FastCallType::U32 => quote!(CType::Uint32), + V8FastCallType::I32 => quote!(CType::Int32), + V8FastCallType::U64 => quote!(CType::Uint64), + V8FastCallType::I64 => quote!(CType::Int64), + V8FastCallType::F32 => quote!(CType::Float32), + V8FastCallType::F64 => quote!(CType::Float64), + V8FastCallType::Pointer => quote!(CType::Pointer), + V8FastCallType::V8Value => quote!(CType::V8Value), + V8FastCallType::CallbackOptions => quote!(CType::CallbackOptions), + V8FastCallType::Uint8Array => unreachable!(), + V8FastCallType::Uint32Array => unreachable!(), + V8FastCallType::Float64Array => unreachable!(), + V8FastCallType::SeqOneByteString => quote!(CType::SeqOneByteString), } } /// Quote fast value type's variant. fn quote_type(&self) -> TokenStream { match &self { - FastValue::Void => quote!(Type::Void), - FastValue::Bool => quote!(Type::Bool), - FastValue::U32 => quote!(Type::Uint32), - FastValue::I32 => quote!(Type::Int32), - FastValue::U64 => quote!(Type::Uint64), - FastValue::I64 => quote!(Type::Int64), - FastValue::F32 => quote!(Type::Float32), - FastValue::F64 => quote!(Type::Float64), - FastValue::Pointer => quote!(Type::Pointer), - FastValue::V8Value => quote!(Type::V8Value), - FastValue::Uint8Array => quote!(Type::TypedArray(CType::Uint8)), - FastValue::Uint32Array => quote!(Type::TypedArray(CType::Uint32)), - FastValue::Float64Array => quote!(Type::TypedArray(CType::Float64)), - FastValue::SeqOneByteString => quote!(Type::SeqOneByteString), + V8FastCallType::Void => quote!(Type::Void), + V8FastCallType::Bool => quote!(Type::Bool), + V8FastCallType::U32 => quote!(Type::Uint32), + V8FastCallType::I32 => quote!(Type::Int32), + V8FastCallType::U64 => quote!(Type::Uint64), + V8FastCallType::I64 => quote!(Type::Int64), + V8FastCallType::F32 => quote!(Type::Float32), + V8FastCallType::F64 => quote!(Type::Float64), + V8FastCallType::Pointer => quote!(Type::Pointer), + V8FastCallType::V8Value => quote!(Type::V8Value), + V8FastCallType::CallbackOptions => quote!(Type::CallbackOptions), + V8FastCallType::Uint8Array => quote!(Type::TypedArray(CType::Uint8)), + V8FastCallType::Uint32Array => quote!(Type::TypedArray(CType::Uint32)), + V8FastCallType::Float64Array => quote!(Type::TypedArray(CType::Float64)), + V8FastCallType::SeqOneByteString => quote!(Type::SeqOneByteString), } } } @@ -95,104 +103,181 @@ pub fn generate_dispatch_fast( generator_state: &mut GeneratorState, signature: &ParsedSignature, ) -> Result<Option<(TokenStream, TokenStream)>, V8MappingError> { - // Result not fast-call compatible (yet) - if matches!(signature.ret_val, RetVal::Result(..)) { - return Ok(None); - } - let mut inputs = vec![]; for arg in &signature.args { - let fv = match arg { - Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None), - Arg::Numeric(NumericArg::bool) => FastValue::Bool, - Arg::Numeric(NumericArg::u32) - | Arg::Numeric(NumericArg::u16) - | Arg::Numeric(NumericArg::u8) => FastValue::U32, - Arg::Numeric(NumericArg::i32) - | Arg::Numeric(NumericArg::i16) - | Arg::Numeric(NumericArg::i8) - | Arg::Numeric(NumericArg::__SMI__) => FastValue::I32, - Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => { - FastValue::U64 - } - Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => { - FastValue::I64 - } - _ => { - return Err(V8MappingError::NoMapping("a fast argument", arg.clone())) - } + let Some(fv) = map_arg_to_v8_fastcall_type(arg)? else { + return Ok(None); }; inputs.push(fv); } + let mut names = inputs + .iter() + .enumerate() + .map(|(i, _)| format_ident!("arg{i}")) + .collect::<Vec<_>>(); let ret_val = match &signature.ret_val { RetVal::Infallible(arg) => arg, RetVal::Result(arg) => arg, }; - let output = match ret_val { - Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None), - Arg::Void => FastValue::Void, - Arg::Numeric(NumericArg::bool) => FastValue::Bool, - Arg::Numeric(NumericArg::u32) - | Arg::Numeric(NumericArg::u16) - | Arg::Numeric(NumericArg::u8) => FastValue::U32, - Arg::Numeric(NumericArg::i32) - | Arg::Numeric(NumericArg::i16) - | Arg::Numeric(NumericArg::i8) => FastValue::I32, - Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => { - FastValue::U64 - } - Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => { - FastValue::I64 - } - Arg::Special(_) => return Ok(None), - _ => { - return Err(V8MappingError::NoMapping( - "a fast return value", - ret_val.clone(), - )) - } + let output = match map_retval_to_v8_fastcall_type(ret_val)? { + None => return Ok(None), + Some(rv) => rv, }; let GeneratorState { fast_function, deno_core, + result, + opctx, + fast_api_callback_options, + needs_fast_api_callback_options, + needs_fast_opctx, .. - } = &generator_state; + } = generator_state; + + let handle_error = match signature.ret_val { + RetVal::Infallible(_) => quote!(), + RetVal::Result(_) => { + *needs_fast_api_callback_options = true; + *needs_fast_opctx = true; + inputs.push(V8FastCallType::CallbackOptions); + quote! { + let #result = match #result { + Ok(#result) => #result, + Err(err) => { + // FASTCALL FALLBACK: This is where we set 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. - let input_types = inputs.iter().map(|fv| fv.quote_type()); + // The reason we need to do this is because V8 does not allow exceptions to be thrown from the + // fast call. Instead, you are required to set the fallback flag, which indicates to V8 that it + // should re-call the slow version of the function. Technically the slow call should perform the + // same operation and then throw the same error (because it should be idempotent), but in our + // case we stash the error and pick it up on the slow path before doing any work. + + // TODO(mmastrac): We should allow an #[op] flag to re-perform slow calls without the error path when + // the method is performance sensitive. + + // 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. + unsafe { #opctx.unsafely_set_last_error_for_ops_only(err); } + #fast_api_callback_options.fallback = true; + return ::std::default::Default::default(); + } + }; + } + } + }; + + let input_types = inputs.iter().map(|fv| fv.quote_type()).collect::<Vec<_>>(); let output_type = output.quote_ctype(); let fast_definition = quote! { use #deno_core::v8::fast_api::Type; use #deno_core::v8::fast_api::CType; #deno_core::v8::fast_api::FastFunction::new( - &[ #( #input_types ),* ], + &[ Type::V8Value, #( #input_types ),* ], #output_type, Self::#fast_function as *const ::std::ffi::c_void ) }; - let output_type = output.quote_rust_type(); - let names = &inputs + let output_type = output.quote_rust_type(deno_core); + let mut types = inputs .iter() - .enumerate() - .map(|(i, _)| format_ident!("arg{i}")) + .map(|rv| rv.quote_rust_type(deno_core)) .collect::<Vec<_>>(); - let types = inputs.iter().map(|rv| rv.quote_rust_type()); + + let call_args = names.clone(); + + let with_fast_api_callback_options = if *needs_fast_api_callback_options { + types.push(V8FastCallType::CallbackOptions.quote_rust_type(deno_core)); + names.push(fast_api_callback_options.clone()); + quote! { + let #fast_api_callback_options = unsafe { &mut *#fast_api_callback_options }; + } + } else { + quote!() + }; + let with_opctx = if *needs_fast_opctx { + quote!( + let #opctx = unsafe { + &*(#deno_core::v8::Local::<v8::External>::cast(unsafe { #fast_api_callback_options.data.data }).value() + as *const #deno_core::_ops::OpCtx) + }; + ) + } else { + quote!() + }; let fast_fn = quote!( fn #fast_function( _: #deno_core::v8::Local<#deno_core::v8::Object>, #( #names: #types, )* ) -> #output_type { - #( - let #names = #names as _; - )* - Self::call(#(#names),*) + #with_fast_api_callback_options + #with_opctx + let #result = Self::call(#(#call_args as _),*); + #handle_error + #result } ); Ok(Some((fast_definition, fast_fn))) } + +fn map_arg_to_v8_fastcall_type( + arg: &Arg, +) -> Result<Option<V8FastCallType>, V8MappingError> { + let rv = match arg { + Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None), + Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool, + Arg::Numeric(NumericArg::u32) + | Arg::Numeric(NumericArg::u16) + | Arg::Numeric(NumericArg::u8) => V8FastCallType::U32, + Arg::Numeric(NumericArg::i32) + | Arg::Numeric(NumericArg::i16) + | Arg::Numeric(NumericArg::i8) + | Arg::Numeric(NumericArg::__SMI__) => V8FastCallType::I32, + Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => { + V8FastCallType::U64 + } + Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => { + V8FastCallType::I64 + } + _ => return Err(V8MappingError::NoMapping("a fast argument", arg.clone())), + }; + Ok(Some(rv)) +} + +fn map_retval_to_v8_fastcall_type( + arg: &Arg, +) -> Result<Option<V8FastCallType>, V8MappingError> { + let rv = match arg { + Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None), + Arg::Void => V8FastCallType::Void, + Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool, + Arg::Numeric(NumericArg::u32) + | Arg::Numeric(NumericArg::u16) + | Arg::Numeric(NumericArg::u8) => V8FastCallType::U32, + Arg::Numeric(NumericArg::i32) + | Arg::Numeric(NumericArg::i16) + | Arg::Numeric(NumericArg::i8) => V8FastCallType::I32, + Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => { + V8FastCallType::U64 + } + Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => { + V8FastCallType::I64 + } + Arg::Special(_) => return Ok(None), + _ => { + return Err(V8MappingError::NoMapping( + "a fast return value", + arg.clone(), + )) + } + }; + Ok(Some(rv)) +} |