Repository: denoland/deno_core
Pull request #28 (2023-07-10 16:02:25)

feat(ops): Implement #[opstate] for op2


Patch:
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 13ba9eb..dcf4457 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -350,12 +350,15 @@ mod tests {
   use crate::error::JsError;
   use crate::FastString;
   use crate::JsRuntime;
+  use crate::OpState;
   use crate::RuntimeOptions;
   use deno_ops::op2;
   use serde::Deserialize;
   use serde::Serialize;
   use std::borrow::Cow;
   use std::cell::Cell;
+  use std::cell::RefCell;
+  use std::rc::Rc;
 
   crate::extension!(
     testing,
@@ -386,7 +389,13 @@ mod tests {
       op_test_v8_type_handle_scope_obj,
       op_test_v8_type_handle_scope_result,
       op_test_serde_v8,
-    ]
+      op_state_rc,
+      op_state_ref,
+      op_state_mut,
+    ],
+    state = |state| {
+      state.put(1234u32)
+    }
   );
 
   thread_local! {
@@ -872,4 +881,35 @@ mod tests {
     )?;
     Ok(())
   }
+
+  #[op2(core, fast)]
+  pub fn op_state_rc(state: Rc<RefCell<OpState>>, value: u32) -> u32 {
+    let old_value: u32 = state.borrow_mut().take();
+    state.borrow_mut().put(value);
+    println!("{value} {old_value}");
+    old_value
+  }
+
+  #[op2(core, fast)]
+  pub fn op_state_ref(state: &OpState) -> u32 {
+    let old_value: &u32 = state.borrow();
+    *old_value
+  }
+
+  #[op2(core, fast)]
+  pub fn op_state_mut(state: &mut OpState, value: u32) {
+    *state.borrow_mut() = value;
+  }
+
+  #[tokio::test]
+  pub async fn test_op_state() -> Result<(), Box<dyn std::error::Error>> {
+    run_test2(
+      10000,
+      "op_state_rc",
+      "if (__index__ == 0) { op_state_rc(__index__) } else { assert(op_state_rc(__index__) == __index__ - 1) }",
+    )?;
+    run_test2(10000, "op_state_mut", "op_state_mut(__index__)")?;
+    run_test2(10000, "op_state_ref", "assert(op_state_ref() == 1234)")?;
+    Ok(())
+  }
 }
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index f3596ad..befe60c 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -5,6 +5,7 @@ use super::generator_state::GeneratorState;
 use super::signature::Arg;
 use super::signature::NumericArg;
 use super::signature::ParsedSignature;
+use super::signature::RefType;
 use super::signature::RetVal;
 use super::signature::Special;
 use super::V8MappingError;
@@ -33,6 +34,8 @@ pub(crate) enum V8FastCallType {
   Float64Array,
   SeqOneByteString,
   CallbackOptions,
+  /// Used for virtual arguments that do not contribute a raw argument
+  Virtual,
 }
 
 impl V8FastCallType {
@@ -60,6 +63,7 @@ impl V8FastCallType {
       V8FastCallType::Uint8Array
       | V8FastCallType::Uint32Array
       | V8FastCallType::Float64Array => unreachable!(),
+      V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
   }
 
@@ -81,6 +85,7 @@ impl V8FastCallType {
       V8FastCallType::Uint32Array => unreachable!(),
       V8FastCallType::Float64Array => unreachable!(),
       V8FastCallType::SeqOneByteString => quote!(CType::SeqOneByteString),
+      V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
   }
 
@@ -102,6 +107,7 @@ impl V8FastCallType {
       V8FastCallType::Uint32Array => quote!(Type::TypedArray(CType::Uint32)),
       V8FastCallType::Float64Array => quote!(Type::TypedArray(CType::Float64)),
       V8FastCallType::SeqOneByteString => quote!(Type::SeqOneByteString),
+      V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
   }
 }
@@ -110,18 +116,22 @@ pub fn generate_dispatch_fast(
   generator_state: &mut GeneratorState,
   signature: &ParsedSignature,
 ) -> Result<Option<(TokenStream, TokenStream)>, V8MappingError> {
+  enum Input<'a> {
+    Concrete(V8FastCallType),
+    Virtual(&'a Arg),
+  }
+
   let mut inputs = vec![];
   for arg in &signature.args {
     let Some(fv) = map_arg_to_v8_fastcall_type(arg)? else {
       return Ok(None);
     };
-    inputs.push(fv);
+    if fv == V8FastCallType::Virtual {
+      inputs.push(Input::Virtual(arg));
+    } else {
+      inputs.push(Input::Concrete(fv));
+    }
   }
-  let mut names = inputs
-    .iter()
-    .enumerate()
-    .map(|(i, _)| format_ident!("arg{i}"))
-    .collect::<Vec<_>>();
 
   let ret_val = match &signature.ret_val {
     RetVal::Infallible(arg) => arg,
@@ -144,12 +154,37 @@ pub fn generate_dispatch_fast(
     ..
   } = generator_state;
 
+  // Collect the names and types for the fastcall and the underlying op call
+  let mut fastcall_names = vec![];
+  let mut fastcall_types = vec![];
+  let mut input_types = vec![];
+  let mut call_names = vec![];
+  let mut call_args = vec![];
+  for (i, (input, arg)) in zip(inputs, &signature.args).enumerate() {
+    let name = format_ident!("arg{i}");
+    if let Input::Concrete(fv) = input {
+      fastcall_names.push(name.clone());
+      fastcall_types.push(fv.quote_rust_type(deno_core));
+      input_types.push(fv.quote_type());
+    }
+
+    call_names.push(name.clone());
+    call_args.push(map_v8_fastcall_arg_to_arg(
+      deno_core,
+      opctx,
+      fast_api_callback_options,
+      needs_fast_opctx,
+      needs_fast_api_callback_options,
+      &name,
+      arg,
+    )?)
+  }
+
   let handle_error = match signature.ret_val {
     RetVal::Infallible(_) => quote!(),
     RetVal::Result(_) => {
       *needs_fast_api_callback_options = true;
       *needs_fast_opctx = true;
-      inputs.push(V8FastCallType::CallbackOptions);
       quote! {
         let #result = match #result {
           Ok(#result) => #result,
@@ -178,66 +213,55 @@ pub fn generate_dispatch_fast(
     }
   };
 
-  let input_types = inputs.iter().map(|fv| fv.quote_type()).collect::<Vec<_>>();
-  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(
-      &[ Type::V8Value, #( #input_types ),* ],
-      #output_type,
-      Self::#fast_function as *const ::std::ffi::c_void
+  let with_opctx = if *needs_fast_opctx {
+    *needs_fast_api_callback_options = true;
+    quote!(
+      let #opctx = unsafe {
+        &*(#deno_core::v8::Local::<v8::External>::cast(unsafe { #fast_api_callback_options.data.data }).value()
+            as *const #deno_core::_ops::OpCtx)
+      };
     )
+  } else {
+    quote!()
   };
 
-  let output_type = output.quote_rust_type(deno_core);
-  let mut types = inputs
-    .iter()
-    .map(|rv| rv.quote_rust_type(deno_core))
-    .collect::<Vec<_>>();
-
-  let call_idents = names.clone();
-  let mut call_args = vec![];
-  for (name, arg) in zip(names.iter(), signature.args.iter()) {
-    call_args.push(map_v8_fastcall_arg_to_arg(
-      deno_core,
-      fast_api_callback_options,
-      needs_fast_api_callback_options,
-      name,
-      arg,
-    )?)
-  }
-
   let with_fast_api_callback_options = if *needs_fast_api_callback_options {
-    types.push(V8FastCallType::CallbackOptions.quote_rust_type(deno_core));
-    names.push(fast_api_callback_options.clone());
+    input_types.push(V8FastCallType::CallbackOptions.quote_type());
+    fastcall_types
+      .push(V8FastCallType::CallbackOptions.quote_rust_type(deno_core));
+    fastcall_names.push(fast_api_callback_options.clone());
     quote! {
       let #fast_api_callback_options = unsafe { &mut *#fast_api_callback_options };
     }
   } else {
     quote!()
   };
-  let with_opctx = if *needs_fast_opctx {
-    quote!(
-      let #opctx = unsafe {
-        &*(#deno_core::v8::Local::<v8::External>::cast(unsafe { #fast_api_callback_options.data.data }).value()
-            as *const #deno_core::_ops::OpCtx)
-      };
+
+  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(
+      &[ Type::V8Value, #( #input_types ),* ],
+      #output_type,
+      Self::#fast_function as *const ::std::ffi::c_void
     )
-  } else {
-    quote!()
   };
 
+  // Ensure that we have the same types in the fast function definition as we do in the signature
+  debug_assert!(fastcall_types.len() == input_types.len());
+
+  let output_type = output.quote_rust_type(deno_core);
   let fast_fn = quote!(
     fn #fast_function(
       _: #deno_core::v8::Local<#deno_core::v8::Object>,
-      #( #names: #types, )*
+      #( #fastcall_names: #fastcall_types, )*
     ) -> #output_type {
       #with_fast_api_callback_options
       #with_opctx
       #(#call_args)*
-      let #result = Self::call(#(#call_idents),*);
+      let #result = Self::call(#(#call_names),*);
       #handle_error
       #result
     }
@@ -248,13 +272,27 @@ pub fn generate_dispatch_fast(
 
 fn map_v8_fastcall_arg_to_arg(
   deno_core: &TokenStream,
+  opctx: &Ident,
   fast_api_callback_options: &Ident,
+  needs_opctx: &mut bool,
   needs_fast_api_callback_options: &mut bool,
   arg_ident: &Ident,
   arg: &Arg,
 ) -> Result<TokenStream, V8MappingError> {
   let arg_temp = format_ident!("{}_temp", arg_ident);
   let res = match arg {
+    Arg::Ref(RefType::Ref, Special::OpState) => {
+      *needs_opctx = true;
+      quote!(let #arg_ident = &#opctx.state.borrow();)
+    }
+    Arg::Ref(RefType::Mut, Special::OpState) => {
+      *needs_opctx = true;
+      quote!(let #arg_ident = &mut #opctx.state.borrow_mut();)
+    }
+    Arg::RcRefCell(Special::OpState) => {
+      *needs_opctx = true;
+      quote!(let #arg_ident = #opctx.state.clone();)
+    }
     Arg::Special(Special::RefStr) => {
       quote! {
         let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
@@ -303,6 +341,11 @@ fn map_arg_to_v8_fastcall_type(
   arg: &Arg,
 ) -> Result<Option<V8FastCallType>, V8MappingError> {
   let rv = match arg {
+    // Virtual OpState arguments
+    Arg::RcRefCell(Special::OpState) | Arg::Ref(_, Special::OpState) => {
+      V8FastCallType::Virtual
+    }
+    // Other types + ref types are not handled
     Arg::OptionNumeric(_) | Arg::SerdeV8(_) | Arg::Ref(..) => return Ok(None),
     // We don't support v8 global arguments
     Arg::V8Global(_) => return Ok(None),
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index c2ea242..5475e02 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -5,6 +5,7 @@ use super::generator_state::GeneratorState;
 use super::signature::Arg;
 use super::signature::NumericArg;
 use super::signature::ParsedSignature;
+use super::signature::RefType;
 use super::signature::RetVal;
 use super::signature::Special;
 use super::MacroConfig;
@@ -64,6 +65,12 @@ pub(crate) fn generate_dispatch_slow(
     quote!()
   };
 
+  let with_opstate = if generator_state.needs_opstate {
+    with_opstate(generator_state)
+  } else {
+    quote!()
+  };
+
   let with_opctx = if generator_state.needs_opctx {
     with_opctx(generator_state)
   } else {
@@ -95,6 +102,7 @@ pub(crate) fn generate_dispatch_slow(
     #with_retval
     #with_args
     #with_opctx
+    #with_opstate
 
     #output
   }})
@@ -149,6 +157,18 @@ fn with_opctx(generator_state: &mut GeneratorState) -> TokenStream {
   };)
 }
 
+fn with_opstate(generator_state: &mut GeneratorState) -> TokenStream {
+  let GeneratorState {
+    opctx,
+    opstate,
+    needs_opctx,
+    ..
+  } = generator_state;
+
+  *needs_opctx = true;
+  quote!(let #opstate = &#opctx.state;)
+}
+
 pub fn extract_arg(
   generator_state: &mut GeneratorState,
   index: usize,
@@ -171,7 +191,9 @@ pub fn from_arg(
     deno_core,
     args,
     scope,
+    opstate,
     needs_scope,
+    needs_opstate,
     ..
   } = &mut generator_state;
   let arg_ident = args.get_mut(index).expect("Argument at index was missing");
@@ -250,6 +272,18 @@ pub fn from_arg(
       *needs_scope = true;
       quote!(let #arg_ident = &mut #scope;)
     }
+    Arg::Ref(RefType::Ref, Special::OpState) => {
+      *needs_opstate = true;
+      quote!(let #arg_ident = &#opstate.borrow();)
+    }
+    Arg::Ref(RefType::Mut, Special::OpState) => {
+      *needs_opstate = true;
+      quote!(let #arg_ident = &mut #opstate.borrow_mut();)
+    }
+    Arg::RcRefCell(Special::OpState) => {
+      *needs_opstate = true;
+      quote!(let #arg_ident = #opstate.clone();)
+    }
     Arg::V8Local(v8)
     | Arg::OptionV8Local(v8)
     | Arg::V8Ref(_, v8)
diff --git a/ops/op2/generator_state.rs b/ops/op2/generator_state.rs
index e437ea4..09cdb46 100644
--- a/ops/op2/generator_state.rs
+++ b/ops/op2/generator_state.rs
@@ -21,6 +21,8 @@ pub struct GeneratorState {
   pub fn_args: Ident,
   /// The `OpCtx` used for various information required for some ops.
   pub opctx: Ident,
+  /// The `OpState` used for storing op state.
+  pub opstate: Ident,
   /// The `FastApiCallbackOptions` used in fast calls for fallback returns.
   pub fast_api_callback_options: Ident,
   /// The `v8::ReturnValue` used in the slow function
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 81bad90..9f53489 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -154,6 +154,7 @@ fn generate_op2(
   let scope = Ident::new("scope", Span::call_site());
   let info = Ident::new("info", Span::call_site());
   let opctx = Ident::new("opctx", Span::call_site());
+  let opstate = Ident::new("opstate", Span::call_site());
   let slow_function = Ident::new("v8_fn_ptr", Span::call_site());
   let fast_function = Ident::new("v8_fn_ptr_fast", Span::call_site());
   let fast_api_callback_options =
@@ -174,6 +175,7 @@ fn generate_op2(
     scope,
     info,
     opctx,
+    opstate,
     fast_api_callback_options,
     deno_core,
     result,
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 5840c7d..f5e74cb 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -167,6 +167,11 @@ impl Arg {
         | Special::OpState
         | Special::HandleScope,
       ) => true,
+      Self::RcRefCell(
+        Special::FastApiCallbackOptions
+        | Special::OpState
+        | Special::HandleScope,
+      ) => true,
       _ => false,
     }
   }
@@ -575,10 +580,9 @@ fn parse_type_path(
   // Ensure that we have the correct reference state. This is a bit awkward but it's
   // the easiest way to work with the 'rules!' macro above.
   match res {
-    CBare(
-      TSpecial(Special::RefStr | Special::OpState | Special::HandleScope)
-      | TV8(_),
-    ) => {
+    // OpState appears in both ways
+    CBare(TSpecial(Special::OpState)) => {}
+    CBare(TSpecial(Special::RefStr | Special::HandleScope) | TV8(_)) => {
       if !is_ref {
         return Err(ArgError::MissingReference(stringify_token(tp)));
       }
@@ -891,7 +895,14 @@ mod tests {
     fn op_v8_scope<'s>(scope: &mut v8::HandleScope<'s>);
     <'s> (Ref(Mut, HandleScope)) -> Infallible(Void)
   );
-
+  test!(
+    fn op_state_rc(state: Rc<RefCell<OpState>>);
+    (RcRefCell(OpState)) -> Infallible(Void)
+  );
+  test!(
+    fn op_state_ref(state: &OpState);
+    (Ref(Ref, OpState)) -> Infallible(Void)
+  );
   // Args
 
   expect_fail!(


Test patch:
diff --git a/ops/op2/test_cases/sync/op_state_rc.out b/ops/op2/test_cases/sync/op_state_rc.out
new file mode 100644
index 0000000..2ef2371
--- /dev/null
+++ b/ops/op2/test_cases/sync/op_state_rc.out
@@ -0,0 +1,62 @@
+#[allow(non_camel_case_types)]
+struct op_state_rc {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_state_rc {
+    const NAME: &'static str = stringify!(op_state_rc);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_state_rc),
+        false,
+        false,
+        false,
+        1usize as u8,
+        Self::v8_fn_ptr as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new(
+                &[Type::V8Value, Type::CallbackOptions],
+                CType::Void,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl op_state_rc {
+    pub const fn name() -> &'static str {
+        stringify!(op_state_rc)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+    ) -> () {
+        let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<
+                v8::External,
+            >::cast(unsafe { fast_api_callback_options.data.data })
+                .value() as *const deno_core::_ops::OpCtx)
+        };
+        let arg0 = opctx.state.clone();
+        let result = Self::call(arg0);
+        result
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+                as *const deno_core::_ops::OpCtx)
+        };
+        let opstate = &opctx.state;
+        let arg0 = opstate.clone();
+        let result = Self::call(arg0);
+    }
+    #[inline(always)]
+    fn call(state: Rc<RefCell<OpState>>) {}
+}
diff --git a/ops/op2/test_cases/sync/op_state_rc.rs b/ops/op2/test_cases/sync/op_state_rc.rs
new file mode 100644
index 0000000..609e805
--- /dev/null
+++ b/ops/op2/test_cases/sync/op_state_rc.rs
@@ -0,0 +1,4 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2(fast)]
+fn op_state_rc(state: Rc<RefCell<OpState>>) {}
\ No newline at end of file
diff --git a/ops/op2/test_cases/sync/op_state_ref.out b/ops/op2/test_cases/sync/op_state_ref.out
new file mode 100644
index 0000000..a0ad798
--- /dev/null
+++ b/ops/op2/test_cases/sync/op_state_ref.out
@@ -0,0 +1,62 @@
+#[allow(non_camel_case_types)]
+struct op_state_rc {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_state_rc {
+    const NAME: &'static str = stringify!(op_state_rc);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_state_rc),
+        false,
+        false,
+        false,
+        1usize as u8,
+        Self::v8_fn_ptr as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new(
+                &[Type::V8Value, Type::CallbackOptions],
+                CType::Void,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl op_state_rc {
+    pub const fn name() -> &'static str {
+        stringify!(op_state_rc)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+    ) -> () {
+        let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<
+                v8::External,
+            >::cast(unsafe { fast_api_callback_options.data.data })
+                .value() as *const deno_core::_ops::OpCtx)
+        };
+        let arg0 = &opctx.state.borrow();
+        let result = Self::call(arg0);
+        result
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+                as *const deno_core::_ops::OpCtx)
+        };
+        let opstate = &opctx.state;
+        let arg0 = &opstate.borrow();
+        let result = Self::call(arg0);
+    }
+    #[inline(always)]
+    fn call(state: &OpState) {}
+}
diff --git a/ops/op2/test_cases/sync/op_state_ref.rs b/ops/op2/test_cases/sync/op_state_ref.rs
new file mode 100644
index 0000000..1920d52
--- /dev/null
+++ b/ops/op2/test_cases/sync/op_state_ref.rs
@@ -0,0 +1,4 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2(fast)]
+fn op_state_rc(state: &OpState) {}
\ No newline at end of file
diff --git a/ops/op2/test_cases/sync/v8_ref_option.out b/ops/op2/test_cases/sync/v8_ref_option.out
index 4a031b6..bb083cd 100644
--- a/ops/op2/test_cases/sync/v8_ref_option.out
+++ b/ops/op2/test_cases/sync/v8_ref_option.out
@@ -15,7 +15,7 @@ impl deno_core::_ops::Op for op_v8_lifetime {
             use deno_core::v8::fast_api::Type;
             use deno_core::v8::fast_api::CType;
             deno_core::v8::fast_api::FastFunction::new(
-                &[Type::V8Value, Type::V8Value, Type::V8Value],
+                &[Type::V8Value, Type::V8Value, Type::V8Value, Type::CallbackOptions],
                 CType::Void,
                 Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
             )