diff options
Diffstat (limited to 'ops')
-rw-r--r-- | ops/Cargo.toml | 4 | ||||
-rw-r--r-- | ops/README.md | 28 | ||||
-rw-r--r-- | ops/lib.rs | 250 | ||||
-rw-r--r-- | ops/tests/compile_fail/unsupported.rs | 27 | ||||
-rw-r--r-- | ops/tests/compile_fail/unsupported.stderr | 31 | ||||
-rw-r--r-- | ops/tests/mod.rs | 5 |
6 files changed, 4 insertions, 341 deletions
diff --git a/ops/Cargo.toml b/ops/Cargo.toml index 9628ca91f..e0453b9e2 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -17,7 +17,3 @@ proc-macro2 = "1" quote = "1" regex = "1.5.6" syn = { version = "1", features = ["full", "extra-traits"] } - -[dev-dependencies] -deno_core = { path = "../core" } -trybuild = "1.0.61" diff --git a/ops/README.md b/ops/README.md index 7b61704b5..02ee266f7 100644 --- a/ops/README.md +++ b/ops/README.md @@ -14,31 +14,3 @@ Extension::builder() .ops(vec![op_add::decl()]) .build(); ``` - -## Peformance - -The macro can optimize away code, short circuit fast paths and generate a Fast -API impl. - -Cases where code is optimized away: - -- `-> ()` skips serde_v8 and `rv.set` calls. -- `-> Result<(), E>` skips serde_v8 and `rv.set` calls for `Ok()` branch. -- `-> ResourceId` or `-> [int]` types will use specialized method like - `v8::ReturnValue::set_uint32`. A fast path for SMI. -- `-> Result<ResourceId, E>` or `-> Result<[int], E>` types will be optimized - like above for the `Ok()` branch. - -### Fast calls - -The macro will infer and try to auto generate V8 fast API call trait impl for -`sync` ops with: - -- arguments: integers / `&mut OpState` -- return_type: integers - -The `#[op(fast)]` attribute can be used to enforce fast call generation at -compile time. - -Trait gen for `async` ops & a ZeroCopyBuf equivalent type is planned and will be -added soon. diff --git a/ops/lib.rs b/ops/lib.rs index bd40630ab..df8abf61e 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -1,13 +1,10 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use core::panic; use once_cell::sync::Lazy; use proc_macro::TokenStream; use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use proc_macro_crate::crate_name; use proc_macro_crate::FoundCrate; -use quote::format_ident; use quote::quote; use quote::ToTokens; use regex::Regex; @@ -17,9 +14,6 @@ use syn::FnArg; use syn::GenericParam; use syn::Ident; -#[cfg(test)] -mod tests; - // Identifier to the `deno_core` crate. // // If macro called in deno_core, `crate` is used. @@ -50,7 +44,6 @@ fn core_import() -> TokenStream2 { struct MacroArgs { is_unstable: bool, is_v8: bool, - must_be_fast: bool, } impl syn::parse::Parse for MacroArgs { @@ -62,7 +55,7 @@ impl syn::parse::Parse for MacroArgs { let vars: Vec<_> = vars.iter().map(Ident::to_string).collect(); let vars: Vec<_> = vars.iter().map(String::as_str).collect(); for var in vars.iter() { - if !["unstable", "v8", "fast"].contains(var) { + if !["unstable", "v8"].contains(var) { return Err(syn::Error::new( input.span(), "Ops expect #[op] or #[op(unstable)]", @@ -72,7 +65,6 @@ impl syn::parse::Parse for MacroArgs { Ok(Self { is_unstable: vars.contains(&"unstable"), is_v8: vars.contains(&"v8"), - must_be_fast: vars.contains(&"fast"), }) } } @@ -80,11 +72,7 @@ impl syn::parse::Parse for MacroArgs { #[proc_macro_attribute] pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { let margs = syn::parse_macro_input!(attr as MacroArgs); - let MacroArgs { - is_unstable, - is_v8, - must_be_fast, - } = margs; + let MacroArgs { is_unstable, is_v8 } = margs; let func = syn::parse::<syn::ItemFn>(item).expect("expected a function"); let name = &func.sig.ident; let mut generics = func.sig.generics.clone(); @@ -114,8 +102,6 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { } else { codegen_v8_sync(&core, &func, margs) }; - 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"); // Generate wrapper @@ -143,7 +129,6 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { name: Self::name(), v8_fn_ptr: Self::v8_fn_ptr::<#type_params>(), enabled: true, - fast_fn: #fast_field, is_async: #is_async, is_unstable: #is_unstable, is_v8: #is_v8, @@ -162,8 +147,6 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { #v8_body } } - - #fast_impl }.into() } @@ -277,114 +260,6 @@ fn opstate_arg(arg: &FnArg) -> Option<TokenStream2> { } } -fn codegen_fast_impl( - core: &TokenStream2, - f: &syn::ItemFn, - name: &syn::Ident, - is_async: bool, - must_be_fast: bool, -) -> (TokenStream2, TokenStream2) { - let fast_info = can_be_fast_api(core, f); - if must_be_fast && fast_info.is_none() { - panic!("op cannot be a fast api. enforced by #[op(fast)]") - } - if must_be_fast && is_async { - panic!("async op cannot be a fast api. enforced by #[op(fast)]") - } - if !is_async { - if let Some(FastApiSyn { - args, - ret, - use_recv, - }) = fast_info - { - let inputs = &f - .sig - .inputs - .iter() - .skip(if use_recv { 1 } else { 0 }) - .collect::<Vec<_>>(); - let input_idents = f - .sig - .inputs - .iter() - .map(|a| match a { - FnArg::Receiver(_) => unreachable!(), - FnArg::Typed(t) => match &*t.pat { - syn::Pat::Ident(i) => format_ident!("{}", i.ident), - _ => unreachable!(), - }, - }) - .collect::<Vec<_>>(); - let generics = &f.sig.generics; - let (impl_generics, ty_generics, where_clause) = - generics.split_for_impl(); - let type_params = exclude_lifetime_params(&f.sig.generics.params); - let (trampoline, raw_block) = if is_async { - // TODO(@littledivy): Fast async calls. - ( - quote! { - fn func(recv: #core::v8::Local<#core::v8::Object>, __promise_id: u32, #(#inputs),*) { - let op_ctx = recv.get_aligned_pointer_from_internal_field(#core::_ops::V8_WRAPPER_OBJECT_INDEX); - let op_id = op_ctx.op_id; - #core::_ops::queue_async_op(scope, async move { - let result = Self::call(#args); - (__promise_id, __op_id, #core::_ops::OpResult::Ok(result)) - }); - } - func as *const _ - }, - quote! {}, - ) - } else { - let output = &f.sig.output; - let func_name = format_ident!("func_{}", name); - let recv_decl = if use_recv { - quote! { - let ptr = unsafe { recv.get_aligned_pointer_from_internal_field(#core::_ops::V8_WRAPPER_OBJECT_INDEX) }; - let op_ctx = unsafe { &*(ptr as *const #core::_ops::OpCtx) }; - let state = &mut op_ctx.state.borrow_mut(); - } - } else { - quote!() - }; - - ( - quote! { - fn #func_name #generics (recv: #core::v8::Local<#core::v8::Object>, #(#inputs),*) #output #where_clause { - #recv_decl - #name::call::<#type_params>(#(#input_idents),*) - } - }, - quote! { - #func_name #ty_generics as *const _ - }, - ) - }; - return ( - quote! { - #trampoline - impl #impl_generics #core::v8::fast_api::FastFunction for #name #ty_generics { - fn function(&self) -> *const ::std::ffi::c_void { - #raw_block - } - fn args(&self) -> &'static [#core::v8::fast_api::Type] { - &[ #args ] - } - fn return_type(&self) -> #core::v8::fast_api::CType { - #ret - } - } - }, - quote! { Some(Box::new(#name #ty_generics)) }, - ); - } - } - - // Default impl to satisfy generic bounds for non-fast ops - (quote! {}, quote! { None }) -} - /// Generate the body of a v8 func for a sync op fn codegen_v8_sync( core: &TokenStream2, @@ -402,6 +277,7 @@ fn codegen_v8_sync( .collect::<Vec<_>>(); let rust_i0 = special_args.len(); let args_head = special_args.into_iter().collect::<TokenStream2>(); + let (arg_decls, args_tail) = codegen_args(core, f, rust_i0, 0); let ret = codegen_sync_ret(core, &f.sig.output); let type_params = exclude_lifetime_params(&f.sig.generics.params); @@ -424,124 +300,6 @@ fn codegen_v8_sync( } } -struct FastApiSyn { - args: TokenStream2, - ret: TokenStream2, - use_recv: bool, -} - -fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> { - // TODO(@littledivy): Support generics - if !f.sig.generics.params.is_empty() { - return None; - } - - let inputs = &f.sig.inputs; - let ret = match &f.sig.output { - syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void), - syn::ReturnType::Type(_, ty) => match is_fast_scalar(core, ty, true) { - Some(ret) => ret, - None => return None, - }, - }; - - let mut use_recv = false; - let mut args = vec![quote! { #core::v8::fast_api::Type::V8Value }]; - for (pos, input) in inputs.iter().enumerate() { - if pos == 0 && is_mut_ref_opstate(input) { - use_recv = true; - continue; - } - - let ty = match input { - syn::FnArg::Typed(pat) => &pat.ty, - _ => unreachable!(), - }; - - match is_fast_scalar(core, ty, false) { - None => match is_fast_arg_sequence(core, ty) { - Some(arg) => { - args.push(arg); - } - // early return, this function cannot be a fast call. - None => return None, - }, - Some(arg) => { - args.push(arg); - } - } - } - - let args = args - .iter() - .map(|arg| format!("{}", arg)) - .collect::<Vec<_>>() - .join(", "); - Some(FastApiSyn { - args: args.parse().unwrap(), - ret, - use_recv, - }) -} - -// A v8::Local<v8::Array> or FastApiTypedArray<T> -fn is_fast_arg_sequence( - core: &TokenStream2, - ty: impl ToTokens, -) -> Option<TokenStream2> { - // TODO(@littledivy): Make `v8::` parts optional. - if is_fast_typed_array(&ty) { - return Some( - quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint32) }, - ); - } - if is_local_array(&ty) { - return Some( - quote! { #core::v8::fast_api::Type::Sequence(#core::v8::fast_api::CType::Void) }, - ); - } - None -} - -fn is_local_array(arg: impl ToTokens) -> bool { - static RE: Lazy<Regex> = - Lazy::new(|| Regex::new(r"^v8::Local<v8::Array>$").unwrap()); - RE.is_match(&tokens(arg)) -} - -fn is_fast_typed_array(arg: impl ToTokens) -> bool { - static RE: Lazy<Regex> = Lazy::new(|| { - Regex::new(r#": (?:deno_core :: )?FastApiTypedArray$"#).unwrap() - }); - RE.is_match(&tokens(arg)) -} - -fn is_fast_scalar( - core: &TokenStream2, - ty: impl ToTokens, - is_ret: bool, -) -> Option<TokenStream2> { - let cty = if is_ret { - quote! { CType } - } else { - quote! { Type } - }; - if is_resource_id(&ty) { - return Some(quote! { #core::v8::fast_api::#cty::Uint32 }); - } - if is_void(&ty) { - return Some(quote! { #core::v8::fast_api::#cty::Void }); - } - // TODO(@littledivy): Support u8, i8, u16, i16 by casting. - match tokens(&ty).as_str() { - "u32" => Some(quote! { #core::v8::fast_api::#cty::Uint32 }), - "i32" => Some(quote! { #core::v8::fast_api::#cty::Int32 }), - "f32" => Some(quote! { #core::v8::fast_api::#cty::Float32 }), - "f64" => Some(quote! { #core::v8::fast_api::#cty::Float64 }), - _ => None, - } -} - fn codegen_args( core: &TokenStream2, f: &syn::ItemFn, @@ -685,7 +443,7 @@ fn is_resource_id(arg: impl ToTokens) -> bool { RE.is_match(&tokens(arg)) } -fn is_mut_ref_opstate(arg: impl ToTokens) -> bool { +fn is_mut_ref_opstate(arg: &syn::FnArg) -> bool { static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#": & mut (?:deno_core :: )?OpState$"#).unwrap()); RE.is_match(&tokens(arg)) diff --git a/ops/tests/compile_fail/unsupported.rs b/ops/tests/compile_fail/unsupported.rs deleted file mode 100644 index 1c4d6407a..000000000 --- a/ops/tests/compile_fail/unsupported.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2019-2020 the Deno authors. All rights reserved. MIT license. - -use deno_ops::op; - -#[op(fast)] -fn op_result_return(a: i32, b: i32) -> Result<(), ()> { - a + b -} - -#[op(fast)] -fn op_u8_arg(a: u8, b: u8) { - // -} - -#[op(fast)] -fn op_u16_arg(a: u16, b: u16) { - // -} - -#[op(fast)] -async fn op_async_fn(a: i32, b: i32) -> i32 { - a + b -} - -fn main() { - // pass -} diff --git a/ops/tests/compile_fail/unsupported.stderr b/ops/tests/compile_fail/unsupported.stderr deleted file mode 100644 index 68c9f7f16..000000000 --- a/ops/tests/compile_fail/unsupported.stderr +++ /dev/null @@ -1,31 +0,0 @@ -error: custom attribute panicked - --> tests/compile_fail/unsupported.rs:5:1 - | -5 | #[op(fast)] - | ^^^^^^^^^^^ - | - = help: message: op cannot be a fast api. enforced by #[op(fast)] - -error: custom attribute panicked - --> tests/compile_fail/unsupported.rs:10:1 - | -10 | #[op(fast)] - | ^^^^^^^^^^^ - | - = help: message: op cannot be a fast api. enforced by #[op(fast)] - -error: custom attribute panicked - --> tests/compile_fail/unsupported.rs:15:1 - | -15 | #[op(fast)] - | ^^^^^^^^^^^ - | - = help: message: op cannot be a fast api. enforced by #[op(fast)] - -error: custom attribute panicked - --> tests/compile_fail/unsupported.rs:20:1 - | -20 | #[op(fast)] - | ^^^^^^^^^^^ - | - = help: message: async op cannot be a fast api. enforced by #[op(fast)] diff --git a/ops/tests/mod.rs b/ops/tests/mod.rs deleted file mode 100644 index 522647f51..000000000 --- a/ops/tests/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn op_macro() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/compile_fail/*.rs"); -} |