diff --git a/Cargo.lock b/Cargo.lock index dad9744508f9ba..c8ddbfcbd2a9c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1149,12 +1149,14 @@ dependencies = [ name = "deno_ops" version = "0.25.0" dependencies = [ + "deno_core", "once_cell", "proc-macro-crate", "proc-macro2 1.0.39", "quote 1.0.18", "regex", "syn 1.0.96", + "trybuild", ] [[package]] @@ -1930,6 +1932,12 @@ dependencies = [ "polyval", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "glow" version = "0.11.2" @@ -4990,6 +4998,21 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "trybuild" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764b9e244b482a9b81bde596aa37aa6f1347bf8007adab25e59f901b32b4e0a0" +dependencies = [ + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] + [[package]] name = "tungstenite" version = "0.16.0" diff --git a/cli/bench/deno_common.js b/cli/bench/deno_common.js index 074346d283b039..d9879194f94f60 100644 --- a/cli/bench/deno_common.js +++ b/cli/bench/deno_common.js @@ -5,12 +5,31 @@ Deno.bench("date_now", { n: 5e5 }, () => { Date.now(); }); +// Fast API calls +{ + // deno-lint-ignore camelcase + const { op_add } = Deno.core.ops; + // deno-lint-ignore no-inner-declarations + function add(a, b) { + return op_add.fast(a, b); + } + // deno-lint-ignore no-inner-declarations + function addJS(a, b) { + return a + b; + } + Deno.bench("op_add", () => add(1, 2)); + Deno.bench("add_js", () => addJS(1, 2)); +} + +// deno-lint-ignore camelcase +const { op_void_sync } = Deno.core.ops; +function sync() { + return op_void_sync.fast(); +} +sync(); // Warmup + // Void ops measure op-overhead -Deno.bench( - "op_void_sync", - { n: 1e7 }, - () => Deno.core.ops.op_void_sync(), -); +Deno.bench("op_void_sync", () => sync()); Deno.bench( "op_void_async", diff --git a/core/bindings.rs b/core/bindings.rs index f3c16acbff6ecc..f75f6c7f327b71 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -9,17 +9,39 @@ use crate::modules::ModuleMap; use crate::ops::OpCtx; use crate::JsRuntime; use log::debug; -use once_cell::sync::Lazy; use std::option::Option; use std::os::raw::c_void; +use v8::fast_api::FastFunction; use v8::MapFnTo; -pub static EXTERNAL_REFERENCES: Lazy = - Lazy::new(|| { - v8::ExternalReferences::new(&[v8::ExternalReference { - function: call_console.map_fn_to(), - }]) - }); +pub fn external_references( + ops: &[OpCtx], + snapshot_loaded: bool, +) -> v8::ExternalReferences { + let mut references = vec![v8::ExternalReference { + function: call_console.map_fn_to(), + }]; + + for ctx in ops { + let ctx_ptr = ctx as *const OpCtx as _; + references.push(v8::ExternalReference { pointer: ctx_ptr }); + references.push(v8::ExternalReference { + function: ctx.decl.v8_fn_ptr, + }); + if snapshot_loaded { + if let Some(fast_fn) = &ctx.decl.fast_fn { + references.push(v8::ExternalReference { + pointer: fast_fn.function() as _, + }); + } + } + } + + let refs = v8::ExternalReferences::new(&references); + // Leak, V8 takes ownership of the references. + std::mem::forget(references); + refs +} // TODO(nayeemrmn): Move to runtime and/or make `pub(crate)`. pub fn script_origin<'a>( @@ -82,7 +104,8 @@ pub fn initialize_context<'s>( // Grab the Deno.core.ops object & init it let ops_obj = JsRuntime::grab_global::(scope, "Deno.core.ops") .expect("Deno.core.ops to exist"); - initialize_ops(scope, ops_obj, op_ctxs); + initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded); + return scope.escape(context); } @@ -94,7 +117,8 @@ pub fn initialize_context<'s>( // Bind functions to Deno.core.ops.* let ops_obj = JsRuntime::ensure_objs(scope, global, "Deno.core.ops").unwrap(); - initialize_ops(scope, ops_obj, op_ctxs); + + initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded); scope.escape(context) } @@ -102,10 +126,46 @@ fn initialize_ops( scope: &mut v8::HandleScope, ops_obj: v8::Local, op_ctxs: &[OpCtx], + snapshot_loaded: bool, ) { for ctx in op_ctxs { let ctx_ptr = ctx as *const OpCtx as *const c_void; - set_func_raw(scope, ops_obj, ctx.decl.name, ctx.decl.v8_fn_ptr, ctx_ptr); + + // If this is a fast op, we don't want it to be in the snapshot. + // Only initialize once snapshot is loaded. + if ctx.decl.fast_fn.is_some() && snapshot_loaded { + let object_template = v8::ObjectTemplate::new(scope); + assert!(object_template.set_internal_field_count( + (crate::runtime::V8_WRAPPER_OBJECT_INDEX + 1) as usize + )); + + let method_obj = object_template.new_instance(scope).unwrap(); + method_obj.set_aligned_pointer_in_internal_field( + crate::runtime::V8_WRAPPER_OBJECT_INDEX, + ctx_ptr, + ); + set_func_raw( + scope, + method_obj, + "fast", + ctx.decl.v8_fn_ptr, + ctx_ptr, + &ctx.decl.fast_fn, + snapshot_loaded, + ); + let method_key = v8::String::new(scope, ctx.decl.name).unwrap(); + ops_obj.set(scope, method_key.into(), method_obj.into()); + } else { + set_func_raw( + scope, + ops_obj, + ctx.decl.name, + ctx.decl.v8_fn_ptr, + ctx_ptr, + &None, + snapshot_loaded, + ); + } } } @@ -129,13 +189,25 @@ pub fn set_func_raw( name: &'static str, callback: v8::FunctionCallback, external_data: *const c_void, + fast_function: &Option>, + snapshot_loaded: bool, ) { let key = v8::String::new(scope, name).unwrap(); let external = v8::External::new(scope, external_data as *mut c_void); - let val = v8::Function::builder_raw(callback) - .data(external.into()) - .build(scope) - .unwrap(); + let builder = + v8::FunctionTemplate::builder_raw(callback).data(external.into()); + let templ = if let Some(fast_function) = fast_function { + // Don't initialize fast ops when snapshotting, the external references count mismatch. + if !snapshot_loaded { + builder.build(scope) + } else { + // TODO(@littledivy): Support fast api overloads in ops. + builder.build_fast(scope, &**fast_function, None) + } + } else { + builder.build(scope) + }; + let val = templ.get_function(scope).unwrap(); val.set_name(key); obj.set(scope, key.into(), val.into()); } diff --git a/core/extensions.rs b/core/extensions.rs index ce69578752d91a..846770d1fb5a51 100644 --- a/core/extensions.rs +++ b/core/extensions.rs @@ -2,6 +2,7 @@ use crate::OpState; use anyhow::Error; use std::{cell::RefCell, rc::Rc, task::Context}; +use v8::fast_api::FastFunction; pub type SourcePair = (&'static str, &'static str); pub type OpFnRef = v8::FunctionCallback; @@ -9,14 +10,14 @@ pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl; pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), Error>; pub type OpEventLoopFn = dyn Fn(Rc>, &mut Context) -> bool; -#[derive(Clone, Copy)] pub struct OpDecl { pub name: &'static str, pub v8_fn_ptr: OpFnRef, pub enabled: bool, - pub is_async: bool, // TODO(@AaronO): enum sync/async/fast ? + pub is_async: bool, pub is_unstable: bool, pub is_v8: bool, + pub fast_fn: Option>, } impl OpDecl { diff --git a/core/lib.rs b/core/lib.rs index 57e81ee7a9494a..8043b5e95b81ad 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -118,6 +118,8 @@ pub mod _ops { pub use super::ops::to_op_result; pub use super::ops::OpCtx; pub use super::runtime::queue_async_op; + pub use super::runtime::V8_WRAPPER_OBJECT_INDEX; + pub use super::runtime::V8_WRAPPER_TYPE_INDEX; } /// A helper macro that will return a call site in Rust code. Should be diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs index a42c0bae88e158..2e911e41500e17 100644 --- a/core/ops_builtin.rs +++ b/core/ops_builtin.rs @@ -31,6 +31,7 @@ pub(crate) fn init_builtins() -> Extension { op_wasm_streaming_set_url::decl(), op_void_sync::decl(), op_void_async::decl(), + op_add::decl(), // // TODO(@AaronO): track IO metrics for builtin streams op_read::decl(), op_write::decl(), @@ -54,7 +55,12 @@ pub fn op_resources(state: &mut OpState) -> Vec<(ResourceId, String)> { .collect() } -#[op] +#[op(fast)] +fn op_add(a: i32, b: i32) -> i32 { + a + b +} + +#[op(fast)] pub fn op_void_sync() {} #[op] diff --git a/core/runtime.rs b/core/runtime.rs index 68c3ab0023f8fb..06c777687a15da 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -328,7 +328,6 @@ impl JsRuntime { if let Some(get_error_class_fn) = options.get_error_class_fn { op_state.get_error_class_fn = get_error_class_fn; } - let op_state = Rc::new(RefCell::new(op_state)); let op_ctxs = ops .into_iter() @@ -341,12 +340,14 @@ impl JsRuntime { .collect::>() .into_boxed_slice(); + let refs = bindings::external_references(&op_ctxs, !options.will_snapshot); + // V8 takes ownership of external_references. + let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs)); let global_context; let (mut isolate, maybe_snapshot_creator) = if options.will_snapshot { // TODO(ry) Support loading snapshots before snapshotting. assert!(options.startup_snapshot.is_none()); - let mut creator = - v8::SnapshotCreator::new(Some(&bindings::EXTERNAL_REFERENCES)); + let mut creator = v8::SnapshotCreator::new(Some(refs)); // SAFETY: `get_owned_isolate` is unsafe because it may only be called // once. This is the only place we call this function, so this call is // safe. @@ -369,7 +370,7 @@ impl JsRuntime { V8_WRAPPER_OBJECT_INDEX, ) }) - .external_references(&**bindings::EXTERNAL_REFERENCES); + .external_references(&**refs); let snapshot_loaded = if let Some(snapshot) = options.startup_snapshot { params = match snapshot { Snapshot::Static(data) => params.snapshot_blob(data), diff --git a/ext/flash/lib.rs b/ext/flash/lib.rs index abd8502d092af8..92350db585e218 100644 --- a/ext/flash/lib.rs +++ b/ext/flash/lib.rs @@ -1344,8 +1344,8 @@ async fn op_flash_next_async( // the ContextScope creation is optimized away and the op is as simple as: // f(info: *const v8::FunctionCallbackInfo) { let rv = ...; rv.set_uint32(op_flash_next()); } #[op] -fn op_flash_next(op_state: &mut OpState) -> u32 { - let flash_ctx = op_state.borrow_mut::(); +fn op_flash_next(state: &mut OpState) -> u32 { + let flash_ctx = state.borrow_mut::(); let ctx = flash_ctx.servers.get_mut(&0).unwrap(); next_request_sync(ctx) } @@ -1353,8 +1353,8 @@ fn op_flash_next(op_state: &mut OpState) -> u32 { // Syncrhonous version of op_flash_next_async. Under heavy load, // this can collect buffered requests from rx channel and return tokens in a single batch. #[op] -fn op_flash_next_server(op_state: &mut OpState, server_id: u32) -> u32 { - let flash_ctx = op_state.borrow_mut::(); +fn op_flash_next_server(state: &mut OpState, server_id: u32) -> u32 { + let flash_ctx = state.borrow_mut::(); let ctx = flash_ctx.servers.get_mut(&server_id).unwrap(); next_request_sync(ctx) } diff --git a/ext/net/lib.rs b/ext/net/lib.rs index c95348020b9e57..2491700607847c 100644 --- a/ext/net/lib.rs +++ b/ext/net/lib.rs @@ -81,6 +81,8 @@ pub fn init( unstable: bool, unsafely_ignore_certificate_errors: Option>, ) -> Extension { + let mut ops = ops::init::

(); + ops.extend(ops_tls::init::

()); Extension::builder() .js(include_js_files!( prefix "deno:ext/net", @@ -88,7 +90,7 @@ pub fn init( "02_tls.js", "04_net_unstable.js", )) - .ops([&ops::init::

()[..], &ops_tls::init::

()[..]].concat()) + .ops(ops) .state(move |state| { state.put(DefaultTlsOptions { root_cert_store: root_cert_store.clone(), diff --git a/ops/Cargo.toml b/ops/Cargo.toml index 9fafba99730283..d40356c29d52b7 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -17,3 +17,7 @@ proc-macro2 = "1" quote = "1" regex = "1.6.0" 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 02ee266f7109de..b9114b5546fc35 100644 --- a/ops/README.md +++ b/ops/README.md @@ -4,9 +4,9 @@ ```rust // Declare an op. -#[op] -pub fn op_add(_: &mut OpState, a: i32, b: i32) -> Result { - Ok(a + b) +#[op(fast)] +pub fn op_add(_: &mut OpState, a: i32, b: i32) -> i32 { + a + b } // Register with an extension. @@ -14,3 +14,31 @@ 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` 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 shoukd 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 42913160b42e80..2028fc875b774f 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -1,10 +1,13 @@ // 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; @@ -14,6 +17,9 @@ 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. @@ -44,6 +50,7 @@ fn core_import() -> TokenStream2 { struct MacroArgs { is_unstable: bool, is_v8: bool, + must_be_fast: bool, } impl syn::parse::Parse for MacroArgs { @@ -55,7 +62,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"].contains(var) { + if !["unstable", "v8", "fast"].contains(var) { return Err(syn::Error::new( input.span(), "Ops expect #[op] or #[op(unstable)]", @@ -65,6 +72,7 @@ impl syn::parse::Parse for MacroArgs { Ok(Self { is_unstable: vars.contains(&"unstable"), is_v8: vars.contains(&"v8"), + must_be_fast: vars.contains(&"fast"), }) } } @@ -72,7 +80,11 @@ 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 } = margs; + let MacroArgs { + is_unstable, + is_v8, + must_be_fast, + } = margs; let func = syn::parse::(item).expect("expected a function"); let name = &func.sig.ident; let mut generics = func.sig.generics.clone(); @@ -102,6 +114,8 @@ 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 @@ -129,6 +143,7 @@ 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, @@ -147,6 +162,8 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { #v8_body } } + + #fast_impl }.into() } @@ -265,6 +282,117 @@ fn opstate_arg(arg: &FnArg) -> Option { } } +fn codegen_fast_impl( + core: &TokenStream2, + f: &syn::ItemFn, + name: &syn::Ident, + is_async: bool, + must_be_fast: bool, +) -> (TokenStream2, TokenStream2) { + if !must_be_fast { + return (quote! {}, quote! { None }); + } + 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::>(); + 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::>(); + 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, @@ -282,7 +410,6 @@ fn codegen_v8_sync( .collect::>(); let rust_i0 = special_args.len(); let args_head = special_args.into_iter().collect::(); - 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); @@ -305,6 +432,124 @@ fn codegen_v8_sync( } } +struct FastApiSyn { + args: TokenStream2, + ret: TokenStream2, + use_recv: bool, +} + +fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option { + // 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::>() + .join(", "); + Some(FastApiSyn { + args: args.parse().unwrap(), + ret, + use_recv, + }) +} + +// A v8::Local or FastApiTypedArray +fn is_fast_arg_sequence( + core: &TokenStream2, + ty: impl ToTokens, +) -> Option { + // 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 = + Lazy::new(|| Regex::new(r"^v8::Local$").unwrap()); + RE.is_match(&tokens(arg)) +} + +fn is_fast_typed_array(arg: impl ToTokens) -> bool { + static RE: Lazy = 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 { + 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, @@ -448,7 +693,7 @@ fn is_resource_id(arg: impl ToTokens) -> bool { RE.is_match(&tokens(arg)) } -fn is_mut_ref_opstate(arg: &syn::FnArg) -> bool { +fn is_mut_ref_opstate(arg: impl ToTokens) -> bool { static RE: Lazy = 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 new file mode 100644 index 00000000000000..1c4d6407ad315e --- /dev/null +++ b/ops/tests/compile_fail/unsupported.rs @@ -0,0 +1,27 @@ +// 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 new file mode 100644 index 00000000000000..68c9f7f162903f --- /dev/null +++ b/ops/tests/compile_fail/unsupported.stderr @@ -0,0 +1,31 @@ +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 new file mode 100644 index 00000000000000..522647f51eb1a8 --- /dev/null +++ b/ops/tests/mod.rs @@ -0,0 +1,5 @@ +#[test] +fn op_macro() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile_fail/*.rs"); +}