Repository: denoland/deno_core
Pull request #788 (2024-06-19 18:48:33)

feat: add Option<GcResource> support in ops
Optionals are supported both as arguments, via
`#[cppgc] resource: Option<&T>`, and as return
values, via `Option<T>`.

This commit also adds the `GcResource` trait,
which all CppGc resources must implement. This is
done to ensure that one does not accidentally
create CppGc objects for resources that one did
not intend to be managed by CppGc.



Patch:
diff --git a/core/cppgc.rs b/core/cppgc.rs
index b617606..8b5c002 100644
--- a/core/cppgc.rs
+++ b/core/cppgc.rs
@@ -2,12 +2,14 @@
 
 use std::any::TypeId;
 
-struct CppGcObject<T> {
+pub trait GcResource {}
+
+struct CppGcObject<T: GcResource> {
   tag: TypeId,
   member: T,
 }
 
-impl<T> v8::cppgc::GarbageCollected for CppGcObject<T> {
+impl<T: GcResource> v8::cppgc::GarbageCollected for CppGcObject<T> {
   fn trace(&self, _: &v8::cppgc::Visitor) {}
 }
 
@@ -17,7 +19,7 @@ const EMBEDDER_ID_OFFSET: i32 = 0;
 const SLOT_OFFSET: i32 = 1;
 const FIELD_COUNT: usize = 2;
 
-pub fn make_cppgc_object<'a, T: 'static>(
+pub fn make_cppgc_object<'a, T: GcResource + 'static>(
   scope: &mut v8::HandleScope<'a>,
   t: T,
 ) -> v8::Local<'a, v8::Object> {
@@ -42,7 +44,7 @@ pub fn make_cppgc_object<'a, T: 'static>(
 }
 
 #[allow(clippy::needless_lifetimes)]
-pub fn try_unwrap_cppgc_object<'sc, T: 'static>(
+pub fn try_unwrap_cppgc_object<'sc, T: GcResource + 'static>(
   val: v8::Local<'sc, v8::Value>,
 ) -> Option<&'sc T> {
   let Ok(obj): Result<v8::Local<v8::Object>, _> = val.try_into() else {
diff --git a/core/lib.rs b/core/lib.rs
index a85e74d..465aa71 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -69,6 +69,7 @@ pub use crate::async_cell::RcLike;
 pub use crate::async_cell::RcRef;
 pub use crate::convert::FromV8;
 pub use crate::convert::ToV8;
+pub use crate::cppgc::GcResource;
 pub use crate::error::GetErrorClassFn;
 pub use crate::error::JsErrorCreateFn;
 pub use crate::extensions::Extension;
@@ -174,6 +175,8 @@ pub fn v8_version() -> &'static str {
 /// An internal module re-exporting functions used by the #[op] (`deno_ops`) macro
 #[doc(hidden)]
 pub mod _ops {
+  pub use super::cppgc::make_cppgc_object;
+  pub use super::cppgc::try_unwrap_cppgc_object;
   pub use super::error::throw_type_error;
   pub use super::error_codes::get_error_code;
   pub use super::extensions::Op;
diff --git a/core/ops.rs b/core/ops.rs
index 1fa095f..41ed3e6 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -8,6 +8,7 @@ use crate::ops_metrics::OpMetricsFn;
 use crate::runtime::JsRuntimeState;
 use crate::runtime::OpDriverImpl;
 use crate::FeatureChecker;
+use crate::GcResource;
 use crate::OpDecl;
 use futures::task::AtomicWaker;
 use std::cell::RefCell;
@@ -58,12 +59,12 @@ pub fn reentrancy_check(decl: &'static OpDecl) -> Option<ReentrancyGuard> {
 }
 
 /// A guard for a [`cppgc::Gc`] object that ensures the object is rooted while the guard is alive.
-pub struct CppGcObjectGuard<T: 'static> {
+pub struct CppGcObjectGuard<T: GcResource + 'static> {
   _global: v8::Global<v8::Value>,
   t: &'static T,
 }
 
-impl<T: 'static> CppGcObjectGuard<T> {
+impl<T: GcResource + 'static> CppGcObjectGuard<T> {
   pub fn try_new_from_cppgc_object(
     isolate: &mut Isolate,
     val: v8::Local<v8::Value>,
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 6a1c193..1d22ed7 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -525,6 +525,7 @@ mod tests {
   use crate::op2;
   use crate::runtime::JsRuntimeState;
   use crate::FromV8;
+  use crate::GcResource;
   use crate::JsRuntime;
   use crate::OpState;
   use crate::RuntimeOptions;
@@ -607,7 +608,9 @@ mod tests {
       op_buffer_any_length,
       op_arraybuffer_slice,
       op_test_get_cppgc_resource,
+      op_test_get_cppgc_resource_option,
       op_test_make_cppgc_resource,
+      op_test_make_cppgc_resource_option,
       op_external_make,
       op_external_process,
       op_external_make_ptr,
@@ -1895,27 +1898,63 @@ mod tests {
     pub value: u32,
   }
 
+  impl GcResource for TestResource {}
+
   #[op2]
   #[cppgc]
   pub fn op_test_make_cppgc_resource() -> TestResource {
     TestResource { value: 42 }
   }
 
-  #[op2(fast)]
+  #[op2]
+  #[cppgc]
+  pub fn op_test_make_cppgc_resource_option(
+    create: bool,
+  ) -> Option<TestResource> {
+    if create {
+      Some(TestResource { value: 42 })
+    } else {
+      None
+    }
+  }
+
+  #[op2(async)]
   #[smi]
-  pub fn op_test_get_cppgc_resource(#[cppgc] resource: &TestResource) -> u32 {
+  pub async fn op_test_get_cppgc_resource(
+    #[cppgc] resource: &TestResource,
+  ) -> u32 {
+    tokio::task::yield_now().await;
     resource.value
   }
 
-  #[test]
-  pub fn test_op_cppgc_object() -> Result<(), Box<dyn std::error::Error>> {
-    run_test2(
+  #[op2(fast)]
+  #[smi]
+  pub fn op_test_get_cppgc_resource_option(
+    #[cppgc] resource: Option<&TestResource>,
+  ) -> u32 {
+    if let Some(resource) = resource {
+      resource.value
+    } else {
+      0
+    }
+  }
+
+  #[tokio::test]
+  pub async fn test_op_cppgc_object() -> Result<(), Box<dyn std::error::Error>>
+  {
+    run_async_test(
       10,
-      "op_test_make_cppgc_resource, op_test_get_cppgc_resource",
+      "op_test_make_cppgc_resource, op_test_get_cppgc_resource, op_test_get_cppgc_resource_option, op_test_make_cppgc_resource_option",
       r"
       const resource = op_test_make_cppgc_resource();
-      assert(op_test_get_cppgc_resource(resource) == 42);",
-    )?;
+      assert((await op_test_get_cppgc_resource(resource)) === 42);
+      assert(op_test_get_cppgc_resource_option(resource) === 42);
+      assert(op_test_get_cppgc_resource_option(null) === 0);
+      const resource2 = op_test_make_cppgc_resource_option(false);
+      assert(resource2 === null);
+      const resource3 = op_test_make_cppgc_resource_option(true);
+      assert((await op_test_get_cppgc_resource(resource3)) === 42);",
+    ).await?;
     Ok(())
   }
 
diff --git a/core/runtime/ops_rust_to_v8.rs b/core/runtime/ops_rust_to_v8.rs
index d8bec71..607f3c5 100644
--- a/core/runtime/ops_rust_to_v8.rs
+++ b/core/runtime/ops_rust_to_v8.rs
@@ -161,6 +161,10 @@ impl Marker for NumberMarker {}
 pub struct ArrayBufferMarker;
 impl Marker for ArrayBufferMarker {}
 
+/// This struct should be wrapped with cppgc.
+pub struct CppGcMarker;
+impl Marker for CppGcMarker {}
+
 pub struct ToV8Marker;
 impl Marker for ToV8Marker {}
 
@@ -405,6 +409,21 @@ impl<'a, T: serde::Serialize> RustToV8Fallible<'a>
   }
 }
 
+//
+// CppGc
+//
+
+impl<'a, T: crate::cppgc::GcResource + 'static> RustToV8<'a>
+  for RustToV8Marker<CppGcMarker, T>
+{
+  #[inline(always)]
+  fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> v8::Local<'a, v8::Value> {
+    v8::Local::<v8::Value>::from(deno_core::cppgc::make_cppgc_object(
+      scope, self.0,
+    ))
+  }
+}
+
 impl<'a, T: crate::ToV8<'a>> RustToV8Fallible<'a>
   for RustToV8Marker<ToV8Marker, T>
 {
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 876ddb6..ea0325c 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -454,7 +454,7 @@ pub(crate) fn generate_dispatch_fast(
   let with_self = if generator_state.needs_self {
     generator_state.needs_fast_api_callback_options = true;
     gs_quote!(generator_state(self_ty, fast_api_callback_options) => {
-      let Some(self_) = deno_core::cppgc::try_unwrap_cppgc_object::<#self_ty>(this.into()) else {
+      let Some(self_) = deno_core::_ops::try_unwrap_cppgc_object::<#self_ty>(this.into()) else {
         #fast_api_callback_options.fallback = true;
         // SAFETY: All fast return types have zero as a valid value
         return unsafe { std::mem::zeroed() };
@@ -741,13 +741,30 @@ fn map_v8_fastcall_arg_to_arg(
 
       *needs_fast_api_callback_options = true;
       quote! {
-        let Some(#arg_ident) = deno_core::cppgc::try_unwrap_cppgc_object::<#ty>(#arg_ident) else {
+        let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(#arg_ident) else {
             #fast_api_callback_options.fallback = true;
             // SAFETY: All fast return types have zero as a valid value
             return unsafe { std::mem::zeroed() };
         };
       }
     }
+    Arg::OptionCppGcResource(ty) => {
+      let ty =
+        syn::parse_str::<syn::Path>(ty).expect("Failed to reparse state type");
+
+      *needs_fast_api_callback_options = true;
+      quote! {
+        let #arg_ident = if #arg_ident.is_null_or_undefined() {
+          None
+        } else if let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(#arg_ident) {
+          Some(#arg_ident)
+        } else {
+          #fast_api_callback_options.fallback = true;
+          // SAFETY: All fast return types have zero as a valid value
+          return unsafe { std::mem::zeroed() };
+        };
+      }
+    }
     _ => quote!(let #arg_ident = #arg_ident as _;),
   };
   Ok(res)
@@ -857,6 +874,7 @@ fn map_arg_to_v8_fastcall_type(
     Arg::String(Strings::CowByte) => V8FastCallType::SeqOneByteString,
     Arg::External(..) => V8FastCallType::Pointer,
     Arg::CppGcResource(..) => V8FastCallType::V8Value,
+    Arg::OptionCppGcResource(..) => V8FastCallType::V8Value,
     _ => return Err("a fast argument"),
   };
   Ok(Some(rv))
@@ -900,7 +918,8 @@ fn map_retval_to_v8_fastcall_type(
     | Arg::OptionV8Local(_)
     | Arg::OptionV8Global(_)
     | Arg::OptionV8Ref(..)
-    | Arg::CppGcResource(..) => return Ok(None),
+    | Arg::CppGcResource(..)
+    | Arg::OptionCppGcResource(..) => return Ok(None),
     Arg::Buffer(..) | Arg::OptionBuffer(..) => return Ok(None),
     Arg::External(..) => V8FastCallType::Pointer,
     _ => return Err("a fast return value"),
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 1b6a476..96c52c0 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -263,11 +263,11 @@ pub(crate) fn with_self(
     generator_state.needs_scope = true;
     generator_state
       .idents_that_need_to_be_captured_by_future_and_as_refd
-      .push(format_ident!("self_"));
+      .push((format_ident!("self_"), false));
     tokens
   } else {
     gs_quote!(generator_state(self_ty, fn_args) => {
-      let Some(self_) = deno_core::cppgc::try_unwrap_cppgc_object::<#self_ty>(#fn_args.this().into()) else {
+      let Some(self_) = deno_core::_ops::try_unwrap_cppgc_object::<#self_ty>(#fn_args.this().into()) else {
         #throw_exception;
       };
     })
@@ -581,11 +581,44 @@ pub fn from_arg(
         generator_state.needs_scope = true;
         generator_state
           .idents_that_need_to_be_captured_by_future_and_as_refd
-          .push(arg_ident.clone());
+          .push((arg_ident.clone(), false));
         tokens
       } else {
         quote! {
-          let Some(#arg_ident) = deno_core::cppgc::try_unwrap_cppgc_object::<#ty>(#arg_ident) else {
+          let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(#arg_ident) else {
+            #throw_exception;
+          };
+        }
+      }
+    }
+    Arg::OptionCppGcResource(ty) => {
+      let throw_exception =
+        throw_type_error(generator_state, format!("expected {}", &ty))?;
+      let ty =
+        syn::parse_str::<syn::Path>(ty).expect("Failed to reparse state type");
+      if matches!(ret_val, RetVal::Future(_) | RetVal::FutureResult(_)) {
+        let scope = &generator_state.scope;
+        let tokens = quote! {
+          let #arg_ident = if #arg_ident.is_null_or_undefined() {
+            None
+          } else if let Some(#arg_ident) = deno_core::_ops::CppGcObjectGuard::<#ty>::try_new_from_cppgc_object(&mut *#scope, #arg_ident) else {
+            Some(#arg_ident)
+          } else {
+            #throw_exception;
+          };
+        };
+        generator_state.needs_scope = true;
+        generator_state
+          .idents_that_need_to_be_captured_by_future_and_as_refd
+          .push((arg_ident.clone(), true));
+        tokens
+      } else {
+        quote! {
+          let #arg_ident = if #arg_ident.is_null_or_undefined() {
+            None
+          } else if let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(#arg_ident) {
+            Some(#arg_ident)
+          } else {
             #throw_exception;
           };
         }
@@ -776,10 +809,14 @@ pub fn call(
       .is_empty()
   {
     let mut tokens = TokenStream::new();
-    for ident in
+    for (ident, is_option) in
       &generator_state.idents_that_need_to_be_captured_by_future_and_as_refd
     {
-      tokens.extend(quote!( let #ident = unsafe { #ident.as_ref() }; ));
+      tokens.extend(if *is_option {
+        quote!( let #ident = unsafe { #ident.map(|i| i.as_ref()) }; )
+      } else {
+        quote!( let #ident = unsafe { #ident.as_ref() }; )
+      });
     }
     quote!(async move {
       #tokens
@@ -824,8 +861,12 @@ pub fn return_value_infallible(
       gs_quote!(generator_state(result) => (deno_core::_ops::RustToV8Marker::<deno_core::_ops::NumberMarker, _>::from(#result)))
     }
     ArgMarker::Cppgc => {
-      generator_state.needs_scope = true;
-      gs_quote!(generator_state(scope, result) => (deno_core::v8::Local::<deno_core::v8::Value>::from(deno_core::cppgc::make_cppgc_object(&mut #scope, #result))))
+      let marker = quote!(deno_core::_ops::RustToV8Marker::<deno_core::_ops::CppGcMarker, _>::from);
+      if ret_type.is_option() {
+        gs_quote!(generator_state(result) => (#result.map(#marker)))
+      } else {
+        gs_quote!(generator_state(result) => (#marker(#result)))
+      }
     }
     ArgMarker::ToV8 => {
       gs_quote!(generator_state(result) => (deno_core::_ops::RustToV8Marker::<deno_core::_ops::ToV8Marker, _>::from(#result)))
@@ -893,7 +934,12 @@ pub fn return_value_v8_value(
       quote!(deno_core::_ops::RustToV8Marker::<deno_core::_ops::NumberMarker, _>::from(#result))
     }
     ArgMarker::Cppgc => {
-      quote!(deno_core::cppgc::make_cppgc_object(#scope, #result))
+      let marker = quote!(deno_core::_ops::RustToV8Marker::<deno_core::_ops::CppGcMarker, _>::from);
+      if ret_type.is_option() {
+        quote!(#result.map(#marker))
+      } else {
+        quote!(#marker(#result))
+      }
     }
     ArgMarker::ToV8 => {
       quote!(deno_core::_ops::RustToV8Marker::<deno_core::_ops::ToV8Marker, _>::from(#result))
diff --git a/ops/op2/generator_state.rs b/ops/op2/generator_state.rs
index 6e82898..8816bcc 100644
--- a/ops/op2/generator_state.rs
+++ b/ops/op2/generator_state.rs
@@ -38,8 +38,9 @@ pub struct GeneratorState {
   pub self_ty: Ident,
 
   /// Idents that need to be moved into the future and have a reference taken
-  /// before being passed to the underlying call.
-  pub idents_that_need_to_be_captured_by_future_and_as_refd: Vec<Ident>,
+  /// before being passed to the underlying call. The bool indicates whether
+  /// the data is wrapped in a `Option`
+  pub idents_that_need_to_be_captured_by_future_and_as_refd: Vec<(Ident, bool)>,
 
   pub needs_args: bool,
   pub needs_retval: bool,
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 33e8714..a803614 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -327,7 +327,6 @@ mod tests {
   use pretty_assertions::assert_eq;
   use quote::ToTokens;
   use std::path::PathBuf;
-  use syn::parse_str;
   use syn::File;
   use syn::Item;
 
@@ -355,7 +354,8 @@ deno_ops_compile_test_runner::prelude!();";
       panic!("Source does not start with expected prelude:]n{PRELUDE}");
     }
 
-    let file = parse_str::<File>(&source).expect("Failed to parse Rust file");
+    let file =
+      syn::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 {
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 0af1af1..4187e56 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -279,6 +279,7 @@ pub enum Arg {
   State(RefType, String),
   OptionState(RefType, String),
   CppGcResource(String),
+  OptionCppGcResource(String),
   WasmMemory(RefType, WasmMemorySource),
   OptionWasmMemory(RefType, WasmMemorySource),
   FromV8(String),
@@ -412,6 +413,7 @@ impl Arg {
         | Arg::OptionString(..)
         | Arg::OptionBuffer(..)
         | Arg::OptionState(..)
+        | Arg::OptionCppGcResource(..)
     )
   }
 
@@ -426,6 +428,7 @@ impl Arg {
       Arg::OptionString(t) => Arg::String(*t),
       Arg::OptionBuffer(t, m, s) => Arg::Buffer(*t, *m, *s),
       Arg::OptionState(r, t) => Arg::State(*r, t.clone()),
+      Arg::OptionCppGcResource(t) => Arg::CppGcResource(t.clone()),
       _ => return None,
     })
   }
@@ -479,7 +482,7 @@ impl Arg {
         Arg::OptionBuffer(.., BufferSource::TypedArray) => {
           ArgSlowRetval::V8LocalFalliable
         }
-        Arg::CppGcResource(_) => ArgSlowRetval::V8LocalNoScope,
+        Arg::CppGcResource(_) => ArgSlowRetval::V8Local,
         _ => ArgSlowRetval::None,
       }
     }
@@ -495,7 +498,7 @@ impl Arg {
       Arg::SerdeV8(_) => ArgMarker::Serde,
       Arg::Numeric(NumericArg::__SMI__, _) => ArgMarker::Smi,
       Arg::Numeric(_, NumericFlag::Number) => ArgMarker::Number,
-      Arg::CppGcResource(_) => ArgMarker::Cppgc,
+      Arg::CppGcResource(_) | Arg::OptionCppGcResource(_) => ArgMarker::Cppgc,
       Arg::ToV8(_) => ArgMarker::ToV8,
       _ => ArgMarker::None,
     }
@@ -1473,13 +1476,42 @@ fn parse_cppgc(position: Position, ty: &Type) -> Result<Arg, ArgError> {
     (Position::Arg, Type::Reference(of)) if of.mutability.is_none() => {
       match &*of.elem {
         Type::Path(of) => Ok(Arg::CppGcResource(stringify_token(&of.path))),
-        _ => Err(ArgError::InvalidCppGcType(stringify_token(ty))),
+        _ => Err(ArgError::InvalidCppGcType(stringify_token(&of.elem))),
       }
     }
-    (Position::RetVal, Type::Path(of)) => {
-      Ok(Arg::CppGcResource(stringify_token(&of.path)))
+    (Position::Arg, Type::Path(of)) => {
+      rules!(of.to_token_stream() => {
+        ( Option < $ty:ty $(,)? > ) => {
+          match ty {
+            Type::Reference(of) if of.mutability.is_none() => {
+              match &*of.elem {
+                Type::Path(of) => Ok(Arg::OptionCppGcResource(stringify_token(&of.path))),
+                _ => Err(ArgError::InvalidCppGcType(stringify_token(&of.elem))),
+              }
+            }
+            _ => Err(ArgError::ExpectedCppGcReference(stringify_token(ty))),
+          }
+        }
+        ( $_ty:ty ) => Err(ArgError::ExpectedCppGcReference(stringify_token(ty))),
+      })
+    }
+    (Position::Arg, _) => {
+      Err(ArgError::ExpectedCppGcReference(stringify_token(ty)))
+    }
+    (Position::RetVal, ty) => {
+      rules!(ty.to_token_stream() => {
+        ( Option < $ty:ty $(,)? > ) => {
+          match ty {
+            Type::Path(of) => Ok(Arg::OptionCppGcResource(stringify_token(&of.path))),
+            _ => Err(ArgError::InvalidCppGcType(stringify_token(ty))),
+          }
+        }
+        ( $ty:ty ) => match ty {
+          Type::Path(of) => Ok(Arg::CppGcResource(stringify_token(&of.path))),
+          _ => Err(ArgError::InvalidCppGcType(stringify_token(ty))),
+        },
+      })
     }
-    _ => Err(ArgError::ExpectedCppGcReference(stringify_token(ty))),
   }
 }
 
@@ -2011,6 +2043,21 @@ mod tests {
     ArgError("resource", ExpectedCppGcReference("std::fs::File")),
     fn f(#[cppgc] resource: std::fs::File) {}
   );
+  expect_fail!(
+    op_cppgc_resource_option_owned,
+    ArgError("resource", ExpectedCppGcReference("std::fs::File")),
+    fn f(#[cppgc] resource: Option<std::fs::File>) {}
+  );
+  expect_fail!(
+    op_cppgc_resource_invalid_type,
+    ArgError("resource", InvalidCppGcType("[std :: fs :: File]")),
+    fn f(#[cppgc] resource: &[std::fs::File]) {}
+  );
+  expect_fail!(
+    op_cppgc_resource_option_invalid_type,
+    ArgError("resource", InvalidCppGcType("[std :: fs :: File]")),
+    fn f(#[cppgc] resource: Option<&[std::fs::File]>) {}
+  );
 
   // Args
 


Test patch:
diff --git a/ops/op2/test_cases/async/async_cppgc.out b/ops/op2/test_cases/async/async_cppgc.out
index fc1347b..f5d5d33 100644
--- a/ops/op2/test_cases/async/async_cppgc.out
+++ b/ops/op2/test_cases/async/async_cppgc.out
@@ -52,17 +52,23 @@ const fn op_make_cppgc_object() -> ::deno_core::_ops::OpDecl {
                 result,
                 |scope, result| {
                     Ok(
-                        deno_core::_ops::RustToV8NoScope::to_v8(
-                            deno_core::cppgc::make_cppgc_object(scope, result),
+                        deno_core::_ops::RustToV8::to_v8(
+                            deno_core::_ops::RustToV8Marker::<
+                                deno_core::_ops::CppGcMarker,
+                                _,
+                            >::from(result),
+                            scope,
                         ),
                     )
                 },
             ) {
                 rv.set(
-                    deno_core::_ops::RustToV8NoScope::to_v8(
-                        deno_core::v8::Local::<
-                            deno_core::v8::Value,
-                        >::from(deno_core::cppgc::make_cppgc_object(&mut scope, result)),
+                    deno_core::_ops::RustToV8::to_v8(
+                        deno_core::_ops::RustToV8Marker::<
+                            deno_core::_ops::CppGcMarker,
+                            _,
+                        >::from(result),
+                        &mut scope,
                     ),
                 );
                 return 0;
diff --git a/ops/op2/test_cases/async/async_cppgc.rs b/ops/op2/test_cases/async/async_cppgc.rs
index f44541e..460193f 100644
--- a/ops/op2/test_cases/async/async_cppgc.rs
+++ b/ops/op2/test_cases/async/async_cppgc.rs
@@ -1,9 +1,12 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 #![deny(warnings)]
 deno_ops_compile_test_runner::prelude!();
+use deno_core::GcResource;
 
 struct Wrap;
 
+impl GcResource for Wrap {}
+
 #[op2(async)]
 #[cppgc]
 async fn op_make_cppgc_object() -> Wrap {
diff --git a/ops/op2/test_cases/sync/cppgc_resource.out b/ops/op2/test_cases/sync/cppgc_resource.out
index a910f35..196abce 100644
--- a/ops/op2/test_cases/sync/cppgc_resource.out
+++ b/ops/op2/test_cases/sync/cppgc_resource.out
@@ -1,3 +1,347 @@
+#[allow(non_camel_case_types)]
+const fn op_cppgc_object() -> ::deno_core::_ops::OpDecl {
+    #[allow(non_camel_case_types)]
+    struct op_cppgc_object {
+        _unconstructable: ::std::marker::PhantomData<()>,
+    }
+    impl ::deno_core::_ops::Op for op_cppgc_object {
+        const NAME: &'static str = stringify!(op_cppgc_object);
+        const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
+            ::deno_core::__op_name_fast!(op_cppgc_object),
+            false,
+            false,
+            1usize 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::V8Value, Type::CallbackOptions],
+                    CType::Void,
+                    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::V8Value, Type::CallbackOptions],
+                    CType::Void,
+                    Self::v8_fn_ptr_fast_metrics as *const ::std::ffi::c_void,
+                )
+            }),
+            ::deno_core::OpMetadata {
+                ..::deno_core::OpMetadata::default()
+            },
+        );
+    }
+    impl op_cppgc_object {
+        pub const fn name() -> &'static str {
+            stringify!(op_cppgc_object)
+        }
+        #[allow(clippy::too_many_arguments)]
+        extern "C" fn v8_fn_ptr_fast_metrics<'s>(
+            this: deno_core::v8::Local<deno_core::v8::Object>,
+            arg0: deno_core::v8::Local<deno_core::v8::Value>,
+            fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions<
+                's,
+            >,
+        ) -> () {
+            let fast_api_callback_options: &'s mut _ = unsafe {
+                &mut *fast_api_callback_options
+            };
+            let opctx: &'s _ = 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, fast_api_callback_options);
+            deno_core::_ops::dispatch_metrics_fast(
+                opctx,
+                deno_core::_ops::OpMetricsEvent::Completed,
+            );
+            res
+        }
+        #[allow(clippy::too_many_arguments)]
+        extern "C" fn v8_fn_ptr_fast<'s>(
+            this: deno_core::v8::Local<deno_core::v8::Object>,
+            arg0: deno_core::v8::Local<deno_core::v8::Value>,
+            fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions<
+                's,
+            >,
+        ) -> () {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let fast_api_callback_options: &'s mut _ = unsafe {
+                &mut *fast_api_callback_options
+            };
+            let result = {
+                let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<Wrap>(arg0)
+                else {
+                    fast_api_callback_options.fallback = true;
+                    return unsafe { std::mem::zeroed() };
+                };
+                Self::call(arg0)
+            };
+            result as _
+        }
+        #[inline(always)]
+        fn slow_function_impl<'s>(
+            info: &'s deno_core::v8::FunctionCallbackInfo,
+        ) -> usize {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(info);
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let result = {
+                let arg0 = args.get(0usize as i32);
+                let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<Wrap>(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 Wrap".as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return 1;
+                };
+                Self::call(arg0)
+            };
+            deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv);
+            return 0;
+        }
+        extern "C" fn v8_fn_ptr<'s>(info: *const deno_core::v8::FunctionCallbackInfo) {
+            let info: &'s _ = unsafe { &*info };
+            Self::slow_function_impl(info);
+        }
+        extern "C" fn v8_fn_ptr_metrics<'s>(
+            info: *const deno_core::v8::FunctionCallbackInfo,
+        ) {
+            let info: &'s _ = unsafe { &*info };
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let opctx: &'s _ = 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,
+            );
+            let res = Self::slow_function_impl(info);
+            if res == 0 {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Completed,
+                );
+            } else {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Error,
+                );
+            }
+        }
+    }
+    impl op_cppgc_object {
+        #[inline(always)]
+        fn call(_resource: &Wrap) {}
+    }
+    <op_cppgc_object as ::deno_core::_ops::Op>::DECL
+}
+
+#[allow(non_camel_case_types)]
+const fn op_option_cppgc_object() -> ::deno_core::_ops::OpDecl {
+    #[allow(non_camel_case_types)]
+    struct op_option_cppgc_object {
+        _unconstructable: ::std::marker::PhantomData<()>,
+    }
+    impl ::deno_core::_ops::Op for op_option_cppgc_object {
+        const NAME: &'static str = stringify!(op_option_cppgc_object);
+        const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
+            ::deno_core::__op_name_fast!(op_option_cppgc_object),
+            false,
+            false,
+            1usize 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::V8Value, Type::CallbackOptions],
+                    CType::Void,
+                    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::V8Value, Type::CallbackOptions],
+                    CType::Void,
+                    Self::v8_fn_ptr_fast_metrics as *const ::std::ffi::c_void,
+                )
+            }),
+            ::deno_core::OpMetadata {
+                ..::deno_core::OpMetadata::default()
+            },
+        );
+    }
+    impl op_option_cppgc_object {
+        pub const fn name() -> &'static str {
+            stringify!(op_option_cppgc_object)
+        }
+        #[allow(clippy::too_many_arguments)]
+        extern "C" fn v8_fn_ptr_fast_metrics<'s>(
+            this: deno_core::v8::Local<deno_core::v8::Object>,
+            arg0: deno_core::v8::Local<deno_core::v8::Value>,
+            fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions<
+                's,
+            >,
+        ) -> () {
+            let fast_api_callback_options: &'s mut _ = unsafe {
+                &mut *fast_api_callback_options
+            };
+            let opctx: &'s _ = 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, fast_api_callback_options);
+            deno_core::_ops::dispatch_metrics_fast(
+                opctx,
+                deno_core::_ops::OpMetricsEvent::Completed,
+            );
+            res
+        }
+        #[allow(clippy::too_many_arguments)]
+        extern "C" fn v8_fn_ptr_fast<'s>(
+            this: deno_core::v8::Local<deno_core::v8::Object>,
+            arg0: deno_core::v8::Local<deno_core::v8::Value>,
+            fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions<
+                's,
+            >,
+        ) -> () {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let fast_api_callback_options: &'s mut _ = unsafe {
+                &mut *fast_api_callback_options
+            };
+            let result = {
+                let arg0 = if arg0.is_null_or_undefined() {
+                    None
+                } else if let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<
+                    Wrap,
+                >(arg0) {
+                    Some(arg0)
+                } else {
+                    fast_api_callback_options.fallback = true;
+                    return unsafe { std::mem::zeroed() };
+                };
+                Self::call(arg0)
+            };
+            result as _
+        }
+        #[inline(always)]
+        fn slow_function_impl<'s>(
+            info: &'s deno_core::v8::FunctionCallbackInfo,
+        ) -> usize {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(info);
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let result = {
+                let arg0 = args.get(0usize as i32);
+                let arg0 = if arg0.is_null_or_undefined() {
+                    None
+                } else if let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<
+                    Wrap,
+                >(arg0) {
+                    Some(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 Wrap".as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return 1;
+                };
+                Self::call(arg0)
+            };
+            deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv);
+            return 0;
+        }
+        extern "C" fn v8_fn_ptr<'s>(info: *const deno_core::v8::FunctionCallbackInfo) {
+            let info: &'s _ = unsafe { &*info };
+            Self::slow_function_impl(info);
+        }
+        extern "C" fn v8_fn_ptr_metrics<'s>(
+            info: *const deno_core::v8::FunctionCallbackInfo,
+        ) {
+            let info: &'s _ = unsafe { &*info };
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let opctx: &'s _ = 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,
+            );
+            let res = Self::slow_function_impl(info);
+            if res == 0 {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Completed,
+                );
+            } else {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Error,
+                );
+            }
+        }
+    }
+    impl op_option_cppgc_object {
+        #[inline(always)]
+        fn call(_resource: Option<&Wrap>) {}
+    }
+    <op_option_cppgc_object as ::deno_core::_ops::Op>::DECL
+}
+
 #[allow(non_camel_case_types)]
 const fn op_make_cppgc_object() -> ::deno_core::_ops::OpDecl {
     #[allow(non_camel_case_types)]
@@ -36,10 +380,12 @@ const fn op_make_cppgc_object() -> ::deno_core::_ops::OpDecl {
             let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(info);
             let result = { Self::call() };
             rv.set(
-                deno_core::_ops::RustToV8NoScope::to_v8(
-                    deno_core::v8::Local::<
-                        deno_core::v8::Value,
-                    >::from(deno_core::cppgc::make_cppgc_object(&mut scope, result)),
+                deno_core::_ops::RustToV8::to_v8(
+                    deno_core::_ops::RustToV8Marker::<
+                        deno_core::_ops::CppGcMarker,
+                        _,
+                    >::from(result),
+                    &mut scope,
                 ),
             );
             return 0;
@@ -172,7 +518,7 @@ const fn op_use_cppgc_object() -> ::deno_core::_ops::OpDecl {
                 &mut *fast_api_callback_options
             };
             let result = {
-                let Some(arg0) = deno_core::cppgc::try_unwrap_cppgc_object::<Wrap>(arg0)
+                let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<Wrap>(arg0)
                 else {
                     fast_api_callback_options.fallback = true;
                     return unsafe { std::mem::zeroed() };
@@ -195,7 +541,7 @@ const fn op_use_cppgc_object() -> ::deno_core::_ops::OpDecl {
             );
             let result = {
                 let arg0 = args.get(0usize as i32);
-                let Some(arg0) = deno_core::cppgc::try_unwrap_cppgc_object::<Wrap>(arg0)
+                let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<Wrap>(arg0)
                 else {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
@@ -252,3 +598,263 @@ const fn op_use_cppgc_object() -> ::deno_core::_ops::OpDecl {
     }
     <op_use_cppgc_object as ::deno_core::_ops::Op>::DECL
 }
+
+#[allow(non_camel_case_types)]
+const fn op_make_cppgc_object_option() -> ::deno_core::_ops::OpDecl {
+    #[allow(non_camel_case_types)]
+    struct op_make_cppgc_object_option {
+        _unconstructable: ::std::marker::PhantomData<()>,
+    }
+    impl ::deno_core::_ops::Op for op_make_cppgc_object_option {
+        const NAME: &'static str = stringify!(op_make_cppgc_object_option);
+        const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
+            ::deno_core::__op_name_fast!(op_make_cppgc_object_option),
+            false,
+            false,
+            0usize as u8,
+            Self::v8_fn_ptr as _,
+            Self::v8_fn_ptr_metrics as _,
+            None,
+            None,
+            ::deno_core::OpMetadata {
+                ..::deno_core::OpMetadata::default()
+            },
+        );
+    }
+    impl op_make_cppgc_object_option {
+        pub const fn name() -> &'static str {
+            stringify!(op_make_cppgc_object_option)
+        }
+        #[inline(always)]
+        fn slow_function_impl<'s>(
+            info: &'s deno_core::v8::FunctionCallbackInfo,
+        ) -> usize {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let mut scope = unsafe { deno_core::v8::CallbackScope::new(info) };
+            let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(info);
+            let result = { Self::call() };
+            rv.set(
+                deno_core::_ops::RustToV8::to_v8(
+                    result
+                        .map(
+                            deno_core::_ops::RustToV8Marker::<
+                                deno_core::_ops::CppGcMarker,
+                                _,
+                            >::from,
+                        ),
+                    &mut scope,
+                ),
+            );
+            return 0;
+        }
+        extern "C" fn v8_fn_ptr<'s>(info: *const deno_core::v8::FunctionCallbackInfo) {
+            let info: &'s _ = unsafe { &*info };
+            Self::slow_function_impl(info);
+        }
+        extern "C" fn v8_fn_ptr_metrics<'s>(
+            info: *const deno_core::v8::FunctionCallbackInfo,
+        ) {
+            let info: &'s _ = unsafe { &*info };
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let opctx: &'s _ = 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,
+            );
+            let res = Self::slow_function_impl(info);
+            if res == 0 {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Completed,
+                );
+            } else {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Error,
+                );
+            }
+        }
+    }
+    impl op_make_cppgc_object_option {
+        #[inline(always)]
+        fn call() -> Option<Wrap> {
+            Some(Wrap)
+        }
+    }
+    <op_make_cppgc_object_option as ::deno_core::_ops::Op>::DECL
+}
+
+#[allow(non_camel_case_types)]
+const fn op_use_cppgc_object_option() -> ::deno_core::_ops::OpDecl {
+    #[allow(non_camel_case_types)]
+    struct op_use_cppgc_object_option {
+        _unconstructable: ::std::marker::PhantomData<()>,
+    }
+    impl ::deno_core::_ops::Op for op_use_cppgc_object_option {
+        const NAME: &'static str = stringify!(op_use_cppgc_object_option);
+        const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2(
+            ::deno_core::__op_name_fast!(op_use_cppgc_object_option),
+            false,
+            false,
+            1usize 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::V8Value, Type::CallbackOptions],
+                    CType::Void,
+                    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::V8Value, Type::CallbackOptions],
+                    CType::Void,
+                    Self::v8_fn_ptr_fast_metrics as *const ::std::ffi::c_void,
+                )
+            }),
+            ::deno_core::OpMetadata {
+                ..::deno_core::OpMetadata::default()
+            },
+        );
+    }
+    impl op_use_cppgc_object_option {
+        pub const fn name() -> &'static str {
+            stringify!(op_use_cppgc_object_option)
+        }
+        #[allow(clippy::too_many_arguments)]
+        extern "C" fn v8_fn_ptr_fast_metrics<'s>(
+            this: deno_core::v8::Local<deno_core::v8::Object>,
+            arg0: deno_core::v8::Local<deno_core::v8::Value>,
+            fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions<
+                's,
+            >,
+        ) -> () {
+            let fast_api_callback_options: &'s mut _ = unsafe {
+                &mut *fast_api_callback_options
+            };
+            let opctx: &'s _ = 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, fast_api_callback_options);
+            deno_core::_ops::dispatch_metrics_fast(
+                opctx,
+                deno_core::_ops::OpMetricsEvent::Completed,
+            );
+            res
+        }
+        #[allow(clippy::too_many_arguments)]
+        extern "C" fn v8_fn_ptr_fast<'s>(
+            this: deno_core::v8::Local<deno_core::v8::Object>,
+            arg0: deno_core::v8::Local<deno_core::v8::Value>,
+            fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions<
+                's,
+            >,
+        ) -> () {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let fast_api_callback_options: &'s mut _ = unsafe {
+                &mut *fast_api_callback_options
+            };
+            let result = {
+                let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<Wrap>(arg0)
+                else {
+                    fast_api_callback_options.fallback = true;
+                    return unsafe { std::mem::zeroed() };
+                };
+                Self::call(arg0)
+            };
+            result as _
+        }
+        #[inline(always)]
+        fn slow_function_impl<'s>(
+            info: &'s deno_core::v8::FunctionCallbackInfo,
+        ) -> usize {
+            #[cfg(debug_assertions)]
+            let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+                &<Self as deno_core::_ops::Op>::DECL,
+            );
+            let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(info);
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let result = {
+                let arg0 = args.get(0usize as i32);
+                let Some(arg0) = deno_core::_ops::try_unwrap_cppgc_object::<Wrap>(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 Wrap".as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::type_error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return 1;
+                };
+                Self::call(arg0)
+            };
+            deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv);
+            return 0;
+        }
+        extern "C" fn v8_fn_ptr<'s>(info: *const deno_core::v8::FunctionCallbackInfo) {
+            let info: &'s _ = unsafe { &*info };
+            Self::slow_function_impl(info);
+        }
+        extern "C" fn v8_fn_ptr_metrics<'s>(
+            info: *const deno_core::v8::FunctionCallbackInfo,
+        ) {
+            let info: &'s _ = unsafe { &*info };
+            let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(
+                info,
+            );
+            let opctx: &'s _ = 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,
+            );
+            let res = Self::slow_function_impl(info);
+            if res == 0 {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Completed,
+                );
+            } else {
+                deno_core::_ops::dispatch_metrics_slow(
+                    opctx,
+                    deno_core::_ops::OpMetricsEvent::Error,
+                );
+            }
+        }
+    }
+    impl op_use_cppgc_object_option {
+        #[inline(always)]
+        fn call(_wrap: &Wrap) {}
+    }
+    <op_use_cppgc_object_option as ::deno_core::_ops::Op>::DECL
+}
diff --git a/ops/op2/test_cases/sync/cppgc_resource.rs b/ops/op2/test_cases/sync/cppgc_resource.rs
index 43aa4b2..d2fa98c 100644
--- a/ops/op2/test_cases/sync/cppgc_resource.rs
+++ b/ops/op2/test_cases/sync/cppgc_resource.rs
@@ -1,9 +1,18 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 #![deny(warnings)]
 deno_ops_compile_test_runner::prelude!();
+use deno_core::GcResource;
 
 struct Wrap;
 
+impl GcResource for Wrap {}
+
+#[op2(fast)]
+fn op_cppgc_object(#[cppgc] _resource: &Wrap) {}
+
+#[op2(fast)]
+fn op_option_cppgc_object(#[cppgc] _resource: Option<&Wrap>) {}
+
 #[op2]
 #[cppgc]
 fn op_make_cppgc_object() -> Wrap {
@@ -12,3 +21,12 @@ fn op_make_cppgc_object() -> Wrap {
 
 #[op2(fast)]
 fn op_use_cppgc_object(#[cppgc] _wrap: &Wrap) {}
+
+#[op2]
+#[cppgc]
+fn op_make_cppgc_object_option() -> Option<Wrap> {
+    Some(Wrap)
+}
+
+#[op2(fast)]
+fn op_use_cppgc_object_option(#[cppgc] _wrap: &Wrap)  {}
diff --git a/ops/op2/test_cases_fail/lifetimes.rs b/ops/op2/test_cases_fail/lifetimes.rs
index 83e4b94..bafad16 100644
--- a/ops/op2/test_cases_fail/lifetimes.rs
+++ b/ops/op2/test_cases_fail/lifetimes.rs
@@ -2,10 +2,13 @@
 #![deny(warnings)]
 deno_ops_compile_test_runner::prelude!();
 use deno_core::error::AnyError;
+use deno_core::GcResource;
 use std::future::Future;
 
 struct Wrap;
 
+impl GcResource for Wrap {}
+
 #[op2(fast)]
 fn op_use_cppgc_object(#[cppgc] _wrap: &'static Wrap) {}
 
diff --git a/ops/op2/test_cases_fail/lifetimes.stderr b/ops/op2/test_cases_fail/lifetimes.stderr
index 6905e1a..89fd320 100644
--- a/ops/op2/test_cases_fail/lifetimes.stderr
+++ b/ops/op2/test_cases_fail/lifetimes.stderr
@@ -12,41 +12,41 @@ note: the lint level is defined here
   = note: `#[deny(unused_imports)]` implied by `#[deny(warnings)]`
 
 error: unused import: `std::future::Future`
- --> ../op2/test_cases_fail/lifetimes.rs:5:5
+ --> ../op2/test_cases_fail/lifetimes.rs:6:5
   |
-5 | use std::future::Future;
+6 | use std::future::Future;
   |     ^^^^^^^^^^^^^^^^^^^
 
 error[E0521]: borrowed data escapes outside of associated function
- --> ../op2/test_cases_fail/lifetimes.rs:9:1
-  |
-9 | #[op2(fast)]
-  | ^^^^^^^^^^^^
-  | |
-  | `arg0` is a reference that is only valid in the associated function body
-  | `arg0` escapes the associated function body here
-  | has type `Local<'1, deno_core::v8::Value>`
-  | argument requires that `'1` must outlive `'static`
-  |
-  = note: this error originates in the attribute macro `op2` (in Nightly builds, run with -Z macro-backtrace for more info)
+  --> ../op2/test_cases_fail/lifetimes.rs:12:1
+   |
+12 | #[op2(fast)]
+   | ^^^^^^^^^^^^
+   | |
+   | `arg0` is a reference that is only valid in the associated function body
+   | `arg0` escapes the associated function body here
+   | has type `Local<'1, deno_core::v8::Value>`
+   | argument requires that `'1` must outlive `'static`
+   |
+   = note: this error originates in the attribute macro `op2` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0521]: borrowed data escapes outside of associated function
- --> ../op2/test_cases_fail/lifetimes.rs:9:1
-  |
-9 | #[op2(fast)]
-  | ^^^^^^^^^^^^
-  | |
-  | `info` is a reference that is only valid in the associated function body
-  | `info` escapes the associated function body here
-  | lifetime `'s` defined here
-  | argument requires that `'s` must outlive `'static`
-  |
-  = note: this error originates in the attribute macro `op2` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error[E0597]: `arg0_temp` does not live long enough
   --> ../op2/test_cases_fail/lifetimes.rs:12:1
    |
 12 | #[op2(fast)]
+   | ^^^^^^^^^^^^
+   | |
+   | `info` is a reference that is only valid in the associated function body
+   | `info` escapes the associated function body here
+   | lifetime `'s` defined here
+   | argument requires that `'s` must outlive `'static`
+   |
+   = note: this error originates in the attribute macro `op2` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0597]: `arg0_temp` does not live long enough
+  --> ../op2/test_cases_fail/lifetimes.rs:15:1
+   |
+15 | #[op2(fast)]
    | ^^^^^^^^^^^-
    | |          |
    | |          `arg0_temp` dropped here while still borrowed
diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs
index ef5b0e1..631ad9a 100644
--- a/testing/checkin/runner/ops.rs
+++ b/testing/checkin/runner/ops.rs
@@ -8,6 +8,7 @@ use deno_core::stats::RuntimeActivitySnapshot;
 use deno_core::stats::RuntimeActivityStats;
 use deno_core::stats::RuntimeActivityStatsFactory;
 use deno_core::stats::RuntimeActivityStatsFilter;
+use deno_core::GcResource;
 use deno_core::OpDecl;
 use deno_core::OpState;
 
@@ -72,6 +73,8 @@ pub struct Stateful {
   name: String,
 }
 
+impl GcResource for Stateful {}
+
 impl Stateful {
   #[op2(method(Stateful))]
   #[string]
diff --git a/testing/checkin/runner/ops_async.rs b/testing/checkin/runner/ops_async.rs
index 8d21a1f..98dd8a0 100644
--- a/testing/checkin/runner/ops_async.rs
+++ b/testing/checkin/runner/ops_async.rs
@@ -1,6 +1,7 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 use deno_core::op2;
 use deno_core::v8;
+use deno_core::GcResource;
 use deno_core::OpState;
 use deno_core::V8TaskSpawner;
 use futures::future::poll_fn;
@@ -65,6 +66,8 @@ pub struct TestResource {
   value: u32,
 }
 
+impl GcResource for TestResource {}
+
 #[op2(async)]
 #[cppgc]
 pub async fn op_async_make_cppgc_resource() -> TestResource {
diff --git a/testing/checkin/runner/ops_worker.rs b/testing/checkin/runner/ops_worker.rs
index 9bf2f01..9b6b655 100644
--- a/testing/checkin/runner/ops_worker.rs
+++ b/testing/checkin/runner/ops_worker.rs
@@ -2,11 +2,11 @@
 use anyhow::anyhow;
 use anyhow::bail;
 use anyhow::Error;
-use deno_core::cppgc;
 use deno_core::op2;
 use deno_core::url::Url;
 use deno_core::v8;
 use deno_core::v8::IsolateHandle;
+use deno_core::GcResource;
 use deno_core::JsRuntime;
 use deno_core::OpState;
 use deno_core::PollEventLoopOptions;
@@ -34,6 +34,8 @@ pub struct WorkerControl {
   shutdown_flag: Option<UnboundedSender<()>>,
 }
 
+impl GcResource for WorkerControl {}
+
 pub struct WorkerChannel {
   tx: UnboundedSender<String>,
   rx: Mutex<UnboundedReceiver<String>>,
@@ -117,7 +119,7 @@ pub fn op_worker_spawn<'s>(
 
   // This is technically a blocking call
   let worker = init_recv.recv()?;
-  Ok(cppgc::make_cppgc_object(scope, worker))
+  Ok(deno_core::cppgc::make_cppgc_object(scope, worker))
 }
 
 async fn run_worker_task(
@@ -181,7 +183,7 @@ pub fn op_worker_parent<'s>(
   ) else {
     bail!("No parent worker is available")
   };
-  Ok(cppgc::make_cppgc_object(
+  Ok(deno_core::cppgc::make_cppgc_object(
     scope,
     WorkerControl {
       worker_channel,