diff options
Diffstat (limited to 'ops')
-rw-r--r-- | ops/Cargo.toml | 6 | ||||
-rw-r--r-- | ops/lib.rs | 22 | ||||
-rw-r--r-- | ops/op2/dispatch_fast.rs | 193 | ||||
-rw-r--r-- | ops/op2/dispatch_slow.rs | 220 | ||||
-rw-r--r-- | ops/op2/generator_state.rs | 32 | ||||
-rw-r--r-- | ops/op2/mod.rs | 287 | ||||
-rw-r--r-- | ops/op2/signature.rs | 516 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/add.out | 54 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/add.rs | 6 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/add_options.out | 44 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/add_options.rs | 6 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/doc_comment.out | 35 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/doc_comment.rs | 5 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/smi.out | 52 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/smi.rs | 4 |
15 files changed, 1482 insertions, 0 deletions
diff --git a/ops/Cargo.toml b/ops/Cargo.toml index f142e4449..f9951f4e6 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -15,6 +15,7 @@ path = "./lib.rs" proc-macro = true [dependencies] +deno-proc-macro-rules.workspace = true lazy-regex.workspace = true once_cell.workspace = true pmutil = "0.5.3" @@ -22,7 +23,12 @@ proc-macro-crate = "1.1.3" proc-macro2.workspace = true quote.workspace = true regex.workspace = true +strum.workspace = true +strum_macros.workspace = true syn.workspace = true +syn2.workspace = true +thiserror.workspace = true +v8.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/ops/lib.rs b/ops/lib.rs index d7c8b0640..bd8ff9caf 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -8,6 +8,7 @@ use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use quote::ToTokens; +use std::error::Error; use syn::parse; use syn::parse_macro_input; use syn::punctuated::Punctuated; @@ -22,6 +23,7 @@ use syn::LifetimeDef; mod attrs; mod deno; mod fast_call; +mod op2; mod optimizer; const SCOPE_LIFETIME: &str = "'scope"; @@ -235,6 +237,26 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { op.gen().into() } +#[proc_macro_attribute] +pub fn op2(attr: TokenStream, item: TokenStream) -> TokenStream { + match crate::op2::op2(attr.into(), item.into()) { + Ok(output) => output.into(), + Err(err) => { + let mut err: &dyn Error = &err; + let mut output = "Failed to parse #[op2]:\n".to_owned(); + loop { + output += &format!(" - {err}\n"); + if let Some(source) = err.source() { + err = source; + } else { + break; + } + } + panic!("{output}"); + } + } +} + /// Generate the body of a v8 func for an async op fn codegen_v8_async( core: &TokenStream2, diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs new file mode 100644 index 000000000..79b8d141b --- /dev/null +++ b/ops/op2/dispatch_fast.rs @@ -0,0 +1,193 @@ +// 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::V8MappingError; +use proc_macro2::TokenStream; +use quote::format_ident; +use quote::quote; + +#[allow(unused)] +#[derive(Debug, Default, PartialEq, Clone)] +pub(crate) enum FastValue { + #[default] + Void, + Bool, + U32, + I32, + U64, + I64, + F32, + F64, + Pointer, + V8Value, + Uint8Array, + Uint32Array, + Float64Array, + SeqOneByteString, +} + +impl FastValue { + /// Quote fast value type. + fn quote_rust_type(&self) -> 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!(), + } + } + + /// 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), + } + } + + /// 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), + } + } +} + +pub fn generate_dispatch_fast( + generator_state: &mut GeneratorState, + signature: &ParsedSignature, +) -> Result<Option<(TokenStream, TokenStream)>, V8MappingError> { + 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())) + } + }; + inputs.push(fv); + } + + 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 GeneratorState { + fast_function, + deno_core, + .. + } = &generator_state; + + let input_types = inputs.iter().map(|fv| fv.quote_type()); + 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 ),* ], + #output_type, + Self::#fast_function as *const ::std::ffi::c_void + ) + }; + + let output_type = output.quote_rust_type(); + let names = &inputs + .iter() + .enumerate() + .map(|(i, _)| format_ident!("arg{i}")) + .collect::<Vec<_>>(); + let types = inputs.iter().map(|rv| rv.quote_rust_type()); + + 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),*) + } + ); + + Ok(Some((fast_definition, fast_fn))) +} diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs new file mode 100644 index 000000000..dd47b2017 --- /dev/null +++ b/ops/op2/dispatch_slow.rs @@ -0,0 +1,220 @@ +// 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 fn generate_dispatch_slow( + generator_state: &mut GeneratorState, + signature: &ParsedSignature, +) -> Result<TokenStream, V8MappingError> { + let mut output = TokenStream::new(); + 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 GeneratorState { + deno_core, + scope, + fn_args, + retval, + info, + slow_function, + .. + } = &generator_state; + + let with_scope = if generator_state.needs_scope { + quote!(let #scope = &mut unsafe { #deno_core::v8::CallbackScope::new(&*#info) };) + } else { + quote!() + }; + + let with_retval = if generator_state.needs_retval { + quote!(let mut #retval = #deno_core::v8::ReturnValue::from_function_callback_info(unsafe { &*#info });) + } else { + quote!() + }; + + let with_args = if generator_state.needs_args { + quote!(let #fn_args = #deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe { &*#info });) + } else { + quote!() + }; + + Ok(quote! { + pub extern "C" fn #slow_function(#info: *const #deno_core::v8::FunctionCallbackInfo) { + #with_scope + #with_retval + #with_args + + #output + }}) +} + +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::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) +} + +pub fn return_value_result( + generator_state: &mut GeneratorState, + ret_type: &Arg, +) -> Result<TokenStream, V8MappingError> { + let infallible = return_value_infallible(generator_state, ret_type)?; + let GeneratorState { result, .. } = &generator_state; + + let tokens = quote!( + let result = match ret_type { + Ok(#result) => { + #infallible, + } + Err(err) => { + return; + } + } + ); + Ok(tokens) +} diff --git a/ops/op2/generator_state.rs b/ops/op2/generator_state.rs new file mode 100644 index 000000000..741d4f7f3 --- /dev/null +++ b/ops/op2/generator_state.rs @@ -0,0 +1,32 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use proc_macro2::Ident; +use proc_macro2::TokenStream; + +pub struct GeneratorState { + /// The path to the `deno_core` crate (either `deno_core` or `crate`, the latter used if the op is `(core)`). + pub deno_core: TokenStream, + + /// Identifiers for each of the arguments of the original function + pub args: Vec<Ident>, + /// The new identifier for the original function's contents. + pub call: Ident, + /// The result of the `call` function + pub result: Ident, + + /// The `v8::CallbackScope` used if necessary for the function. + pub scope: Ident, + /// The `v8::FunctionCallbackInfo` used to pass args into the slow function. + pub info: Ident, + /// The `v8::FunctionCallbackArguments` used to pass args into the slow function. + pub fn_args: Ident, + /// The `v8::ReturnValue` used in the slow function + pub retval: Ident, + /// The "slow" function (ie: the one that isn't a fastcall) + pub slow_function: Ident, + /// The "fast" function (ie: a fastcall) + pub fast_function: Ident, + + pub needs_args: bool, + pub needs_retval: bool, + pub needs_scope: bool, +} diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs new file mode 100644 index 000000000..73a457f25 --- /dev/null +++ b/ops/op2/mod.rs @@ -0,0 +1,287 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_proc_macro_rules::rules; +use proc_macro2::Ident; +use proc_macro2::Span; +use proc_macro2::TokenStream; +use quote::format_ident; +use quote::quote; +use quote::ToTokens; +use std::iter::zip; +use syn2::parse2; +use syn2::FnArg; +use syn2::ItemFn; +use syn2::Path; +use thiserror::Error; + +use self::dispatch_fast::generate_dispatch_fast; +use self::dispatch_slow::generate_dispatch_slow; +use self::generator_state::GeneratorState; +use self::signature::parse_signature; +use self::signature::Arg; +use self::signature::SignatureError; + +pub mod dispatch_fast; +pub mod dispatch_slow; +pub mod generator_state; +pub mod signature; + +#[derive(Debug, Error)] +pub enum Op2Error { + #[error("Failed to match a pattern for '{0}': (input was '{1}')")] + PatternMatchFailed(&'static str, String), + #[error("Invalid attribute: '{0}'")] + InvalidAttribute(String), + #[error("Failed to parse syntax tree")] + ParseError(#[from] syn2::Error), + #[error("Failed to map a parsed signature to a V8 call")] + V8MappingError(#[from] V8MappingError), + #[error("Failed to parse signature")] + SignatureError(#[from] SignatureError), + #[error("This op is fast-compatible and should be marked as (fast)")] + ShouldBeFast, + #[error("This op is not fast-compatible and should not be marked as (fast)")] + ShouldNotBeFast, +} + +#[derive(Debug, Error)] +pub enum V8MappingError { + #[error("Unable to map {1:?} to {0}")] + NoMapping(&'static str, Arg), +} + +#[derive(Default)] +struct MacroConfig { + pub core: bool, + pub fast: bool, +} + +impl MacroConfig { + pub fn from_flags(flags: Vec<Ident>) -> Result<Self, Op2Error> { + let mut config: MacroConfig = Self::default(); + for flag in flags { + if flag == "core" { + config.core = true; + } else if flag == "fast" { + config.fast = true; + } else { + return Err(Op2Error::InvalidAttribute(flag.to_string())); + } + } + Ok(config) + } + + pub fn from_tokens(tokens: TokenStream) -> Result<Self, Op2Error> { + let attr_string = tokens.to_string(); + let config = std::panic::catch_unwind(|| { + rules!(tokens => { + () => { + Ok(MacroConfig::default()) + } + ($($flags:ident),+) => { + Self::from_flags(flags) + } + }) + }) + .map_err(|_| Op2Error::PatternMatchFailed("attribute", attr_string))??; + Ok(config) + } +} + +pub fn op2( + attr: TokenStream, + item: TokenStream, +) -> Result<TokenStream, Op2Error> { + let func = parse2::<ItemFn>(item)?; + let config = MacroConfig::from_tokens(attr)?; + generate_op2(config, func) +} + +fn generate_op2( + config: MacroConfig, + func: ItemFn, +) -> Result<TokenStream, Op2Error> { + // Create a copy of the original function, named "call" + let call = Ident::new("call", Span::call_site()); + let mut op_fn = func.clone(); + op_fn.attrs.clear(); + op_fn.sig.ident = call.clone(); + + // Clear inert attributes + // TODO(mmastrac): This should limit itself to clearing ours only + for arg in op_fn.sig.inputs.iter_mut() { + match arg { + FnArg::Receiver(slf) => slf.attrs.clear(), + FnArg::Typed(ty) => ty.attrs.clear(), + } + } + + let signature = parse_signature(func.attrs, func.sig.clone())?; + let processed_args = + zip(signature.args.iter(), &func.sig.inputs).collect::<Vec<_>>(); + + let mut args = vec![]; + let mut needs_args = false; + for (index, _) in processed_args.iter().enumerate() { + let input = format_ident!("arg{index}"); + args.push(input); + needs_args = true; + } + + let retval = Ident::new("rv", Span::call_site()); + let result = Ident::new("result", Span::call_site()); + let fn_args = Ident::new("args", Span::call_site()); + let scope = Ident::new("scope", Span::call_site()); + let info = Ident::new("info", Span::call_site()); + let slow_function = Ident::new("slow_function", Span::call_site()); + let fast_function = Ident::new("fast_function", Span::call_site()); + + let deno_core = if config.core { + syn2::parse_str::<Path>("crate::deno_core") + } else { + syn2::parse_str::<Path>("deno_core") + } + .expect("Parsing crate should not fail") + .into_token_stream(); + + let mut generator_state = GeneratorState { + args, + fn_args, + call, + scope, + info, + deno_core, + result, + retval, + needs_args, + slow_function, + fast_function, + needs_retval: false, + needs_scope: false, + }; + + let name = func.sig.ident; + let slow_fn = generate_dispatch_slow(&mut generator_state, &signature)?; + let (fast_definition, fast_fn) = + match generate_dispatch_fast(&mut generator_state, &signature)? { + Some((fast_definition, fast_fn)) => { + if !config.fast { + return Err(Op2Error::ShouldBeFast); + } + (quote!(Some({#fast_definition})), fast_fn) + } + None => { + if config.fast { + return Err(Op2Error::ShouldNotBeFast); + } + (quote!(None), quote!()) + } + }; + + let GeneratorState { + deno_core, + slow_function, + .. + } = &generator_state; + + let arg_count: usize = generator_state.args.len(); + let vis = func.vis; + + Ok(quote! { + #[allow(non_camel_case_types)] + #vis struct #name { + } + + impl #name { + pub const fn name() -> &'static str { + stringify!(#name) + } + + pub const fn decl() -> #deno_core::_ops::OpDecl { + #deno_core::_ops::OpDecl { + name: stringify!(#name), + v8_fn_ptr: Self::#slow_function as _, + enabled: true, + fast_fn: #fast_definition, + is_async: false, + is_unstable: false, + is_v8: false, + arg_count: #arg_count as u8, + } + } + + #slow_fn + #fast_fn + + #[inline(always)] + #op_fn + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + use syn2::parse_str; + use syn2::File; + use syn2::Item; + + #[testing_macros::fixture("op2/test_cases/**/*.rs")] + fn test_signature_parser(input: PathBuf) { + let update_expected = std::env::var("UPDATE_EXPECTED").is_ok(); + + let source = + std::fs::read_to_string(&input).expect("Failed to read test file"); + let file = parse_str::<File>(&source).expect("Failed to parse Rust file"); + let mut expected_out = vec![]; + for item in file.items { + if let Item::Fn(mut func) = item { + let mut config = None; + func.attrs.retain(|attr| { + let tokens = attr.into_token_stream(); + let attr_string = attr.clone().into_token_stream().to_string(); + println!("{}", attr_string); + use syn2 as syn; + if let Some(new_config) = rules!(tokens => { + (#[op2]) => { + Some(MacroConfig::default()) + } + (#[op2( $($x:ident),* )]) => { + Some(MacroConfig::from_flags(x).expect("Failed to parse attribute")) + } + (#[$_attr:meta]) => { + None + } + }) { + config = Some(new_config); + false + } else { + true + } + }); + let tokens = + generate_op2(config.unwrap(), func).expect("Failed to generate op"); + println!("======== Raw tokens ========:\n{}", tokens.clone()); + let tree = syn::parse2(tokens).unwrap(); + let actual = prettyplease::unparse(&tree); + println!("======== Generated ========:\n{}", actual); + expected_out.push(actual); + } + } + + let expected_out = expected_out.join("\n"); + + if update_expected { + std::fs::write(input.with_extension("out"), expected_out) + .expect("Failed to write expectation file"); + } else { + let expected = std::fs::read_to_string(input.with_extension("out")) + .expect("Failed to read expectation file"); + assert_eq!( + expected, expected_out, + "Failed to match expectation. Use UPDATE_EXPECTED=1." + ); + } + } +} diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs new file mode 100644 index 000000000..6158b2a55 --- /dev/null +++ b/ops/op2/signature.rs @@ -0,0 +1,516 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_proc_macro_rules::rules; +use proc_macro2::Ident; +use proc_macro2::Span; +use quote::quote; +use quote::ToTokens; +use strum::IntoEnumIterator; +use strum::IntoStaticStr; +use strum_macros::EnumIter; +use strum_macros::EnumString; +use syn2::Attribute; +use syn2::FnArg; +use syn2::Pat; +use syn2::ReturnType; +use syn2::Signature; +use syn2::Type; +use syn2::TypePath; +use thiserror::Error; + +#[allow(non_camel_case_types)] +#[derive( + Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString, EnumIter, +)] +pub enum NumericArg { + /// A placeholder argument for arguments annotated with #[smi]. + __SMI__, + /// A placeholder argument for void data. + __VOID__, + bool, + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, + f32, + f64, + isize, + usize, +} + +impl ToTokens for NumericArg { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ident = Ident::new(self.into(), Span::call_site()); + tokens.extend(quote! { #ident }) + } +} + +#[derive( + Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString, EnumIter, +)] +pub enum V8Arg { + External, + Object, + Array, + ArrayBuffer, + ArrayBufferView, + DataView, + TypedArray, + BigInt64Array, + BigUint64Array, + Float32Array, + Float64Array, + Int16Array, + Int32Array, + Int8Array, + Uint16Array, + Uint32Array, + Uint8Array, + Uint8ClampedArray, + BigIntObject, + BooleanObject, + Date, + Function, + Map, + NumberObject, + Promise, + PromiseResolver, + Proxy, + RegExp, + Set, + SharedArrayBuffer, + StringObject, + SymbolObject, + WasmMemoryObject, + WasmModuleObject, + Primitive, + BigInt, + Boolean, + Name, + String, + Symbol, + Number, + Integer, + Int32, + Uint32, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Special { + HandleScope, + OpState, + String, + RefStr, + FastApiCallbackOptions, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RefType { + Ref, + Mut, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Arg { + Void, + Special(Special), + Ref(RefType, Special), + RcRefCell(Special), + Option(Special), + OptionNumeric(NumericArg), + Slice(RefType, NumericArg), + Ptr(RefType, NumericArg), + V8Local(V8Arg), + Numeric(NumericArg), + SerdeV8(String), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RetVal { + Infallible(Arg), + Result(Arg), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParsedSignature { + pub args: Vec<Arg>, + pub names: Vec<String>, + pub ret_val: RetVal, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum AttributeModifier { + /// #[serde], for serde_v8 types. + Serde, + /// #[smi], for small integers + Smi, + /// #[string], for strings. + String, +} + +#[derive(Error, Debug)] +pub enum SignatureError { + #[error("Invalid argument: {0}")] + ArgError(String, #[source] ArgError), + #[error("Invalid return type")] + RetError(#[from] ArgError), +} + +#[derive(Error, Debug)] +pub enum ArgError { + #[error("Invalid self argument")] + InvalidSelf, + #[error("Invalid argument type: {0}")] + InvalidType(String), + #[error( + "Invalid argument type path (should this be #[smi] or #[serde]?): {0}" + )] + InvalidTypePath(String), + #[error("Too many attributes")] + TooManyAttributes, + #[error("Invalid #[serde] type: {0}")] + InvalidSerdeType(String), + #[error("Cannot use #[serde] for type: {0}")] + InvalidSerdeAttributeType(String), + #[error("Invalid v8 type: {0}")] + InvalidV8Type(String), + #[error("Internal error: {0}")] + InternalError(String), + #[error("Missing a #[string] attribute")] + MissingStringAttribute, +} + +#[derive(Copy, Clone, Default)] +struct Attributes { + primary: Option<AttributeModifier>, +} + +fn stringify_token(tokens: impl ToTokens) -> String { + tokens + .into_token_stream() + .into_iter() + .map(|s| s.to_string()) + .collect::<Vec<_>>() + .join("") +} + +pub fn parse_signature( + attributes: Vec<Attribute>, + signature: Signature, +) -> Result<ParsedSignature, SignatureError> { + let mut args = vec![]; + let mut names = vec![]; + for input in signature.inputs { + let name = match &input { + FnArg::Receiver(_) => "self".to_owned(), + FnArg::Typed(ty) => match &*ty.pat { + Pat::Ident(ident) => ident.ident.to_string(), + _ => "(complex)".to_owned(), + }, + }; + names.push(name.clone()); + args.push( + parse_arg(input).map_err(|err| SignatureError::ArgError(name, err))?, + ); + } + Ok(ParsedSignature { + args, + names, + ret_val: parse_return(parse_attributes(&attributes)?, &signature.output)?, + }) +} + +fn parse_attributes(attributes: &[Attribute]) -> Result<Attributes, ArgError> { + let attrs = attributes + .iter() + .filter_map(parse_attribute) + .collect::<Vec<_>>(); + + if attrs.is_empty() { + return Ok(Attributes::default()); + } + if attrs.len() > 1 { + return Err(ArgError::TooManyAttributes); + } + Ok(Attributes { + primary: Some(*attrs.get(0).unwrap()), + }) +} + +fn parse_attribute(attr: &Attribute) -> Option<AttributeModifier> { + let tokens = attr.into_token_stream(); + use syn2 as syn; + std::panic::catch_unwind(|| { + rules!(tokens => { + (#[serde]) => Some(AttributeModifier::Serde), + (#[smi]) => Some(AttributeModifier::Smi), + (#[string]) => Some(AttributeModifier::String), + (#[$_attr:meta]) => None, + }) + }) + .expect("Failed to parse an attribute") +} + +fn parse_return( + attrs: Attributes, + rt: &ReturnType, +) -> Result<RetVal, ArgError> { + match rt { + ReturnType::Default => Ok(RetVal::Infallible(Arg::Void)), + ReturnType::Type(_, ty) => { + let s = stringify_token(ty); + let tokens = ty.into_token_stream(); + use syn2 as syn; + + std::panic::catch_unwind(|| { + rules!(tokens => { + // x::y::Result<Value>, like io::Result and other specialty result types + ($($_package:ident ::)* Result < $ty:ty >) => { + Ok(RetVal::Result(parse_type(attrs, &ty)?)) + } + // x::y::Result<Value, Error> + ($($_package:ident ::)* Result < $ty:ty, $_error:ty >) => { + Ok(RetVal::Result(parse_type(attrs, &ty)?)) + } + ($ty:ty) => { + Ok(RetVal::Infallible(parse_type(attrs, &ty)?)) + } + }) + }) + .map_err(|e| { + ArgError::InternalError(format!( + "parse_return({}) {}", + s, + e.downcast::<&str>().unwrap_or_default() + )) + })? + } + } +} + +fn parse_type_path(attrs: Attributes, tp: &TypePath) -> Result<Arg, ArgError> { + if tp.path.segments.len() == 1 { + let segment = tp.path.segments.first().unwrap().ident.to_string(); + for numeric in NumericArg::iter() { + if Into::<&'static str>::into(numeric) == segment.as_str() { + return Ok(Arg::Numeric(numeric)); + } + } + } + + use syn2 as syn; + + let tokens = tp.clone().into_token_stream(); + std::panic::catch_unwind(|| { + rules!(tokens => { + ( $( std :: str :: )? String ) => { + if attrs.primary == Some(AttributeModifier::String) { + Ok(Arg::Special(Special::String)) + } else { + Err(ArgError::MissingStringAttribute) + } + } + ( $( std :: ffi :: )? c_void ) => Ok(Arg::Numeric(NumericArg::__VOID__)), + ( OpState ) => Ok(Arg::Special(Special::OpState)), + ( v8 :: HandleScope ) => Ok(Arg::Special(Special::HandleScope)), + ( v8 :: FastApiCallbackOptions ) => Ok(Arg::Special(Special::FastApiCallbackOptions)), + ( v8 :: Local < $( $_scope:lifetime , )? v8 :: $v8:ident >) => Ok(Arg::V8Local(parse_v8_type(&v8)?)), + ( Rc < RefCell < $ty:ty > > ) => Ok(Arg::RcRefCell(parse_type_special(attrs, &ty)?)), + ( Option < $ty:ty > ) => { + match parse_type(attrs, &ty)? { + Arg::Special(special) => Ok(Arg::Option(special)), + Arg::Numeric(numeric) => Ok(Arg::OptionNumeric(numeric)), + _ => Err(ArgError::InvalidType(stringify_token(ty))) + } + } + ( $any:ty ) => Err(ArgError::InvalidTypePath(stringify_token(any))), + }) + }).map_err(|e| ArgError::InternalError(format!("parse_type_path {e:?}")))? +} + +fn parse_v8_type(v8: &Ident) -> Result<V8Arg, ArgError> { + let v8 = v8.to_string(); + V8Arg::try_from(v8.as_str()).map_err(|_| ArgError::InvalidV8Type(v8)) +} + +fn parse_type_special( + attrs: Attributes, + ty: &Type, +) -> Result<Special, ArgError> { + match parse_type(attrs, ty)? { + Arg::Special(special) => Ok(special), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } +} + +fn parse_type(attrs: Attributes, ty: &Type) -> Result<Arg, ArgError> { + if let Some(primary) = attrs.primary { + match primary { + AttributeModifier::Serde => match ty { + Type::Path(of) => { + // If this type will parse without #[serde], it is illegal to use this type with #[serde] + if parse_type_path(Attributes::default(), of).is_ok() { + return Err(ArgError::InvalidSerdeAttributeType(stringify_token( + ty, + ))); + } + return Ok(Arg::SerdeV8(stringify_token(of.path.clone()))); + } + _ => return Err(ArgError::InvalidSerdeType(stringify_token(ty))), + }, + AttributeModifier::String => match ty { + Type::Path(of) => { + return parse_type_path(attrs, of); + } + Type::Reference(of) => { + let mut_type = if of.mutability.is_some() { + RefType::Mut + } else { + RefType::Ref + }; + let tokens = of.elem.clone().into_token_stream(); + use syn2 as syn; + return rules!(tokens => { + (str) => Ok(Arg::Special(Special::RefStr)), + ($_ty:ty) => Ok(Arg::Ref(mut_type, parse_type_special(attrs, &of.elem)?)), + }); + } + _ => return Err(ArgError::InvalidSerdeType(stringify_token(ty))), + }, + AttributeModifier::Smi => { + return Ok(Arg::Numeric(NumericArg::__SMI__)); + } + } + }; + match ty { + Type::Tuple(of) => { + if of.elems.is_empty() { + Ok(Arg::Void) + } else { + Err(ArgError::InvalidType(stringify_token(ty))) + } + } + Type::Reference(of) => { + let mut_type = if of.mutability.is_some() { + RefType::Mut + } else { + RefType::Ref + }; + match &*of.elem { + Type::Slice(of) => match parse_type(attrs, &of.elem)? { + Arg::Numeric(numeric) => Ok(Arg::Slice(mut_type, numeric)), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + }, + Type::Path(of) => match parse_type_path(attrs, of)? { + Arg::Special(special) => Ok(Arg::Ref(mut_type, special)), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + }, + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } + } + Type::Ptr(of) => { + let mut_type = if of.mutability.is_some() { + RefType::Mut + } else { + RefType::Ref + }; + match &*of.elem { + Type::Path(of) => match parse_type_path(attrs, of)? { + Arg::Numeric(numeric) => Ok(Arg::Ptr(mut_type, numeric)), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + }, + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } + } + Type::Path(of) => parse_type_path(attrs, of), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } +} + +fn parse_arg(arg: FnArg) -> Result<Arg, ArgError> { + let FnArg::Typed(typed) = arg else { + return Err(ArgError::InvalidSelf); + }; + parse_type(parse_attributes(&typed.attrs)?, &typed.ty) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::op2::signature::parse_signature; + use syn2::parse_str; + use syn2::ItemFn; + + // We can't test pattern args :/ + // https://github.com/rust-lang/rfcs/issues/2688 + macro_rules! test { + ( $(# [ $fn_attr:ident ])? fn $name:ident ( $( $(# [ $attr:ident ])? $ident:ident : $ty:ty ),* ) $(-> $(# [ $ret_attr:ident ])? $ret:ty)?, ( $( $arg_res:expr ),* ) -> $ret_res:expr ) => { + #[test] + fn $name() { + test( + stringify!($( #[$fn_attr] )? fn op( $( $( #[$attr] )? $ident : $ty ),* ) $(-> $( #[$ret_attr] )? $ret)? {}), + stringify!($($arg_res),*), + stringify!($ret_res) + ); + } + }; + } + + fn test(op: &str, args_expected: &str, return_expected: &str) { + let item_fn = parse_str::<ItemFn>(op) + .unwrap_or_else(|_| panic!("Failed to parse {op} as a ItemFn")); + let attrs = item_fn.attrs; + let sig = parse_signature(attrs, item_fn.sig).unwrap_or_else(|_| { + panic!("Failed to successfully parse signature from {op}") + }); + + assert_eq!( + args_expected, + format!("{:?}", sig.args).trim_matches(|c| c == '[' || c == ']') + ); + assert_eq!(return_expected, format!("{:?}", sig.ret_val)); + } + + test!( + fn op_state_and_number(opstate: &mut OpState, a: u32) -> (), + (Ref(Mut, OpState), Numeric(u32)) -> Infallible(Void) + ); + test!( + fn op_slices(r#in: &[u8], out: &mut [u8]), + (Slice(Ref, u8), Slice(Mut, u8)) -> Infallible(Void) + ); + test!( + #[serde] fn op_serde(#[serde] input: package::SerdeInputType) -> Result<package::SerdeReturnType, Error>, + (SerdeV8("package::SerdeInputType")) -> Result(SerdeV8("package::SerdeReturnType")) + ); + test!( + fn op_local(input: v8::Local<v8::String>) -> Result<v8::Local<v8::String>, Error>, + (V8Local(String)) -> Result(V8Local(String)) + ); + test!( + fn op_resource(#[smi] rid: ResourceId, buffer: &[u8]), + (Numeric(__SMI__), Slice(Ref, u8)) -> Infallible(Void) + ); + test!( + fn op_option_numeric_result(state: &mut OpState) -> Result<Option<u32>, AnyError>, + (Ref(Mut, OpState)) -> Result(OptionNumeric(u32)) + ); + test!( + fn op_ffi_read_f64(state: &mut OpState, ptr: * mut c_void, offset: isize) -> Result <f64, AnyError>, + (Ref(Mut, OpState), Ptr(Mut, __VOID__), Numeric(isize)) -> Result(Numeric(f64)) + ); + test!( + fn op_print(#[string] msg: &str, is_err: bool) -> Result<(), Error>, + (Special(RefStr), Numeric(bool)) -> Result(Void) + ); + + #[test] + fn test_parse_result() { + let rt = parse_str::<ReturnType>("-> Result < (), Error >") + .expect("Failed to parse"); + println!("{:?}", parse_return(Attributes::default(), &rt)); + } +} diff --git a/ops/op2/test_cases/sync/add.out b/ops/op2/test_cases/sync/add.out new file mode 100644 index 000000000..a7269c5cf --- /dev/null +++ b/ops/op2/test_cases/sync/add.out @@ -0,0 +1,54 @@ +#[allow(non_camel_case_types)] +struct op_add {} +impl op_add { + pub const fn name() -> &'static str { + stringify!(op_add) + } + pub const fn decl() -> deno_core::_ops::OpDecl { + deno_core::_ops::OpDecl { + name: stringify!(op_add), + v8_fn_ptr: Self::slow_function as _, + enabled: true, + fast_fn: Some({ + use deno_core::v8::fast_api::Type; + use deno_core::v8::fast_api::CType; + deno_core::v8::fast_api::FastFunction::new( + &[Type::Uint32, Type::Uint32], + CType::Uint32, + Self::fast_function as *const ::std::ffi::c_void, + ) + }), + is_async: false, + is_unstable: false, + is_v8: false, + arg_count: 2usize as u8, + } + } + pub extern "C" fn slow_function(info: *const deno_core::v8::FunctionCallbackInfo) { + let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe { + &*info + }); + let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe { + &*info + }); + let arg0 = args.get(0usize as i32); + let arg0 = deno_core::_ops::to_u32(&arg0) as _; + let arg1 = args.get(1usize as i32); + let arg1 = deno_core::_ops::to_u32(&arg1) as _; + let result = Self::call(arg0, arg1); + rv.set_uint32(result as u32); + } + fn fast_function( + _: deno_core::v8::Local<deno_core::v8::Object>, + arg0: u32, + arg1: u32, + ) -> u32 { + let arg0 = arg0 as _; + let arg1 = arg1 as _; + Self::call(arg0, arg1) + } + #[inline(always)] + fn call(a: u32, b: u32) -> u32 { + a + b + } +} diff --git a/ops/op2/test_cases/sync/add.rs b/ops/op2/test_cases/sync/add.rs new file mode 100644 index 000000000..74dbb1893 --- /dev/null +++ b/ops/op2/test_cases/sync/add.rs @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +#[op2(fast)] +fn op_add(a: u32, b: u32) -> u32 { + a + b +} diff --git a/ops/op2/test_cases/sync/add_options.out b/ops/op2/test_cases/sync/add_options.out new file mode 100644 index 000000000..682a77309 --- /dev/null +++ b/ops/op2/test_cases/sync/add_options.out @@ -0,0 +1,44 @@ +#[allow(non_camel_case_types)] +pub struct op_test_add_option {} +impl op_test_add_option { + pub const fn name() -> &'static str { + stringify!(op_test_add_option) + } + pub const fn decl() -> crate::deno_core::_ops::OpDecl { + crate::deno_core::_ops::OpDecl { + name: stringify!(op_test_add_option), + v8_fn_ptr: Self::slow_function as _, + enabled: true, + fast_fn: None, + is_async: false, + is_unstable: false, + is_v8: false, + arg_count: 2usize as u8, + } + } + pub extern "C" fn slow_function( + info: *const crate::deno_core::v8::FunctionCallbackInfo, + ) { + let mut rv = crate::deno_core::v8::ReturnValue::from_function_callback_info(unsafe { + &*info + }); + let args = crate::deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe { + &*info + }); + let arg0 = args.get(0usize as i32); + let arg0 = crate::deno_core::_ops::to_u32(&arg0) as _; + let arg1 = args.get(1usize as i32); + let arg1 = if arg1.is_null_or_undefined() { + None + } else { + let arg1 = crate::deno_core::_ops::to_u32(&arg1) as _; + Some(arg1) + }; + let result = Self::call(arg0, arg1); + rv.set_uint32(result as u32); + } + #[inline(always)] + pub fn call(a: u32, b: Option<u32>) -> u32 { + a + b.unwrap_or(100) + } +} diff --git a/ops/op2/test_cases/sync/add_options.rs b/ops/op2/test_cases/sync/add_options.rs new file mode 100644 index 000000000..a5f2c8f4a --- /dev/null +++ b/ops/op2/test_cases/sync/add_options.rs @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +#[op2(core)] +pub fn op_test_add_option(a: u32, b: Option<u32>) -> u32 { + a + b.unwrap_or(100) +} diff --git a/ops/op2/test_cases/sync/doc_comment.out b/ops/op2/test_cases/sync/doc_comment.out new file mode 100644 index 000000000..bd0d0b21f --- /dev/null +++ b/ops/op2/test_cases/sync/doc_comment.out @@ -0,0 +1,35 @@ +#[allow(non_camel_case_types)] +pub struct op_has_doc_comment {} +impl op_has_doc_comment { + pub const fn name() -> &'static str { + stringify!(op_has_doc_comment) + } + pub const fn decl() -> deno_core::_ops::OpDecl { + deno_core::_ops::OpDecl { + name: stringify!(op_has_doc_comment), + v8_fn_ptr: Self::slow_function as _, + enabled: true, + fast_fn: Some({ + use deno_core::v8::fast_api::Type; + use deno_core::v8::fast_api::CType; + deno_core::v8::fast_api::FastFunction::new( + &[], + CType::Void, + Self::fast_function as *const ::std::ffi::c_void, + ) + }), + is_async: false, + is_unstable: false, + is_v8: false, + arg_count: 0usize as u8, + } + } + pub extern "C" fn slow_function(info: *const deno_core::v8::FunctionCallbackInfo) { + let result = Self::call(); + } + fn fast_function(_: deno_core::v8::Local<deno_core::v8::Object>) -> () { + Self::call() + } + #[inline(always)] + pub fn call() -> () {} +} diff --git a/ops/op2/test_cases/sync/doc_comment.rs b/ops/op2/test_cases/sync/doc_comment.rs new file mode 100644 index 000000000..b729a64bd --- /dev/null +++ b/ops/op2/test_cases/sync/doc_comment.rs @@ -0,0 +1,5 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/// This is a doc comment. +#[op2(fast)] +pub fn op_has_doc_comment() -> () {} diff --git a/ops/op2/test_cases/sync/smi.out b/ops/op2/test_cases/sync/smi.out new file mode 100644 index 000000000..e6c1bc1e3 --- /dev/null +++ b/ops/op2/test_cases/sync/smi.out @@ -0,0 +1,52 @@ +#[allow(non_camel_case_types)] +struct op_add {} +impl op_add { + pub const fn name() -> &'static str { + stringify!(op_add) + } + pub const fn decl() -> deno_core::_ops::OpDecl { + deno_core::_ops::OpDecl { + name: stringify!(op_add), + v8_fn_ptr: Self::slow_function as _, + enabled: true, + fast_fn: Some({ + use deno_core::v8::fast_api::Type; + use deno_core::v8::fast_api::CType; + deno_core::v8::fast_api::FastFunction::new( + &[Type::Int32, Type::Uint32], + CType::Uint32, + Self::fast_function as *const ::std::ffi::c_void, + ) + }), + is_async: false, + is_unstable: false, + is_v8: false, + arg_count: 2usize as u8, + } + } + pub extern "C" fn slow_function(info: *const deno_core::v8::FunctionCallbackInfo) { + let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe { + &*info + }); + let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe { + &*info + }); + let arg0 = args.get(0usize as i32); + let arg0 = deno_core::_ops::to_i32(&arg0) as _; + let arg1 = args.get(1usize as i32); + let arg1 = deno_core::_ops::to_u32(&arg1) as _; + let result = Self::call(arg0, arg1); + rv.set_uint32(result as u32); + } + fn fast_function( + _: deno_core::v8::Local<deno_core::v8::Object>, + arg0: i32, + arg1: u32, + ) -> u32 { + let arg0 = arg0 as _; + let arg1 = arg1 as _; + Self::call(arg0, arg1) + } + #[inline(always)] + fn call(id: ResourceId, extra: u16) -> u32 {} +} diff --git a/ops/op2/test_cases/sync/smi.rs b/ops/op2/test_cases/sync/smi.rs new file mode 100644 index 000000000..a5a441845 --- /dev/null +++ b/ops/op2/test_cases/sync/smi.rs @@ -0,0 +1,4 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +#[op2(fast)] +fn op_add(#[smi] id: ResourceId, extra: u16) -> u32 {} |