Repository: denoland/deno_core
Pull request #276 (2023-10-24 10:03:26)

chore(deps): bump rustix from 0.38.14 to 0.38.20
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.14 to 0.38.20.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/bytecodealliance/rustix/commit/414309a2a685d872d0842b54296e5391f94a4dc2"><code>414309a</code></a> chore: Release rustix version 0.38.20</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/ce11b6c8cf59aae04b3c52514d5861ca978c4c66"><code>ce11b6c</code></a> Miscellaneous documentation cleanups. (<a href="https://redirect.github.com/bytecodealliance/rustix/issues/887">#887</a>)</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/a59a1918e230519b40f72063acdc3a84e6d4cdf4"><code>a59a191</code></a> Fix <code>sendmsg_unix</code>'s address encoding. (<a href="https://redirect.github.com/bytecodealliance/rustix/issues/885">#885</a>)</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/e35481c9a7e09eb24d4d67a4def48aa661fd52b3"><code>e35481c</code></a> Fix a documentation link.</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/b8d7e00322407c2e623cf85b4e5b4aaa0234de43"><code>b8d7e00</code></a> Miscellaneous documentation cleanups. (<a href="https://redirect.github.com/bytecodealliance/rustix/issues/883">#883</a>)</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/30a5ae174bc093f8e7ba812def0ca63fe2614867"><code>30a5ae1</code></a> Add support for armv7-sony-vita-newlibeabihf. (<a href="https://redirect.github.com/bytecodealliance/rustix/issues/882">#882</a>)</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/702c54a39c051aeba54d0207c0e58adcf7544750"><code>702c54a</code></a> Add documentation for how to do the equivalent of <code>gethostname</code>. (<a href="https://redirect.github.com/bytecodealliance/rustix/issues/880">#880</a>)</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/3a53dfe16cddc39fd20ecfb07c7d78880cb3880a"><code>3a53dfe</code></a> chore: Release rustix version 0.38.19</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/55cbe8839c8a78896b7f1e5310b3bcf20f8b0ac5"><code>55cbe88</code></a> Fixes for <code>Dir</code> on macOS, FreeBSD, and WASI.</li>
<li><a href="https://github.com/bytecodealliance/rustix/commit/31fd98ca723b93cc6101a3e29843ea5cf094e159"><code>31fd98c</code></a> Merge pull request from GHSA-c827-hfw6-qwvm</li>
<li>Additional commits viewable in <a href="https://github.com/bytecodealliance/rustix/compare/v0.38.14...v0.38.20">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustix&package-manager=cargo&previous-version=0.38.14&new-version=0.38.20)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/denoland/deno_core/network/alerts).

</details>

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/denoland/deno_core?pullRequest=276) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/denoland/deno_core?pullRequest=276) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/denoland/deno_core?pullRequest=276) it.</sub>

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0bce832..815a04d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -721,9 +721,9 @@ checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.7"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
 
 [[package]]
 name = "lock_api"
@@ -1133,9 +1133,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.14"
+version = "0.38.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
+checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
 dependencies = [
  "bitflags 2.4.0",
  "errno",
diff --git a/core/extensions.rs b/core/extensions.rs
index f9f9840..0602cea 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -181,6 +181,22 @@ impl OpDecl {
       ..self
     }
   }
+
+  #[doc(hidden)]
+  pub const fn fast_fn(self) -> FastFunction {
+    let Some(f) = self.fast_fn else {
+      panic!("Not a fast function");
+    };
+    f
+  }
+
+  #[doc(hidden)]
+  pub const fn fast_fn_with_metrics(self) -> FastFunction {
+    let Some(f) = self.fast_fn_with_metrics else {
+      panic!("Not a fast function");
+    };
+    f
+  }
 }
 
 /// Declares a block of Deno `#[op]`s. The first parameter determines the name of the
diff --git a/ops/op2/README.md b/ops/op2/README.md
index b10c3a4..ebc2c00 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -71,6 +71,20 @@ used if you really know what you are doing.
 Lazy `async(deferred)` calls _may_ be fastcalls, though the resolution will
 still happen on a slow path.
 
+## fastcalls
+
+`op2` requires fastcall-compatible ops to be annotated with `fast`. If you wish
+to avoid fastcalls for some reason (this is unlikely), you can specify `nofast`
+instead.
+
+You may also choose an alternate op function to use as the fastcall equivalent
+to a slow function. In this case, you can specify `fast(op_XYZ)`. The other op
+must be decorated with `#[op2(fast)]`, and does not need to be registered. When
+v8 optimized the slow function to a fastcall, it will switch the implementation
+over if the parameters are compatible. This is useful for a function that takes
+any buffer type in the slow path and wishes to use the very fast typed `u8`
+buffer for the fast path.
+
 # Parameters
 
 <!-- START ARGS -->
diff --git a/ops/op2/config.rs b/ops/op2/config.rs
index 44675e2..1d5e79b 100644
--- a/ops/op2/config.rs
+++ b/ops/op2/config.rs
@@ -13,6 +13,8 @@ pub(crate) struct MacroConfig {
   pub fast: bool,
   /// Do not generate a fastcall method (must be fastcall compatible).
   pub nofast: bool,
+  /// Use other ops for the fast alternatives, rather than generating one for this op.
+  pub fast_alternatives: Vec<String>,
   /// Marks an async function (either `async fn` or `fn -> impl Future`)
   pub r#async: bool,
   /// Marks an lazy async function (async must also be true)
@@ -24,13 +26,13 @@ pub(crate) struct MacroConfig {
 impl MacroConfig {
   fn from_token_trees(
     flags: Vec<TokenTree>,
-    args: Vec<Option<Vec<TokenTree>>>,
+    args: Vec<Option<Vec<impl ToTokens>>>,
   ) -> Result<Self, Op2Error> {
     let flags = flags.into_iter().zip(args.into_iter()).map(|(flag, args)| {
       if let Some(args) = args {
         let args = args
           .into_iter()
-          .map(|arg| arg.to_string())
+          .map(|arg| arg.to_token_stream().to_string())
           .collect::<Vec<_>>()
           .join(",");
         format!("{}({args})", flag.into_token_stream())
@@ -60,6 +62,18 @@ impl MacroConfig {
         config.core = true;
       } else if flag == "fast" {
         config.fast = true;
+      } else if flag.starts_with("fast(") {
+        let tokens =
+          syn::parse_str::<TokenTree>(&flag[4..])?.into_token_stream();
+        config.fast_alternatives = std::panic::catch_unwind(|| {
+          rules!(tokens => {
+            ( ( $( $types:ty ),+ ) ) => {
+              types.into_iter().map(|s| s.into_token_stream().to_string().replace(' ', ""))
+            }
+          })
+        })
+        .map_err(|_| Op2Error::PatternMatchFailed("attribute", flag))?
+        .collect::<Vec<_>>();
       } else if flag == "nofast" {
         config.nofast = true;
       } else if flag == "async" {
@@ -79,6 +93,12 @@ impl MacroConfig {
     if config.fast && config.nofast {
       return Err(Op2Error::InvalidAttributeCombination("fast", "nofast"));
     }
+    if config.fast && !config.fast_alternatives.is_empty() {
+      return Err(Op2Error::InvalidAttributeCombination("fast", "fast(...)"));
+    }
+    if config.fast_alternatives.len() > 1 {
+      return Err(Op2Error::TooManyFastAlternatives);
+    }
     if config.fast
       && (config.r#async && !config.async_lazy && !config.async_deferred)
     {
@@ -103,7 +123,7 @@ impl MacroConfig {
         () => {
           Ok(MacroConfig::default())
         }
-        ( $($flags:tt $( ( $( $args:tt ),* ) )? ),+ ) => {
+        ( $($flags:tt $( ( $( $args:ty ),* ) )? ),+ ) => {
           Self::from_token_trees(flags, args)
         }
       })
@@ -118,7 +138,7 @@ impl MacroConfig {
 
     let config = std::panic::catch_unwind(|| {
       rules!(tokens.into_token_stream() => {
-        ( ( $($flags:tt $( ( $( $args:tt ),* ) )? ),+ ) ) => {
+        ( ( $($flags:tt $( ( $( $args:ty ),* ) )? ),+ ) ) => {
           Self::from_token_trees(flags, args)
         }
       })
@@ -198,5 +218,19 @@ mod tests {
         ..Default::default()
       },
     );
+    test_parse(
+      "(fast(op_other1))",
+      MacroConfig {
+        fast_alternatives: vec!["op_other1".to_owned()],
+        ..Default::default()
+      },
+    );
+    test_parse(
+      "(fast(op_generic::<T>))",
+      MacroConfig {
+        fast_alternatives: vec!["op_generic::<T>".to_owned()],
+        ..Default::default()
+      },
+    );
   }
 }
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 9ad9e93..0be7269 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -369,6 +369,18 @@ pub(crate) fn generate_dispatch_fast(
   Option<(TokenStream, TokenStream, TokenStream)>,
   V8SignatureMappingError,
 > {
+  if let Some(alternative) = config.fast_alternatives.get(0) {
+    // TODO(mmastrac): we should validate the alternatives. For now we just assume the caller knows what
+    // they are doing.
+    let alternative =
+      syn::parse_str::<Type>(alternative).expect("Failed to reparse type");
+    return Ok(Some((
+      quote!(#alternative::DECL.fast_fn()),
+      quote!(#alternative::DECL.fast_fn_with_metrics()),
+      quote!(),
+    )));
+  }
+
   // async(lazy) can be fast
   if signature.ret_val.is_async()
     && !config.async_lazy
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 5f4d299..0298e02 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -59,6 +59,8 @@ pub enum Op2Error {
   ShouldBeAsync,
   #[error("This op is not async and should not be marked as (async)")]
   ShouldNotBeAsync,
+  #[error("Only one fast alternative is supported in fast(...) at this time")]
+  TooManyFastAlternatives,
   #[error("The flags for this attribute were not sorted alphabetically. They should be listed as '({0})'.")]
   ImproperlySortedAttribute(String),
 }
@@ -202,7 +204,8 @@ fn generate_op2(
   let (fast_definition, fast_definition_metrics, fast_fn) =
     match generate_dispatch_fast(&config, &mut generator_state, &signature)? {
       Some((fast_definition, fast_metrics_definition, fast_fn)) => {
-        if !config.fast && !config.nofast {
+        if !config.fast && !config.nofast && config.fast_alternatives.is_empty()
+        {
           return Err(Op2Error::ShouldBeFast);
         }
         // nofast requires the function to be valid for fast


Test patch:
diff --git a/ops/op2/test_cases/sync/fast_alternative.out b/ops/op2/test_cases/sync/fast_alternative.out
new file mode 100644
index 0000000..a8add50
--- /dev/null
+++ b/ops/op2/test_cases/sync/fast_alternative.out
@@ -0,0 +1,459 @@
+#[allow(non_camel_case_types)]
+struct op_slow {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_slow {
+    const NAME: &'static str = stringify!(op_slow);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
+        stringify!(op_slow),
+        false,
+        3usize as u8,
+        Self::v8_fn_ptr as _,
+        Self::v8_fn_ptr_metrics as _,
+        Some({ op_fast::DECL.fast_fn() }),
+        Some({ op_fast::DECL.fast_fn_with_metrics() }),
+    );
+}
+impl op_slow {
+    pub const fn name() -> &'static str {
+        stringify!(op_slow)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    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
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let result = {
+            let arg1 = args.get(0usize as i32);
+            let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg1 = arg1 as _;
+            let arg2 = args.get(1usize as i32);
+            let Some(arg2) = deno_core::_ops::to_u32_option(&arg2) else {
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg2 = arg2 as _;
+            let arg0 = &mut scope;
+            Self::call(arg0, arg1, arg2)
+        };
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
+    }
+    extern "C" fn v8_fn_ptr_metrics(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)
+        };
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Dispatched,
+        );
+        Self::v8_fn_ptr(info);
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Completed,
+        );
+    }
+    #[inline(always)]
+    fn call(_scope: &v8::HandleScope, a: u32, b: u32) -> u32 {
+        a + b
+    }
+}
+
+#[allow(non_camel_case_types)]
+struct op_fast {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_fast {
+    const NAME: &'static str = stringify!(op_fast);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
+        stringify!(op_fast),
+        false,
+        2usize as u8,
+        Self::v8_fn_ptr as _,
+        Self::v8_fn_ptr_metrics as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[Type::V8Value, Type::Uint32, Type::Uint32],
+                CType::Uint32,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[Type::V8Value, Type::Uint32, Type::Uint32, Type::CallbackOptions],
+                CType::Uint32,
+                Self::v8_fn_ptr_fast_metrics as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl op_fast {
+    pub const fn name() -> &'static str {
+        stringify!(op_fast)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    #[allow(clippy::too_many_arguments)]
+    fn v8_fn_ptr_fast_metrics(
+        this: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: u32,
+        arg1: u32,
+        fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+    ) -> u32 {
+        let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<
+                deno_core::v8::External,
+            >::cast(unsafe { fast_api_callback_options.data.data })
+                .value() as *const deno_core::_ops::OpCtx)
+        };
+        deno_core::_ops::dispatch_metrics_fast(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Dispatched,
+        );
+        let res = Self::v8_fn_ptr_fast(this, arg0, arg1);
+        deno_core::_ops::dispatch_metrics_fast(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Completed,
+        );
+        res
+    }
+    #[allow(clippy::too_many_arguments)]
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: u32,
+        arg1: u32,
+    ) -> u32 {
+        let result = {
+            let arg0 = arg0 as _;
+            let arg1 = arg1 as _;
+            Self::call(arg0, arg1)
+        };
+        result as _
+    }
+    extern "C" fn v8_fn_ptr(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 result = {
+            let arg0 = args.get(0usize as i32);
+            let Some(arg0) = deno_core::_ops::to_u32_option(&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 u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg0 = arg0 as _;
+            let arg1 = args.get(1usize as i32);
+            let Some(arg1) = deno_core::_ops::to_u32_option(&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 u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg1 = arg1 as _;
+            Self::call(arg0, arg1)
+        };
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
+    }
+    extern "C" fn v8_fn_ptr_metrics(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)
+        };
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Dispatched,
+        );
+        Self::v8_fn_ptr(info);
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Completed,
+        );
+    }
+    #[inline(always)]
+    fn call(a: u32, b: u32) -> u32 {
+        a + b
+    }
+}
+
+#[allow(non_camel_case_types)]
+struct op_slow_generic<T> {
+    _unconstructable: ::std::marker::PhantomData<(T)>,
+}
+impl<T: Trait> deno_core::_ops::Op for op_slow_generic<T> {
+    const NAME: &'static str = stringify!(op_slow_generic);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
+        stringify!(op_slow_generic),
+        false,
+        3usize as u8,
+        Self::v8_fn_ptr as _,
+        Self::v8_fn_ptr_metrics as _,
+        Some({ op_fast_generic::<T>::DECL.fast_fn() }),
+        Some({ op_fast_generic::<T>::DECL.fast_fn_with_metrics() }),
+    );
+}
+impl<T: Trait> op_slow_generic<T> {
+    pub const fn name() -> &'static str {
+        stringify!(op_slow_generic)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    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
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let result = {
+            let arg1 = args.get(0usize as i32);
+            let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg1 = arg1 as _;
+            let arg2 = args.get(1usize as i32);
+            let Some(arg2) = deno_core::_ops::to_u32_option(&arg2) else {
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg2 = arg2 as _;
+            let arg0 = &mut scope;
+            Self::call(arg0, arg1, arg2)
+        };
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
+    }
+    extern "C" fn v8_fn_ptr_metrics(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)
+        };
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Dispatched,
+        );
+        Self::v8_fn_ptr(info);
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Completed,
+        );
+    }
+    #[inline(always)]
+    fn call(_scope: &v8::HandleScope, a: u32, b: u32) -> u32 {
+        a + b
+    }
+}
+
+#[allow(non_camel_case_types)]
+struct op_fast_generic<T> {
+    _unconstructable: ::std::marker::PhantomData<(T)>,
+}
+impl<T: Trait> deno_core::_ops::Op for op_fast_generic<T> {
+    const NAME: &'static str = stringify!(op_fast_generic);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
+        stringify!(op_fast_generic),
+        false,
+        2usize as u8,
+        Self::v8_fn_ptr as _,
+        Self::v8_fn_ptr_metrics as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[Type::V8Value, Type::Uint32, Type::Uint32],
+                CType::Uint32,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[Type::V8Value, Type::Uint32, Type::Uint32, Type::CallbackOptions],
+                CType::Uint32,
+                Self::v8_fn_ptr_fast_metrics as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl<T: Trait> op_fast_generic<T> {
+    pub const fn name() -> &'static str {
+        stringify!(op_fast_generic)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    #[allow(clippy::too_many_arguments)]
+    fn v8_fn_ptr_fast_metrics(
+        this: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: u32,
+        arg1: u32,
+        fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+    ) -> u32 {
+        let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<
+                deno_core::v8::External,
+            >::cast(unsafe { fast_api_callback_options.data.data })
+                .value() as *const deno_core::_ops::OpCtx)
+        };
+        deno_core::_ops::dispatch_metrics_fast(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Dispatched,
+        );
+        let res = Self::v8_fn_ptr_fast(this, arg0, arg1);
+        deno_core::_ops::dispatch_metrics_fast(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Completed,
+        );
+        res
+    }
+    #[allow(clippy::too_many_arguments)]
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: u32,
+        arg1: u32,
+    ) -> u32 {
+        let result = {
+            let arg0 = arg0 as _;
+            let arg1 = arg1 as _;
+            Self::call(arg0, arg1)
+        };
+        result as _
+    }
+    extern "C" fn v8_fn_ptr(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 result = {
+            let arg0 = args.get(0usize as i32);
+            let Some(arg0) = deno_core::_ops::to_u32_option(&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 u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg0 = arg0 as _;
+            let arg1 = args.get(1usize as i32);
+            let Some(arg1) = deno_core::_ops::to_u32_option(&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 u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg1 = arg1 as _;
+            Self::call(arg0, arg1)
+        };
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
+    }
+    extern "C" fn v8_fn_ptr_metrics(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)
+        };
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Dispatched,
+        );
+        Self::v8_fn_ptr(info);
+        deno_core::_ops::dispatch_metrics_slow(
+            &opctx,
+            deno_core::_ops::OpMetricsEvent::Completed,
+        );
+    }
+    #[inline(always)]
+    fn call(a: u32, b: u32) -> u32 {
+        a + b
+    }
+}
diff --git a/ops/op2/test_cases/sync/fast_alternative.rs b/ops/op2/test_cases/sync/fast_alternative.rs
new file mode 100644
index 0000000..7a82e84
--- /dev/null
+++ b/ops/op2/test_cases/sync/fast_alternative.rs
@@ -0,0 +1,29 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+#![deny(warnings)]
+deno_ops_compile_test_runner::prelude!();
+
+use deno_core::v8;
+
+// Unused scope would normally make this a slow-only op
+#[op2(fast(op_fast))]
+fn op_slow(_scope: &v8::HandleScope, a: u32, b: u32) -> u32 {
+  a + b
+}
+
+#[op2(fast)]
+fn op_fast(a: u32, b: u32) -> u32 {
+  a + b
+}
+
+pub trait Trait {}
+
+// Unused scope would normally make this a slow-only op
+#[op2(fast(op_fast_generic::<T>))]
+fn op_slow_generic<T: Trait>(_scope: &v8::HandleScope, a: u32, b: u32) -> u32 {
+  a + b
+}
+
+#[op2(fast)]
+fn op_fast_generic<T: Trait>(a: u32, b: u32) -> u32 {
+  a + b
+}