summaryrefslogtreecommitdiff
path: root/ops
diff options
context:
space:
mode:
Diffstat (limited to 'ops')
-rw-r--r--ops/Cargo.toml6
-rw-r--r--ops/lib.rs22
-rw-r--r--ops/op2/dispatch_fast.rs193
-rw-r--r--ops/op2/dispatch_slow.rs220
-rw-r--r--ops/op2/generator_state.rs32
-rw-r--r--ops/op2/mod.rs287
-rw-r--r--ops/op2/signature.rs516
-rw-r--r--ops/op2/test_cases/sync/add.out54
-rw-r--r--ops/op2/test_cases/sync/add.rs6
-rw-r--r--ops/op2/test_cases/sync/add_options.out44
-rw-r--r--ops/op2/test_cases/sync/add_options.rs6
-rw-r--r--ops/op2/test_cases/sync/doc_comment.out35
-rw-r--r--ops/op2/test_cases/sync/doc_comment.rs5
-rw-r--r--ops/op2/test_cases/sync/smi.out52
-rw-r--r--ops/op2/test_cases/sync/smi.rs4
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 {}