mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
feat(ops): Fallible fast ops (#15989)
This commit is contained in:
parent
1b04ff0782
commit
b5dfcbbcbe
4 changed files with 122 additions and 32 deletions
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use crate::error::AnyError;
|
||||||
use crate::gotham_state::GothamState;
|
use crate::gotham_state::GothamState;
|
||||||
use crate::resources::ResourceTable;
|
use crate::resources::ResourceTable;
|
||||||
use crate::runtime::GetErrorClassFn;
|
use crate::runtime::GetErrorClassFn;
|
||||||
|
@ -158,6 +159,7 @@ pub struct OpState {
|
||||||
pub resource_table: ResourceTable,
|
pub resource_table: ResourceTable,
|
||||||
pub get_error_class_fn: GetErrorClassFn,
|
pub get_error_class_fn: GetErrorClassFn,
|
||||||
pub tracker: OpsTracker,
|
pub tracker: OpsTracker,
|
||||||
|
pub last_fast_op_error: Option<AnyError>,
|
||||||
gotham_state: GothamState,
|
gotham_state: GothamState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +169,7 @@ impl OpState {
|
||||||
resource_table: Default::default(),
|
resource_table: Default::default(),
|
||||||
get_error_class_fn: &|_| "Error",
|
get_error_class_fn: &|_| "Error",
|
||||||
gotham_state: Default::default(),
|
gotham_state: Default::default(),
|
||||||
|
last_fast_op_error: None,
|
||||||
tracker: OpsTracker::new(ops_count),
|
tracker: OpsTracker::new(ops_count),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
130
ops/lib.rs
130
ops/lib.rs
|
@ -113,13 +113,16 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
let asyncness = func.sig.asyncness.is_some();
|
let asyncness = func.sig.asyncness.is_some();
|
||||||
let is_async = asyncness || is_future(&func.sig.output);
|
let is_async = asyncness || is_future(&func.sig.output);
|
||||||
|
|
||||||
|
// First generate fast call bindings to opt-in to error handling in slow call
|
||||||
|
let (has_fallible_fast_call, fast_impl, fast_field) =
|
||||||
|
codegen_fast_impl(&core, &func, name, is_async, must_be_fast);
|
||||||
|
|
||||||
let v8_body = if is_async {
|
let v8_body = if is_async {
|
||||||
codegen_v8_async(&core, &func, margs, asyncness, deferred)
|
codegen_v8_async(&core, &func, margs, asyncness, deferred)
|
||||||
} else {
|
} else {
|
||||||
codegen_v8_sync(&core, &func, margs)
|
codegen_v8_sync(&core, &func, margs, has_fallible_fast_call)
|
||||||
};
|
};
|
||||||
let (fast_impl, fast_field) =
|
|
||||||
codegen_fast_impl(&core, &func, name, is_async, must_be_fast);
|
|
||||||
|
|
||||||
let docline = format!("Use `{name}::decl()` to get an op-declaration");
|
let docline = format!("Use `{name}::decl()` to get an op-declaration");
|
||||||
// Generate wrapper
|
// Generate wrapper
|
||||||
|
@ -293,12 +296,12 @@ fn codegen_fast_impl(
|
||||||
name: &syn::Ident,
|
name: &syn::Ident,
|
||||||
is_async: bool,
|
is_async: bool,
|
||||||
must_be_fast: bool,
|
must_be_fast: bool,
|
||||||
) -> (TokenStream2, TokenStream2) {
|
) -> (bool, TokenStream2, TokenStream2) {
|
||||||
if is_async {
|
if is_async {
|
||||||
if must_be_fast {
|
if must_be_fast {
|
||||||
panic!("async op cannot be a fast api. enforced by #[op(fast)]")
|
panic!("async op cannot be a fast api. enforced by #[op(fast)]")
|
||||||
}
|
}
|
||||||
return (quote! {}, quote! { None });
|
return (false, quote! {}, quote! { None });
|
||||||
}
|
}
|
||||||
let fast_info = can_be_fast_api(core, f);
|
let fast_info = can_be_fast_api(core, f);
|
||||||
if must_be_fast && fast_info.is_none() {
|
if must_be_fast && fast_info.is_none() {
|
||||||
|
@ -311,6 +314,7 @@ fn codegen_fast_impl(
|
||||||
use_op_state,
|
use_op_state,
|
||||||
use_fast_cb_opts,
|
use_fast_cb_opts,
|
||||||
v8_values,
|
v8_values,
|
||||||
|
returns_result,
|
||||||
slices,
|
slices,
|
||||||
}) = fast_info
|
}) = fast_info
|
||||||
{
|
{
|
||||||
|
@ -341,7 +345,9 @@ fn codegen_fast_impl(
|
||||||
quote!(#arg)
|
quote!(#arg)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if (!slices.is_empty() || use_op_state) && !use_fast_cb_opts {
|
if (!slices.is_empty() || use_op_state || returns_result)
|
||||||
|
&& !use_fast_cb_opts
|
||||||
|
{
|
||||||
inputs.push(quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions });
|
inputs.push(quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions });
|
||||||
}
|
}
|
||||||
let input_idents = f
|
let input_idents = f
|
||||||
|
@ -410,13 +416,22 @@ fn codegen_fast_impl(
|
||||||
quote! {},
|
quote! {},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let output = &f.sig.output;
|
let output = if returns_result {
|
||||||
|
get_fast_result_return_type(&f.sig.output)
|
||||||
|
} else {
|
||||||
|
let output = &f.sig.output;
|
||||||
|
quote! { #output }
|
||||||
|
};
|
||||||
let func_name = format_ident!("func_{}", name);
|
let func_name = format_ident!("func_{}", name);
|
||||||
let recv_decl = if use_op_state {
|
let op_state_name = if use_op_state {
|
||||||
let op_state_name = input_idents.first();
|
input_idents.first().unwrap().clone()
|
||||||
|
} else {
|
||||||
|
quote! { op_state }
|
||||||
|
};
|
||||||
|
let recv_decl = if use_op_state || returns_result {
|
||||||
quote! {
|
quote! {
|
||||||
// SAFETY: V8 calling convention guarantees that the callback options pointer is non-null.
|
// SAFETY: V8 calling convention guarantees that the callback options pointer is non-null.
|
||||||
let opts: &#core::v8::fast_api::FastApiCallbackOptions = unsafe { &*fast_api_callback_options };
|
let opts: &mut #core::v8::fast_api::FastApiCallbackOptions = unsafe { &mut *fast_api_callback_options };
|
||||||
// SAFETY: data union is always created as the `v8::Local<v8::Value>` version.
|
// SAFETY: data union is always created as the `v8::Local<v8::Value>` version.
|
||||||
let data = unsafe { opts.data.data };
|
let data = unsafe { opts.data.data };
|
||||||
// SAFETY: #core guarantees data is a v8 External pointing to an OpCtx for the isolates lifetime
|
// SAFETY: #core guarantees data is a v8 External pointing to an OpCtx for the isolates lifetime
|
||||||
|
@ -427,14 +442,32 @@ fn codegen_fast_impl(
|
||||||
let #op_state_name = &mut ctx.state.borrow_mut();
|
let #op_state_name = &mut ctx.state.borrow_mut();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote!()
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result_handling = if returns_result {
|
||||||
|
quote! {
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
result
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
#op_state_name.last_fast_op_error.replace(err);
|
||||||
|
opts.fallback = true;
|
||||||
|
Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! { result }
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
quote! {
|
quote! {
|
||||||
fn #func_name #generics (_recv: #core::v8::Local<#core::v8::Object>, #(#inputs),*) #output #where_clause {
|
fn #func_name #generics (_recv: #core::v8::Local<#core::v8::Object>, #(#inputs),*) #output #where_clause {
|
||||||
#recv_decl
|
#recv_decl
|
||||||
#name::call::<#type_params>(#(#input_idents),*)
|
let result = #name::call::<#type_params>(#(#input_idents),*);
|
||||||
|
#result_handling
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -455,6 +488,7 @@ fn codegen_fast_impl(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
returns_result,
|
||||||
quote! {
|
quote! {
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -480,7 +514,7 @@ fn codegen_fast_impl(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default impl to satisfy generic bounds for non-fast ops
|
// Default impl to satisfy generic bounds for non-fast ops
|
||||||
(quote! {}, quote! { None })
|
(false, quote! {}, quote! { None })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the body of a v8 func for a sync op
|
/// Generate the body of a v8 func for a sync op
|
||||||
|
@ -488,6 +522,7 @@ fn codegen_v8_sync(
|
||||||
core: &TokenStream2,
|
core: &TokenStream2,
|
||||||
f: &syn::ItemFn,
|
f: &syn::ItemFn,
|
||||||
margs: MacroArgs,
|
margs: MacroArgs,
|
||||||
|
has_fallible_fast_call: bool,
|
||||||
) -> TokenStream2 {
|
) -> TokenStream2 {
|
||||||
let MacroArgs { is_v8, .. } = margs;
|
let MacroArgs { is_v8, .. } = margs;
|
||||||
let special_args = f
|
let special_args = f
|
||||||
|
@ -504,6 +539,21 @@ fn codegen_v8_sync(
|
||||||
let ret = codegen_sync_ret(core, &f.sig.output);
|
let ret = codegen_sync_ret(core, &f.sig.output);
|
||||||
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
||||||
|
|
||||||
|
let fast_error_handler = if has_fallible_fast_call {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let op_state = &mut ctx.state.borrow_mut();
|
||||||
|
if let Some(err) = op_state.last_fast_op_error.take() {
|
||||||
|
let exception = #core::error::to_v8_error(scope, op_state.get_error_class_fn, &err);
|
||||||
|
scope.throw_exception(exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
||||||
let ctx = unsafe {
|
let ctx = unsafe {
|
||||||
|
@ -511,6 +561,7 @@ fn codegen_v8_sync(
|
||||||
as *const #core::_ops::OpCtx)
|
as *const #core::_ops::OpCtx)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#fast_error_handler
|
||||||
#arg_decls
|
#arg_decls
|
||||||
|
|
||||||
let result = Self::call::<#type_params>(#args_head #args_tail);
|
let result = Self::call::<#type_params>(#args_head #args_tail);
|
||||||
|
@ -529,15 +580,20 @@ struct FastApiSyn {
|
||||||
use_op_state: bool,
|
use_op_state: bool,
|
||||||
use_fast_cb_opts: bool,
|
use_fast_cb_opts: bool,
|
||||||
v8_values: Vec<usize>,
|
v8_values: Vec<usize>,
|
||||||
|
returns_result: bool,
|
||||||
slices: HashMap<usize, TokenStream2>,
|
slices: HashMap<usize, TokenStream2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
|
fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
|
||||||
let inputs = &f.sig.inputs;
|
let inputs = &f.sig.inputs;
|
||||||
|
let mut returns_result = false;
|
||||||
let ret = match &f.sig.output {
|
let ret = match &f.sig.output {
|
||||||
syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void),
|
syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void),
|
||||||
syn::ReturnType::Type(_, ty) => match is_fast_scalar(core, ty, true) {
|
syn::ReturnType::Type(_, ty) => match is_fast_return_type(core, ty) {
|
||||||
Some(ret) => ret,
|
Some((ret, is_result)) => {
|
||||||
|
returns_result = is_result;
|
||||||
|
ret
|
||||||
|
}
|
||||||
None => return None,
|
None => return None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -605,6 +661,7 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
|
||||||
slices,
|
slices,
|
||||||
v8_values,
|
v8_values,
|
||||||
use_fast_cb_opts,
|
use_fast_cb_opts,
|
||||||
|
returns_result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,6 +707,49 @@ fn is_fast_typed_array(arg: impl ToTokens) -> bool {
|
||||||
RE.is_match(&tokens(arg))
|
RE.is_match(&tokens(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_fast_return_type(
|
||||||
|
core: &TokenStream2,
|
||||||
|
ty: impl ToTokens,
|
||||||
|
) -> Option<(TokenStream2, bool)> {
|
||||||
|
if is_result(&ty) {
|
||||||
|
if tokens(&ty).contains("Result < u32") || is_resource_id(&ty) {
|
||||||
|
Some((quote! { #core::v8::fast_api::CType::Uint32 }, true))
|
||||||
|
} else if tokens(&ty).contains("Result < i32") {
|
||||||
|
Some((quote! { #core::v8::fast_api::CType::Int32 }, true))
|
||||||
|
} else if tokens(&ty).contains("Result < f32") {
|
||||||
|
Some((quote! { #core::v8::fast_api::CType::Float32 }, true))
|
||||||
|
} else if tokens(&ty).contains("Result < f64") {
|
||||||
|
Some((quote! { #core::v8::fast_api::CType::Float64 }, true))
|
||||||
|
} else if tokens(&ty).contains("Result < bool") {
|
||||||
|
Some((quote! { #core::v8::fast_api::CType::Bool }, true))
|
||||||
|
} else if tokens(&ty).contains("Result < ()") {
|
||||||
|
Some((quote! { #core::v8::fast_api::CType::Void }, true))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
is_fast_scalar(core, ty, true).map(|s| (s, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fast_result_return_type(ty: impl ToTokens) -> TokenStream2 {
|
||||||
|
if tokens(&ty).contains("Result < u32") || is_resource_id(&ty) {
|
||||||
|
quote! { -> u32 }
|
||||||
|
} else if tokens(&ty).contains("Result < i32") {
|
||||||
|
quote! { -> i32 }
|
||||||
|
} else if tokens(&ty).contains("Result < f32") {
|
||||||
|
quote! { -> f32 }
|
||||||
|
} else if tokens(&ty).contains("Result < f64") {
|
||||||
|
quote! { -> f64 }
|
||||||
|
} else if tokens(&ty).contains("Result < bool") {
|
||||||
|
quote! { -> bool }
|
||||||
|
} else if tokens(&ty).contains("Result < ()") {
|
||||||
|
quote! {}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_fast_scalar(
|
fn is_fast_scalar(
|
||||||
core: &TokenStream2,
|
core: &TokenStream2,
|
||||||
ty: impl ToTokens,
|
ty: impl ToTokens,
|
||||||
|
|
|
@ -2,11 +2,6 @@
|
||||||
|
|
||||||
use deno_ops::op;
|
use deno_ops::op;
|
||||||
|
|
||||||
#[op(fast)]
|
|
||||||
fn op_result_return(a: i32, b: i32) -> Result<(), ()> {
|
|
||||||
a + b
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op(fast)]
|
#[op(fast)]
|
||||||
fn op_u8_arg(a: u8, b: u8) {
|
fn op_u8_arg(a: u8, b: u8) {
|
||||||
//
|
//
|
||||||
|
|
|
@ -15,9 +15,9 @@ error: custom attribute panicked
|
||||||
= help: message: op cannot be a fast api. enforced by #[op(fast)]
|
= help: message: op cannot be a fast api. enforced by #[op(fast)]
|
||||||
|
|
||||||
error: custom attribute panicked
|
error: custom attribute panicked
|
||||||
--> tests/compile_fail/unsupported.rs:15:1
|
--> tests/compile_fail/unsupported.rs:17:1
|
||||||
|
|
|
|
||||||
15 | #[op(fast)]
|
17 | #[op(fast)]
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: message: op cannot be a fast api. enforced by #[op(fast)]
|
= help: message: op cannot be a fast api. enforced by #[op(fast)]
|
||||||
|
@ -26,22 +26,14 @@ error: custom attribute panicked
|
||||||
--> tests/compile_fail/unsupported.rs:22:1
|
--> tests/compile_fail/unsupported.rs:22:1
|
||||||
|
|
|
|
||||||
22 | #[op(fast)]
|
22 | #[op(fast)]
|
||||||
| ^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= help: message: op cannot be a fast api. enforced by #[op(fast)]
|
|
||||||
|
|
||||||
error: custom attribute panicked
|
|
||||||
--> tests/compile_fail/unsupported.rs:27:1
|
|
||||||
|
|
|
||||||
27 | #[op(fast)]
|
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: message: async op cannot be a fast api. enforced by #[op(fast)]
|
= help: message: async op cannot be a fast api. enforced by #[op(fast)]
|
||||||
|
|
||||||
warning: unused import: `deno_core::v8::fast_api::FastApiCallbackOptions`
|
warning: unused import: `deno_core::v8::fast_api::FastApiCallbackOptions`
|
||||||
--> tests/compile_fail/unsupported.rs:20:5
|
--> tests/compile_fail/unsupported.rs:15:5
|
||||||
|
|
|
|
||||||
20 | use deno_core::v8::fast_api::FastApiCallbackOptions;
|
15 | use deno_core::v8::fast_api::FastApiCallbackOptions;
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: `#[warn(unused_imports)]` on by default
|
= note: `#[warn(unused_imports)]` on by default
|
||||||
|
|
Loading…
Add table
Reference in a new issue