Repository: denoland/deno_core
Pull request #25 (2023-07-09 16:16:39)

feat(ops): Make v8::* ops fast-capable
Ops that are `v8::*`-style are fast-capable, so let's implement this. We start sharing a bit of code between fast and slow ops to support this.


Patch:
diff --git a/core/lib.rs b/core/lib.rs
index eef9b8f..9fb7f3b 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -138,19 +138,7 @@ pub mod _ops {
   pub use super::ops::to_op_result;
   pub use super::ops::OpCtx;
   pub use super::ops::OpResult;
-  pub use super::runtime::ops::map_async_op1;
-  pub use super::runtime::ops::map_async_op2;
-  pub use super::runtime::ops::map_async_op3;
-  pub use super::runtime::ops::map_async_op4;
-  pub use super::runtime::ops::queue_async_op;
-  pub use super::runtime::ops::queue_fast_async_op;
-  pub use super::runtime::ops::serde_rust_to_v8;
-  pub use super::runtime::ops::serde_v8_to_rust;
-  pub use super::runtime::ops::to_i32;
-  pub use super::runtime::ops::to_str;
-  pub use super::runtime::ops::to_str_ptr;
-  pub use super::runtime::ops::to_string_ptr;
-  pub use super::runtime::ops::to_u32;
+  pub use super::runtime::ops::*;
   pub use super::runtime::V8_WRAPPER_OBJECT_INDEX;
   pub use super::runtime::V8_WRAPPER_TYPE_INDEX;
 }
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 135445f..13ba9eb 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -301,6 +301,34 @@ pub fn to_str<'a, const N: usize>(
   string.to_rust_cow_lossy(scope, buffer)
 }
 
+/// Converts from a raw [`v8::Value`] to the expected V8 data type.
+#[inline(always)]
+#[allow(clippy::result_unit_err)]
+pub fn v8_try_convert<'a, T>(
+  value: v8::Local<'a, v8::Value>,
+) -> Result<v8::Local<'a, T>, ()>
+where
+  v8::Local<'a, T>: TryFrom<v8::Local<'a, v8::Value>>,
+{
+  v8::Local::<T>::try_from(value).map_err(drop)
+}
+
+/// Converts from a raw [`v8::Value`] to the expected V8 data type, wrapped in an [`Option`].
+#[inline(always)]
+#[allow(clippy::result_unit_err)]
+pub fn v8_try_convert_option<'a, T>(
+  value: v8::Local<'a, v8::Value>,
+) -> Result<Option<v8::Local<'a, T>>, ()>
+where
+  v8::Local<'a, T>: TryFrom<v8::Local<'a, v8::Value>>,
+{
+  if value.is_null_or_undefined() {
+    Ok(None)
+  } else {
+    Ok(Some(v8::Local::<T>::try_from(value).map_err(drop)?))
+  }
+}
+
 pub fn serde_rust_to_v8<'a, T: Serialize>(
   scope: &mut v8::HandleScope<'a>,
   input: T,
@@ -698,7 +726,7 @@ mod tests {
 
   /// Tests v8 types without a handle scope
   #[allow(clippy::needless_lifetimes)]
-  #[op2(core)]
+  #[op2(core, fast)]
   pub fn op_test_v8_types<'s>(
     s: &v8::String,
     s2: v8::Local<v8::String>,
@@ -713,7 +741,7 @@ mod tests {
     }
   }
 
-  #[op2(core)]
+  #[op2(core, fast)]
   pub fn op_test_v8_option_string(s: Option<&v8::String>) -> i32 {
     if let Some(s) = s {
       s.length() as i32
@@ -775,15 +803,21 @@ mod tests {
   pub async fn test_op_v8_types() -> Result<(), Box<dyn std::error::Error>> {
     for (a, b) in [("a", 1), ("b", 2), ("c", 3)] {
       run_test2(
-        1,
+        10000,
         "op_test_v8_types",
         &format!("assert(op_test_v8_types('{a}', 'a', 'b') == {b})"),
       )?;
     }
+    // Fast ops
     for (a, b, c) in [
-      ("op_test_v8_type_return", "'xyz'", "'xyz'"),
       ("op_test_v8_option_string", "'xyz'", "3"),
       ("op_test_v8_option_string", "null", "-1"),
+    ] {
+      run_test2(10000, a, &format!("assert({a}({b}) == {c})"))?;
+    }
+    // Non-fast ops
+    for (a, b, c) in [
+      ("op_test_v8_type_return", "'xyz'", "'xyz'"),
       ("op_test_v8_type_return_option", "'xyz'", "'xyz'"),
       ("op_test_v8_type_return_option", "null", "null"),
       ("op_test_v8_type_handle_scope", "'xyz'", "'xyz'"),
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 713ab08..f3596ad 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -1,4 +1,6 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use super::dispatch_shared::v8_intermediate_to_arg;
+use super::dispatch_shared::v8_to_arg;
 use super::generator_state::GeneratorState;
 use super::signature::Arg;
 use super::signature::NumericArg;
@@ -196,9 +198,16 @@ pub fn generate_dispatch_fast(
     .collect::<Vec<_>>();
 
   let call_idents = names.clone();
-  let call_args = zip(names.iter(), signature.args.iter())
-    .map(|(name, arg)| map_v8_fastcall_arg_to_arg(deno_core, name, arg))
-    .collect::<Vec<_>>();
+  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));
@@ -239,11 +248,13 @@ pub fn generate_dispatch_fast(
 
 fn map_v8_fastcall_arg_to_arg(
   deno_core: &TokenStream,
+  fast_api_callback_options: &Ident,
+  needs_fast_api_callback_options: &mut bool,
   arg_ident: &Ident,
   arg: &Arg,
-) -> TokenStream {
+) -> Result<TokenStream, V8MappingError> {
   let arg_temp = format_ident!("{}_temp", arg_ident);
-  match arg {
+  let res = match arg {
     Arg::Special(Special::RefStr) => {
       quote! {
         let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
@@ -259,8 +270,33 @@ fn map_v8_fastcall_arg_to_arg(
         let #arg_ident = #deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
       }
     }
+    Arg::V8Local(v8)
+    | Arg::OptionV8Local(v8)
+    | Arg::V8Ref(_, v8)
+    | Arg::OptionV8Ref(_, v8) => {
+      let arg_ident = arg_ident.clone();
+      let deno_core = deno_core.clone();
+      // Note that we only request callback options if we were required to provide a type error
+      let throw_type_error = || {
+        *needs_fast_api_callback_options = true;
+        Ok(quote! {
+          #fast_api_callback_options.fallback = true;
+          return ::std::default::Default::default();
+        })
+      };
+      let extract_intermediate = v8_intermediate_to_arg(&arg_ident, arg);
+      v8_to_arg(
+        v8,
+        &arg_ident,
+        arg,
+        &deno_core,
+        throw_type_error,
+        extract_intermediate,
+      )?
+    }
     _ => quote!(let #arg_ident = #arg_ident as _;),
-  }
+  };
+  Ok(res)
 }
 
 fn map_arg_to_v8_fastcall_type(
@@ -268,12 +304,13 @@ fn map_arg_to_v8_fastcall_type(
 ) -> Result<Option<V8FastCallType>, V8MappingError> {
   let rv = match arg {
     Arg::OptionNumeric(_) | Arg::SerdeV8(_) | Arg::Ref(..) => return Ok(None),
+    // We don't support v8 global arguments
+    Arg::V8Global(_) => return Ok(None),
     // We don't support v8 type arguments
     Arg::V8Ref(..)
-    | Arg::V8Global(_)
     | Arg::V8Local(_)
     | Arg::OptionV8Local(_)
-    | Arg::OptionV8Ref(..) => return Ok(None),
+    | Arg::OptionV8Ref(..) => V8FastCallType::V8Value,
 
     Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool,
     Arg::Numeric(NumericArg::u32)
diff --git a/ops/op2/dispatch_shared.rs b/ops/op2/dispatch_shared.rs
new file mode 100644
index 0000000..f8ad474
--- /dev/null
+++ b/ops/op2/dispatch_shared.rs
@@ -0,0 +1,58 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use super::signature::Arg;
+use super::signature::RefType;
+use super::signature::V8Arg;
+use super::V8MappingError;
+use proc_macro2::Ident;
+use proc_macro2::TokenStream;
+use quote::format_ident;
+use quote::quote;
+
+/// Given an [`Arg`] containing a V8 value, converts this value to itself final form.
+pub fn v8_intermediate_to_arg(i: &Ident, arg: &Arg) -> TokenStream {
+  let arg = match arg {
+    Arg::V8Ref(RefType::Ref, _) => quote!(&#i),
+    Arg::V8Ref(RefType::Mut, _) => quote!(&mut #i),
+    Arg::V8Local(_) => quote!(#i),
+    Arg::OptionV8Ref(RefType::Ref, _) => {
+      quote!(match &#i { None => None, Some(v) => Some(::std::ops::Deref::deref(v)) })
+    }
+    Arg::OptionV8Ref(RefType::Mut, _) => {
+      quote!(match &#i { None => None, Some(v) => Some(::std::ops::DerefMut::deref_mut(v)) })
+    }
+    Arg::OptionV8Local(_) => quote!(#i),
+    _ => unreachable!("Not a v8 arg: {arg:?}"),
+  };
+  quote!(let #i = #arg;)
+}
+
+/// Generates a [`v8::Value`] of the correct type for the required V8Arg, throwing an exception if the
+/// type cannot be cast.
+pub fn v8_to_arg(
+  v8: &V8Arg,
+  arg_ident: &Ident,
+  arg: &Arg,
+  deno_core: &TokenStream,
+  mut throw_type_error: impl FnMut() -> Result<TokenStream, V8MappingError>,
+  extract_intermediate: TokenStream,
+) -> Result<TokenStream, V8MappingError> {
+  let try_convert = format_ident!(
+    "{}",
+    if arg.is_option() {
+      "v8_try_convert_option"
+    } else {
+      "v8_try_convert"
+    }
+  );
+  let throw_type_error_block = if *v8 == V8Arg::Value {
+    quote!(unreachable!())
+  } else {
+    throw_type_error()?
+  };
+  Ok(quote! {
+    let Ok(mut #arg_ident) = #deno_core::_ops::#try_convert::<#deno_core::v8::#v8>(#arg_ident) else {
+      #throw_type_error_block
+    };
+    #extract_intermediate
+  })
+}
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 85a41f7..c2ea242 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -1,12 +1,12 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use super::dispatch_shared::v8_intermediate_to_arg;
+use super::dispatch_shared::v8_to_arg;
 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::signature::V8Arg;
 use super::MacroConfig;
 use super::V8MappingError;
 use proc_macro2::Ident;
@@ -250,49 +250,23 @@ pub fn from_arg(
       *needs_scope = true;
       quote!(let #arg_ident = &mut #scope;)
     }
-    Arg::V8Ref(_, v8) => {
+    Arg::V8Local(v8)
+    | Arg::OptionV8Local(v8)
+    | Arg::V8Ref(_, v8)
+    | Arg::OptionV8Ref(_, v8) => {
       let arg_ident = arg_ident.clone();
-      let arg = from_v8_arg(generator_state, v8, &arg_ident)?;
-      quote! {
-        #arg;
-        let #arg_ident = &#arg_ident;
-      }
-    }
-    Arg::V8Local(v8) => {
-      let arg_ident = arg_ident.clone();
-      from_v8_arg(generator_state, v8, &arg_ident)?
-    }
-    Arg::OptionV8Ref(RefType::Ref, v8) => {
-      let arg_ident = arg_ident.clone();
-      let arg = from_arg(generator_state, index, &Arg::OptionV8Local(*v8))?;
-      quote! {
-        // First get the Option<Local>
-        #arg;
-        // Then map the reference
-        let #arg_ident = #arg_ident.as_ref().map(|v| ::std::ops::Deref::deref(v));
-      }
-    }
-    Arg::OptionV8Ref(RefType::Mut, v8) => {
-      let arg_ident = arg_ident.clone();
-      let arg = from_arg(generator_state, index, &Arg::OptionV8Local(*v8))?;
-      quote! {
-        // First get the Option<Local>
-        #arg;
-        // Then map the reference
-        let #arg_ident = #arg_ident.as_mut().map(|v| ::std::ops::DerefMut::deref_mut(v));
-      }
-    }
-    Arg::OptionV8Local(v8) => {
-      let arg_ident = arg_ident.clone();
-      let arg = from_v8_arg(generator_state, v8, &arg_ident)?;
-      quote! {
-        let #arg_ident = if #arg_ident.is_null_or_undefined() {
-          None
-        } else {
-          #arg;
-          Some(#arg_ident)
-        };
-      }
+      let deno_core = deno_core.clone();
+      let throw_type_error =
+        || throw_type_error(generator_state, format!("expected {v8:?}"));
+      let extract_intermediate = v8_intermediate_to_arg(&arg_ident, arg);
+      v8_to_arg(
+        v8,
+        &arg_ident,
+        arg,
+        &deno_core,
+        throw_type_error,
+        extract_intermediate,
+      )?
     }
     Arg::SerdeV8(_class) => {
       *needs_scope = true;
@@ -315,32 +289,6 @@ pub fn from_arg(
   Ok(res)
 }
 
-/// Generates a v8::Value of the correct type for the required V8Arg, throwing an exception if the
-/// type cannot be cast.
-fn from_v8_arg(
-  generator_state: &mut GeneratorState,
-  v8: &V8Arg,
-  arg_ident: &Ident,
-) -> Result<TokenStream, V8MappingError> {
-  let GeneratorState {
-    deno_core,
-    needs_scope,
-    ..
-  } = generator_state;
-
-  *needs_scope = true;
-  let v8: &'static str = v8.into();
-  let deno_core = deno_core.clone();
-  let throw_type_error =
-    throw_type_error(generator_state, format!("expected v8::{}", v8))?;
-  let v8 = format_ident!("{}", v8);
-  Ok(quote! {
-    let Ok(mut #arg_ident) = #deno_core::v8::Local::<#deno_core::v8::#v8>::try_from(#arg_ident) else {
-      #throw_type_error
-    };
-  })
-}
-
 pub fn call(
   generator_state: &mut GeneratorState,
 ) -> Result<TokenStream, V8MappingError> {
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 14d3c3b..a60166d 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -26,6 +26,7 @@ use self::signature::Arg;
 use self::signature::SignatureError;
 
 pub mod dispatch_fast;
+pub mod dispatch_shared;
 pub mod dispatch_slow;
 pub mod generator_state;
 pub mod signature;
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 63ec1f1..5840c7d 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -2,8 +2,11 @@
 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 quote::TokenStreamExt;
 use std::collections::BTreeMap;
 use strum::IntoEnumIterator;
 use strum::IntoStaticStr;
@@ -102,6 +105,13 @@ pub enum V8Arg {
   Uint32,
 }
 
+impl ToTokens for V8Arg {
+  fn to_tokens(&self, tokens: &mut TokenStream) {
+    let v8: &'static str = self.into();
+    tokens.append(format_ident!("{v8}"))
+  }
+}
+
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum Special {
   HandleScope,
@@ -160,6 +170,37 @@ impl Arg {
       _ => false,
     }
   }
+
+  /// Convert the [`Arg`] into a [`TokenStream`] representing the fully-qualified type.
+  #[allow(unused)] // unused for now but keeping
+  pub fn type_token(&self, deno_core: &TokenStream) -> TokenStream {
+    match self {
+      Arg::V8Ref(RefType::Ref, v8) => quote!(&#deno_core::v8::#v8),
+      Arg::V8Ref(RefType::Mut, v8) => quote!(&mut #deno_core::v8::#v8),
+      Arg::V8Local(v8) => quote!(#deno_core::v8::Local<#deno_core::v8::#v8>),
+      Arg::OptionV8Ref(RefType::Ref, v8) => {
+        quote!(::std::option::Option<&#deno_core::v8::#v8>)
+      }
+      Arg::OptionV8Ref(RefType::Mut, v8) => {
+        quote!(::std::option::Option<&mut #deno_core::v8::#v8>)
+      }
+      Arg::OptionV8Local(v8) => {
+        quote!(::std::option::Option<#deno_core::v8::Local<#deno_core::v8::#v8>>)
+      }
+      _ => todo!(),
+    }
+  }
+
+  /// Is this type an [`Option`]?
+  pub fn is_option(&self) -> bool {
+    matches!(
+      self,
+      Arg::OptionV8Ref(..)
+        | Arg::OptionV8Local(..)
+        | Arg::OptionNumeric(..)
+        | Arg::Option(..)
+    )
+  }
 }
 
 enum ParsedType {


Test patch:
diff --git a/ops/op2/test_cases/sync/v8_handlescope.out b/ops/op2/test_cases/sync/v8_handlescope.out
index 85c217f..128c225 100644
--- a/ops/op2/test_cases/sync/v8_handlescope.out
+++ b/ops/op2/test_cases/sync/v8_handlescope.out
@@ -40,12 +40,12 @@ impl op_handlescope {
             &*info
         });
         let arg1 = args.get(0usize as i32);
-        let Ok(mut arg1) = deno_core::v8::Local::<
+        let Ok(mut arg1) = deno_core::_ops::v8_try_convert::<
             deno_core::v8::String,
-        >::try_from(arg1) else {
+        >(arg1) else {
         let msg = deno_core::v8::String::new_from_one_byte(
                 &mut scope,
-                "expected v8::String".as_bytes(),
+                "expected String".as_bytes(),
                 deno_core::v8::NewStringType::Normal,
             )
             .unwrap();
@@ -53,6 +53,7 @@ impl op_handlescope {
         scope.throw_exception(exc);
         return;
     };
+        let arg1 = arg1;
         let arg0 = &mut scope;
         let result = Self::call(arg0, arg1);
         rv.set(result.into())
diff --git a/ops/op2/test_cases/sync/v8_lifetime.out b/ops/op2/test_cases/sync/v8_lifetime.out
index 4c91f29..8990c8c 100644
--- a/ops/op2/test_cases/sync/v8_lifetime.out
+++ b/ops/op2/test_cases/sync/v8_lifetime.out
@@ -32,7 +32,6 @@ impl op_v8_lifetime {
         }
     }
     extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
-        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -40,12 +39,13 @@ impl op_v8_lifetime {
             &*info
         });
         let arg0 = args.get(0usize as i32);
-        let Ok(mut arg0) = deno_core::v8::Local::<
+        let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
             deno_core::v8::String,
-        >::try_from(arg0) else {
+        >(arg0) else {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let msg = deno_core::v8::String::new_from_one_byte(
                 &mut scope,
-                "expected v8::String".as_bytes(),
+                "expected String".as_bytes(),
                 deno_core::v8::NewStringType::Normal,
             )
             .unwrap();
@@ -53,6 +53,7 @@ impl op_v8_lifetime {
         scope.throw_exception(exc);
         return;
     };
+        let arg0 = arg0;
         let result = Self::call(arg0);
         rv.set(result.into())
     }
diff --git a/ops/op2/test_cases/sync/v8_ref_option.out b/ops/op2/test_cases/sync/v8_ref_option.out
index ba04ec1..f558a14 100644
--- a/ops/op2/test_cases/sync/v8_ref_option.out
+++ b/ops/op2/test_cases/sync/v8_ref_option.out
@@ -8,7 +8,15 @@ impl deno_core::_ops::Op for op_v8_lifetime {
         name: stringify!(op_v8_lifetime),
         v8_fn_ptr: Self::v8_fn_ptr as _,
         enabled: true,
-        fast_fn: None,
+        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::V8Value, Type::V8Value, Type::V8Value],
+                CType::Void,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
         is_async: false,
         is_unstable: false,
         is_v8: false,
@@ -24,58 +32,91 @@ impl op_v8_lifetime {
             name: stringify!(op_v8_lifetime),
             v8_fn_ptr: Self::v8_fn_ptr as _,
             enabled: true,
-            fast_fn: None,
+            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::V8Value, Type::V8Value, Type::V8Value],
+                    CType::Void,
+                    Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+                )
+            }),
             is_async: false,
             is_unstable: false,
             is_v8: false,
             arg_count: 2usize as u8,
         }
     }
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: deno_core::v8::Local<deno_core::v8::Value>,
+        arg1: deno_core::v8::Local<deno_core::v8::Value>,
+        fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+    ) -> () {
+        let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+        let Ok(mut arg0) = deno_core::_ops::v8_try_convert_option::<
+            deno_core::v8::String,
+        >(arg0) else { fast_api_callback_options.fallback = true;
+        return ::std::default::Default::default();
+    };
+        let arg0 = match &arg0 {
+            None => None,
+            Some(v) => Some(::std::ops::Deref::deref(v)),
+        };
+        let Ok(mut arg1) = deno_core::_ops::v8_try_convert_option::<
+            deno_core::v8::String,
+        >(arg1) else { fast_api_callback_options.fallback = true;
+        return ::std::default::Default::default();
+    };
+        let arg1 = match &arg1 {
+            None => None,
+            Some(v) => Some(::std::ops::DerefMut::deref_mut(v)),
+        };
+        let result = Self::call(arg0, arg1);
+        result
+    }
     extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
-        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
             &*info
         });
         let arg0 = args.get(0usize as i32);
-        let arg0 = if arg0.is_null_or_undefined() {
-            None
-        } else {
-            let Ok(mut arg0) = deno_core::v8::Local::<
-                deno_core::v8::String,
-            >::try_from(arg0) else {
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected v8::String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
-            Some(arg0)
+        let Ok(mut arg0) = deno_core::_ops::v8_try_convert_option::<
+            deno_core::v8::String,
+        >(arg0) else {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let msg = deno_core::v8::String::new_from_one_byte(
+                &mut scope,
+                "expected String".as_bytes(),
+                deno_core::v8::NewStringType::Normal,
+            )
+            .unwrap();
+        let exc = deno_core::v8::Exception::error(&mut scope, msg);
+        scope.throw_exception(exc);
+        return;
+    };
+        let arg0 = match &arg0 {
+            None => None,
+            Some(v) => Some(::std::ops::Deref::deref(v)),
         };
-        let arg0 = arg0.as_ref().map(|v| ::std::ops::Deref::deref(v));
         let arg1 = args.get(1usize as i32);
-        let arg1 = if arg1.is_null_or_undefined() {
-            None
-        } else {
-            let Ok(mut arg1) = deno_core::v8::Local::<
-                deno_core::v8::String,
-            >::try_from(arg1) else {
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected v8::String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
-            Some(arg1)
+        let Ok(mut arg1) = deno_core::_ops::v8_try_convert_option::<
+            deno_core::v8::String,
+        >(arg1) else {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let msg = deno_core::v8::String::new_from_one_byte(
+                &mut scope,
+                "expected String".as_bytes(),
+                deno_core::v8::NewStringType::Normal,
+            )
+            .unwrap();
+        let exc = deno_core::v8::Exception::error(&mut scope, msg);
+        scope.throw_exception(exc);
+        return;
+    };
+        let arg1 = match &arg1 {
+            None => None,
+            Some(v) => Some(::std::ops::DerefMut::deref_mut(v)),
         };
-        let arg1 = arg1.as_mut().map(|v| ::std::ops::DerefMut::deref_mut(v));
         let result = Self::call(arg0, arg1);
     }
     #[inline(always)]
diff --git a/ops/op2/test_cases/sync/v8_ref_option.rs b/ops/op2/test_cases/sync/v8_ref_option.rs
index 3715b86..863a04c 100644
--- a/ops/op2/test_cases/sync/v8_ref_option.rs
+++ b/ops/op2/test_cases/sync/v8_ref_option.rs
@@ -1,5 +1,5 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
 
-#[op2]
+#[op2(fast)]
 pub fn op_v8_lifetime<'s>(s: Option<&v8::String>, s2: Option<&mut v8::String>) {}
 
diff --git a/ops/op2/test_cases/sync/v8_string.out b/ops/op2/test_cases/sync/v8_string.out
index 3392ad3..5ea77e6 100644
--- a/ops/op2/test_cases/sync/v8_string.out
+++ b/ops/op2/test_cases/sync/v8_string.out
@@ -32,7 +32,6 @@ impl op_v8_string {
         }
     }
     extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
-        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -40,12 +39,13 @@ impl op_v8_string {
             &*info
         });
         let arg0 = args.get(0usize as i32);
-        let Ok(mut arg0) = deno_core::v8::Local::<
+        let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
             deno_core::v8::String,
-        >::try_from(arg0) else {
+        >(arg0) else {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let msg = deno_core::v8::String::new_from_one_byte(
                 &mut scope,
-                "expected v8::String".as_bytes(),
+                "expected String".as_bytes(),
                 deno_core::v8::NewStringType::Normal,
             )
             .unwrap();
@@ -55,12 +55,13 @@ impl op_v8_string {
     };
         let arg0 = &arg0;
         let arg1 = args.get(1usize as i32);
-        let Ok(mut arg1) = deno_core::v8::Local::<
+        let Ok(mut arg1) = deno_core::_ops::v8_try_convert::<
             deno_core::v8::String,
-        >::try_from(arg1) else {
+        >(arg1) else {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let msg = deno_core::v8::String::new_from_one_byte(
                 &mut scope,
-                "expected v8::String".as_bytes(),
+                "expected String".as_bytes(),
                 deno_core::v8::NewStringType::Normal,
             )
             .unwrap();
@@ -68,6 +69,7 @@ impl op_v8_string {
         scope.throw_exception(exc);
         return;
     };
+        let arg1 = arg1;
         let result = Self::call(arg0, arg1);
         rv.set(result.into())
     }