1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-12 00:54:02 -05:00
denoland-deno/ops/op2/dispatch_slow.rs
Matt Mastracci 8fe9b8a4cc
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.
2023-06-25 16:36:09 +02:00

343 lines
8.7 KiB
Rust

use super::MacroConfig;
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use super::generator_state::GeneratorState;
use super::signature::Arg;
use super::signature::NumericArg;
use super::signature::ParsedSignature;
use super::signature::RetVal;
use super::signature::Special;
use super::V8MappingError;
use proc_macro2::TokenStream;
use quote::quote;
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)?);
}
output.extend(call(generator_state)?);
output.extend(return_value(generator_state, &signature.ret_val)?);
let with_scope = if generator_state.needs_scope {
with_scope(generator_state)
} else {
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 {
quote!()
};
let with_args = if generator_state.needs_args {
with_fn_args(generator_state)
} else {
quote!()
};
let GeneratorState {
deno_core,
info,
slow_function,
..
} = &generator_state;
Ok(quote! {
pub extern "C" fn #slow_function(#info: *const #deno_core::v8::FunctionCallbackInfo) {
#with_scope
#with_retval
#with_args
#with_opctx
#output
}})
}
fn with_scope(generator_state: &mut GeneratorState) -> TokenStream {
let GeneratorState {
deno_core,
scope,
info,
..
} = &generator_state;
quote!(let #scope = &mut unsafe { #deno_core::v8::CallbackScope::new(&*#info) };)
}
fn with_retval(generator_state: &mut GeneratorState) -> TokenStream {
let GeneratorState {
deno_core,
retval,
info,
..
} = &generator_state;
quote!(let mut #retval = #deno_core::v8::ReturnValue::from_function_callback_info(unsafe { &*#info });)
}
fn with_fn_args(generator_state: &mut GeneratorState) -> TokenStream {
let GeneratorState {
deno_core,
fn_args,
info,
..
} = &generator_state;
quote!(let #fn_args = #deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe { &*#info });)
}
fn with_opctx(generator_state: &mut GeneratorState) -> TokenStream {
let GeneratorState {
deno_core,
opctx,
fn_args,
needs_args,
..
} = 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)
};)
}
pub fn extract_arg(
generator_state: &mut GeneratorState,
index: usize,
) -> Result<TokenStream, V8MappingError> {
let GeneratorState { fn_args, .. } = &generator_state;
let arg_ident = generator_state.args.get(index);
Ok(quote!(
let #arg_ident = #fn_args.get(#index as i32);
))
}
pub fn from_arg(
mut generator_state: &mut GeneratorState,
index: usize,
arg: &Arg,
) -> Result<TokenStream, V8MappingError> {
let GeneratorState {
deno_core, args, ..
} = &mut generator_state;
let arg_ident = args.get_mut(index).expect("Argument at index was missing");
let res = match arg {
Arg::Numeric(NumericArg::bool) => quote! {
let #arg_ident = #arg_ident.is_true();
},
Arg::Numeric(NumericArg::u8)
| Arg::Numeric(NumericArg::u16)
| Arg::Numeric(NumericArg::u32) => {
quote! {
let #arg_ident = #deno_core::_ops::to_u32(&#arg_ident) as _;
}
}
Arg::Numeric(NumericArg::i8)
| Arg::Numeric(NumericArg::i16)
| Arg::Numeric(NumericArg::i32)
| Arg::Numeric(NumericArg::__SMI__) => {
quote! {
let #arg_ident = #deno_core::_ops::to_i32(&#arg_ident) as _;
}
}
Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
quote! {
let #arg_ident = #deno_core::_ops::to_u64(&#arg_ident) as _;
}
}
Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
quote! {
let #arg_ident = #deno_core::_ops::to_i64(&#arg_ident) as _;
}
}
Arg::OptionNumeric(numeric) => {
// Ends the borrow of generator_state
let arg_ident = arg_ident.clone();
let some = from_arg(generator_state, index, &Arg::Numeric(*numeric))?;
quote! {
let #arg_ident = if #arg_ident.is_null_or_undefined() {
None
} else {
#some
Some(#arg_ident)
};
}
}
Arg::Option(Special::String) => {
quote! {
let #arg_ident = #arg_ident.to_rust_string_lossy();
}
}
Arg::Special(Special::RefStr) => {
quote! {
let #arg_ident = #arg_ident.to_rust_string_lossy();
}
}
_ => return Err(V8MappingError::NoMapping("a slow argument", arg.clone())),
};
Ok(res)
}
pub fn call(
generator_state: &mut GeneratorState,
) -> Result<TokenStream, V8MappingError> {
let GeneratorState { result, .. } = &generator_state;
let mut tokens = TokenStream::new();
for arg in &generator_state.args {
tokens.extend(quote!( #arg , ));
}
Ok(quote! {
let #result = Self::call( #tokens );
})
}
pub fn return_value(
generator_state: &mut GeneratorState,
ret_type: &RetVal,
) -> Result<TokenStream, V8MappingError> {
match ret_type {
RetVal::Infallible(ret_type) => {
return_value_infallible(generator_state, ret_type)
}
RetVal::Result(ret_type) => return_value_result(generator_state, ret_type),
}
}
pub fn return_value_infallible(
generator_state: &mut GeneratorState,
ret_type: &Arg,
) -> Result<TokenStream, V8MappingError> {
let GeneratorState {
result,
retval,
needs_retval,
..
} = generator_state;
let res = match ret_type {
Arg::Void => {
quote! {/* void */}
}
Arg::Numeric(NumericArg::u8)
| Arg::Numeric(NumericArg::u16)
| Arg::Numeric(NumericArg::u32) => {
*needs_retval = true;
quote!(#retval.set_uint32(#result as u32);)
}
Arg::Numeric(NumericArg::i8)
| Arg::Numeric(NumericArg::i16)
| Arg::Numeric(NumericArg::i32) => {
*needs_retval = true;
quote!(#retval.set_int32(#result as i32);)
}
_ => {
return Err(V8MappingError::NoMapping(
"a slow return value",
ret_type.clone(),
))
}
};
Ok(res)
}
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_opctx = if generator_state.needs_opctx {
quote!()
} else {
with_opctx(generator_state)
};
let maybe_args = if generator_state.needs_args {
quote!()
} else {
with_fn_args(generator_state)
};
let GeneratorState {
deno_core,
scope,
opctx,
..
} = &generator_state;
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;
})
}