Repository: denoland/deno_core
Pull request #165 (2023-08-29 18:35:48)

feat(ops): move type conversion handling out of proc macro
Instead of special-casing each type of return value, we instead create a number of traits to handle creation of a `v8::Local` and population of a `v8::ReturnValue`. This allows us to implement the Rust -> v8 logic in a single file and remove brittle code from proc macros.

This is a net increase in code, but has the following benefits:

 - Conversion code supports Rust auto-completion which makes it far easier to work on.
 - Failure to allocate a string or arraybuffer will no longer panic, but instead throw a proper exception
 - We add support for the following return values in sync and async methods:
   - Cow<str>
   - Cow<[u8]>
   - &str
   - v8::Global



Patch:
diff --git a/core/lib.rs b/core/lib.rs
index 6247608..39fa0ad 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -144,6 +144,7 @@ pub mod _ops {
   pub use super::ops::OpCtx;
   pub use super::ops::OpResult;
   pub use super::runtime::ops::*;
+  pub use super::runtime::ops_rust_to_v8::*;
   pub use super::runtime::V8_WRAPPER_OBJECT_INDEX;
   pub use super::runtime::V8_WRAPPER_TYPE_INDEX;
 }
diff --git a/core/runtime/mod.rs b/core/runtime/mod.rs
index 8cdd189..43fdd2f 100644
--- a/core/runtime/mod.rs
+++ b/core/runtime/mod.rs
@@ -4,6 +4,7 @@ mod jsrealm;
 mod jsruntime;
 #[doc(hidden)]
 pub mod ops;
+pub mod ops_rust_to_v8;
 mod snapshot_util;
 
 #[cfg(test)]
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index ae4e17b..8a8a37c 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -3,24 +3,18 @@ use crate::ops::*;
 use crate::OpResult;
 use crate::PromiseId;
 use anyhow::Error;
-use bytes::BytesMut;
 use futures::future::Either;
 use futures::future::Future;
 use futures::future::FutureExt;
 use futures::task::noop_waker_ref;
 use serde::Deserialize;
-use serde::Serialize;
 use serde_v8::from_v8;
-use serde_v8::to_v8;
-use serde_v8::JsBuffer;
-use serde_v8::V8Slice;
 use std::borrow::Cow;
 use std::cell::RefCell;
 use std::ffi::c_void;
 use std::future::ready;
 use std::mem::MaybeUninit;
 use std::option::Option;
-use std::rc::Rc;
 use std::task::Context;
 use std::task::Poll;
 use v8::WriteOptions;
@@ -519,13 +513,6 @@ where
   }
 }
 
-pub fn serde_rust_to_v8<'a, T: Serialize>(
-  scope: &mut v8::HandleScope<'a>,
-  input: T,
-) -> serde_v8::Result<v8::Local<'a, v8::Value>> {
-  to_v8(scope, input)
-}
-
 pub fn serde_v8_to_rust<'a, T: Deserialize<'a>>(
   scope: &mut v8::HandleScope,
   input: v8::Local<v8::Value>,
@@ -594,91 +581,6 @@ where
   Ok(slice)
 }
 
-pub trait ToV8Value {
-  /// Consume and convert this object into a [`v8::Value`]. This is similar to what serde_v8 does, but a
-  /// dedicated API for ops to use.
-  fn to_v8_value<'a>(
-    self,
-    scope: &mut v8::HandleScope<'a>,
-  ) -> v8::Local<'a, v8::Value>;
-}
-
-impl ToV8Value for V8Slice {
-  fn to_v8_value<'a>(
-    self,
-    scope: &mut v8::HandleScope<'a>,
-  ) -> v8::Local<'a, v8::Value> {
-    let (buffer, range) = self.into_parts();
-    let buffer = v8::ArrayBuffer::with_backing_store(scope, &buffer);
-    let array =
-      v8::Uint8Array::new(scope, buffer, range.start, range.len()).unwrap();
-    array.into()
-  }
-}
-
-impl ToV8Value for JsBuffer {
-  fn to_v8_value<'a>(
-    self,
-    scope: &mut v8::HandleScope<'a>,
-  ) -> v8::Local<'a, v8::Value> {
-    self.into_parts().to_v8_value(scope)
-  }
-}
-
-impl ToV8Value for Box<[u8]> {
-  fn to_v8_value<'a>(
-    self,
-    scope: &mut v8::HandleScope<'a>,
-  ) -> v8::Local<'a, v8::Value> {
-    let buf = self;
-    if buf.is_empty() {
-      let ab = v8::ArrayBuffer::new(scope, 0);
-      return v8::Uint8Array::new(scope, ab, 0, 0).unwrap().into();
-    }
-    let buf_len: usize = buf.len();
-    let backing_store =
-      v8::ArrayBuffer::new_backing_store_from_boxed_slice(buf);
-    let backing_store_shared = backing_store.make_shared();
-    let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
-    v8::Uint8Array::new(scope, ab, 0, buf_len).unwrap().into()
-  }
-}
-
-impl ToV8Value for Vec<u8> {
-  fn to_v8_value<'a>(
-    self,
-    scope: &mut v8::HandleScope<'a>,
-  ) -> v8::Local<'a, v8::Value> {
-    self.into_boxed_slice().to_v8_value(scope)
-  }
-}
-
-impl ToV8Value for BytesMut {
-  fn to_v8_value<'a>(
-    self,
-    scope: &mut v8::HandleScope<'a>,
-  ) -> v8::Local<'a, v8::Value> {
-    let ptr = self.as_ptr();
-    let len = self.len() as _;
-    let rc = Rc::into_raw(Rc::new(self)) as *const c_void;
-
-    extern "C" fn drop_rc(_ptr: *mut c_void, _len: usize, data: *mut c_void) {
-      // SAFETY: We know that data is a raw Rc from above
-      unsafe { drop(Rc::<BytesMut>::from_raw(data as _)) }
-    }
-
-    // SAFETY: We are using the BytesMut backing store here
-    let backing_store_shared = unsafe {
-      v8::ArrayBuffer::new_backing_store_from_ptr(
-        ptr as _, len, drop_rc, rc as _,
-      )
-    }
-    .make_shared();
-    let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
-    v8::Uint8Array::new(scope, ab, 0, len).unwrap().into()
-  }
-}
-
 #[cfg(test)]
 mod tests {
   use crate::error::generic_error;
diff --git a/core/runtime/ops_rust_to_v8.rs b/core/runtime/ops_rust_to_v8.rs
new file mode 100644
index 0000000..10bf367
--- /dev/null
+++ b/core/runtime/ops_rust_to_v8.rs
@@ -0,0 +1,417 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+//! This module provides traits and macros that facilitate the conversion of Rust objects into v8 objects.
+//!
+//! The traits combine various behaviors, including converting values into v8 objects, efficiently placing them
+//! into a `v8::ReturnValue`, and handling potential conversion failures.
+//!
+//! These traits are a result of the intersection of two factors: whether the conversion can be directly stored
+//! in a return value and whether the conversion process can fail.
+//!
+//! For instance, the `RustToV8` and `RustToV8RetVal` traits respectively indicate the ability to convert a Rust value
+//! into a v8 object, and to store it in a `v8::ReturnValue`. Similarly, the `RustToV8Fallible` and `RustToV8RetValFallible`
+//! traits manage the same conversions that might encounter errors.
+//!
+//! To illustrate, consider the conversion of a `u32` value. It can be cheaply placed in a `v8::ReturnValue`
+//! without any conversion risk. Conversely, storing a string directly in a `v8::ReturnValue` (without using
+//! the `set(v8::Local<v8::Value>)` function) is not possible. Moreover, converting a Rust string to a `v8::String`
+//! might fail, particularly if its size surpasses v8's limitations.
+//!
+//! The `#[op2]` proc macro maps the distinct calling patterns to the appropriate trait interfaces.
+//! This mapping accounts for conversion nature and potential error occurrences. When a conversion is inherently
+//! expected to succeed, `#[op2]` negates the necessity for explicit error checks, potentially boosting execution
+//! efficiency. Furthermore, `#[op2]` strives to leverage `v8::ReturnValue` setters when possible, eliminating
+//! the need for explicit object handle allocation. This optimization can favorably impact performance and
+//! memory management.
+
+use bytes::BytesMut;
+use libc::c_void;
+use std::borrow::Cow;
+use std::marker::PhantomData;
+use std::rc::Rc;
+
+/// Convert a value to a `v8::Local`, potentially allocating.
+pub trait RustToV8<'a> {
+  fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> v8::Local<'a, v8::Value>;
+}
+
+/// Convert a value to a `v8::Local`, not allocating. This is generally not used for
+/// anything other than `v8::Local`s themselves, so there is no additional macro support.
+pub trait RustToV8NoScope<'a> {
+  fn to_v8(self) -> v8::Local<'a, v8::Value>;
+}
+
+/// Places a value in a `v8::ReturnValue`, non-allocating.
+pub trait RustToV8RetVal<'a>: RustToV8<'a> {
+  fn to_v8_rv(self, rv: &mut v8::ReturnValue<'a>);
+}
+
+/// Convert a value to a `v8::Local`, potentially allocating or failing.
+pub trait RustToV8Fallible<'a> {
+  fn to_v8_fallible(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+  ) -> serde_v8::Result<v8::Local<'a, v8::Value>>;
+}
+
+/// Places a value in a `v8::ReturnValue`, potentially allocating or failing.
+pub trait RustToV8RetValFallible<'a>: RustToV8Fallible<'a> {
+  fn to_v8_rv_fallible(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+    rv: &mut v8::ReturnValue<'a>,
+  ) -> serde_v8::Result<()>;
+}
+
+/// Implement [`RustToV8`] for an `Option` of [`RustToV8`].
+impl<'a, T> RustToV8<'a> for Option<T>
+where
+  T: RustToV8<'a>,
+{
+  #[inline(always)]
+  fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> v8::Local<'a, v8::Value> {
+    if let Some(value) = self {
+      value.to_v8(scope)
+    } else {
+      v8::null(scope).into()
+    }
+  }
+}
+
+/// Implement [`RustToV8RetVal`] for an `Option` of [`RustToV8RetVal`].
+impl<'a, T> RustToV8RetVal<'a> for Option<T>
+where
+  T: RustToV8RetVal<'a>,
+{
+  #[inline(always)]
+  fn to_v8_rv(self, rv: &mut v8::ReturnValue<'a>) {
+    if let Some(value) = self {
+      value.to_v8_rv(rv)
+    } else {
+      rv.set_null()
+    }
+  }
+}
+
+/// Implement [`RustToV8Fallible`] for an `Option` of [`RustToV8Fallible`].
+impl<'a, T> RustToV8Fallible<'a> for Option<T>
+where
+  T: RustToV8Fallible<'a>,
+{
+  #[inline(always)]
+  fn to_v8_fallible(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+  ) -> serde_v8::Result<v8::Local<'a, v8::Value>> {
+    if let Some(value) = self {
+      value.to_v8_fallible(scope)
+    } else {
+      Ok(v8::null(scope).into())
+    }
+  }
+}
+
+/// Implement [`RustToV8RetValFallible`] for an `Option` of [`RustToV8RetValFallible`].
+impl<'a, T> RustToV8RetValFallible<'a> for Option<T>
+where
+  T: RustToV8RetValFallible<'a>,
+{
+  #[inline(always)]
+  fn to_v8_rv_fallible(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+    rv: &mut v8::ReturnValue<'a>,
+  ) -> serde_v8::Result<()> {
+    if let Some(value) = self {
+      value.to_v8_rv_fallible(scope, rv)
+    } else {
+      rv.set_null();
+      Ok(())
+    }
+  }
+}
+
+/// Transparent marker struct that can be used to alter the conversion in various ways. The
+/// main uses of this are to mark something as being `serde`-serializable. This may also be
+/// useful to alter the serialization of various types. For example, a `NumberMarker` and
+/// `BigIntMarker` could control the serialization of `i64`/`u64`/`f64` as `v8::Number` or
+/// `v8::BigInt` objects.
+#[repr(transparent)]
+pub struct RustToV8Marker<M, T>(T, PhantomData<M>);
+impl<M, T> From<T> for RustToV8Marker<M, T> {
+  fn from(value: T) -> Self {
+    RustToV8Marker(value, PhantomData)
+  }
+}
+
+/// This struct should be serialized using `serde`.
+pub struct SerdeMarker;
+
+/// Helper macro for [`RustToV8`] to reduce boilerplate.
+///
+/// Implements a Rust-to-v8 conversion that cannot fail. Multiple types may be specified
+/// to apply the same implementation.
+///
+/// ```no_compile
+/// to_v8!(bool: |value, scope| v8::Boolean::new(scope, value as _));
+/// ```
+macro_rules! to_v8 {
+  (( $( $ty:ty ),+ ) : |$value:ident, $scope:ident| $block:expr) => {
+    $(
+      impl <'a> RustToV8<'a> for $ty {
+        #[inline(always)]
+        fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> v8::Local<'a, v8::Value> {
+          let $value = self;
+          let $scope = scope;
+          v8::Local::<v8::Value>::from($block)
+        }
+      }
+    )+
+  };
+  ($ty:ty : |$value:ident, $scope:ident| $block:expr) => {
+    to_v8!(( $ty ) : |$value, $scope| $block);
+  };
+}
+
+/// Helper macro for [`RustToV8RetVal`] to reduce boilerplate.
+///
+/// Implements a Rust-to-v8 conversion that cannot allocate or fail. Multiple types may be specified
+/// to apply the same implementation. Places the return value in a `v8::ReturnValue`.
+macro_rules! to_v8_retval {
+  (( $( $ty:ty ),+ ) : |$value:ident, $rv:ident| $block:expr) => {
+    $(
+      impl <'a> RustToV8RetVal<'a> for $ty {
+        #[inline(always)]
+        fn to_v8_rv(self, rv: &mut v8::ReturnValue<'a>) {
+          let $value = self;
+          let $rv = rv;
+          $block
+        }
+      }
+    )+
+  };
+  ($ty:ty : |$rv:ident, $scope:ident| $block:expr) => {
+    to_v8_retval!(( $ty ) : |$rv, $scope| $block);
+  };
+}
+
+/// Helper macro for [`RustToV8Fallible`] to reduce boilerplate.
+///
+/// Implements a Rust-to-v8 conversion that can fail. Multiple types may be specified
+/// to apply the same implementation.
+///
+/// ```no_compile
+/// to_v8_fallible!(serde_v8::V8Slice: |value, scope| {
+///   // Implementation
+///   Ok(result)
+/// });
+/// ```
+macro_rules! to_v8_fallible {
+  (( $( $ty:ty ),+ ) : |$value:ident, $scope:ident| $block:expr) => {
+    $(
+      #[allow(clippy::needless_borrow)]
+      impl <'a> RustToV8Fallible<'a> for $ty {
+        #[inline(always)]
+        fn to_v8_fallible(self, scope: &mut v8::HandleScope<'a>) -> serde_v8::Result<v8::Local<'a, v8::Value>> {
+          let $value = self;
+          let $scope = scope;
+          let res = $block;
+          match res {
+            Ok(v) => Ok(v.into()),
+            Err(err) => Err(err),
+          }
+        }
+      }
+    )+
+  };
+  ($ty:ty : |$value:ident, $scope:ident| $block:expr) => {
+    to_v8_fallible!(( $ty ) : |$value, $scope| $block);
+  };
+}
+
+/// Helper macro for [`RustToV8RetValFallible`] to reduce boilerplate.
+///
+/// Implements a Rust-to-v8 conversion that can fail. Multiple types may be specified
+/// to apply the same implementation. Will place the output in a `v8::ReturnValue`, but
+/// will not allocate.
+macro_rules! to_v8_retval_fallible {
+  (( $( $ty:ty ),+ ) : |$value:ident, $scope: ident, $rv:ident| $block:expr) => {
+    $(
+      impl <'a> RustToV8RetValFallible<'a> for $ty {
+        #[inline(always)]
+        fn to_v8_rv_fallible(self, scope: &mut v8::HandleScope<'a>, rv: &mut v8::ReturnValue<'a>) -> serde_v8::Result<()>{
+          let $value = self;
+          let $scope = scope;
+          let $rv = rv;
+          $block
+        }
+      }
+    )+
+  };
+  ($ty:ty : |$value:ident, $scope: ident, $rv:ident| $block:expr) => {
+    to_v8_retval_fallible!(( $ty ) : |$value, $scope, $rv| $block);
+  };
+}
+
+//
+// Integer/primitive conversions (cheap)
+//
+
+to_v8!((): |_value, scope| v8::null(scope));
+to_v8_retval!((): |_value, rv| rv.set_null());
+to_v8!(bool: |value, scope| v8::Boolean::new(scope, value as _));
+to_v8_retval!(bool: |value, rv| rv.set_bool(value));
+to_v8!((u8, u16, u32): |value, scope| v8::Integer::new_from_unsigned(scope, value as _));
+to_v8_retval!((u8, u16, u32): |value, rv| rv.set_uint32(value as _));
+to_v8!((i8, i16, i32): |value, scope| v8::Integer::new(scope, value as _));
+to_v8_retval!((i8, i16, i32): |value, rv| rv.set_int32(value as _));
+to_v8!((f32, f64): |value, scope| v8::Number::new(scope, value as _));
+to_v8_retval!((f32, f64): |value, rv| rv.set_double(value as _));
+
+//
+// Heavier primitives with no retval shortcuts
+//
+
+to_v8!((*const c_void, *mut c_void): |value, scope| v8::External::new(scope, value as _));
+to_v8!((u64, usize): |value, scope| v8::BigInt::new_from_u64(scope, value as _));
+to_v8!((i64, isize): |value, scope| v8::BigInt::new_from_i64(scope, value as _));
+
+//
+// Strings
+//
+
+to_v8_fallible!((String, Cow<'a, str>, &'a str): |value, scope| v8::String::new(scope, &value).ok_or_else(|| serde_v8::Error::Message("failed to allocate string".into())));
+to_v8_retval_fallible!((String, Cow<'a, str>, &'a str): |value, scope, rv| {
+  if value.is_empty() {
+    rv.set_empty_string();
+  } else {
+    rv.set(value.to_v8_fallible(scope)?);
+  }
+  Ok(())
+});
+to_v8_fallible!(Cow<'a, [u8]>: |value, scope| v8::String::new_from_one_byte(scope, &value, v8::NewStringType::Normal).ok_or_else(|| serde_v8::Error::Message("failed to allocate string".into())));
+to_v8_retval_fallible!(Cow<'a, [u8]>: |value, scope, rv| {
+  if value.is_empty() {
+    rv.set_empty_string();
+  } else {
+    rv.set(value.to_v8_fallible(scope)?);
+  }
+  Ok(())
+});
+
+//
+// Buffers
+//
+
+to_v8_fallible!(serde_v8::V8Slice: |value, scope| {
+  let (buffer, range) = value.into_parts();
+  let buffer = v8::ArrayBuffer::with_backing_store(scope, &buffer);
+  v8::Uint8Array::new(scope, buffer, range.start, range.len()).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+});
+to_v8_fallible!(serde_v8::JsBuffer: |value, scope| {
+  value.into_parts().to_v8_fallible(scope)
+});
+to_v8_fallible!(Box<[u8]>: |buf, scope| {
+  if buf.is_empty() {
+    let ab = v8::ArrayBuffer::new(scope, 0);
+    v8::Uint8Array::new(scope, ab, 0, 0).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+  } else {
+    let buf_len: usize = buf.len();
+    let backing_store =
+      v8::ArrayBuffer::new_backing_store_from_boxed_slice(buf);
+    let backing_store_shared = backing_store.make_shared();
+    let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
+    v8::Uint8Array::new(scope, ab, 0, buf_len).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+  }
+});
+to_v8_fallible!(Vec<u8>: |value, scope| value.into_boxed_slice().to_v8_fallible(scope));
+to_v8_fallible!(BytesMut: |value, scope| {
+  let ptr = value.as_ptr();
+  let len = value.len() as _;
+  let rc = Rc::into_raw(Rc::new(value)) as *const c_void;
+
+  extern "C" fn drop_rc(_ptr: *mut c_void, _len: usize, data: *mut c_void) {
+    // SAFETY: We know that data is a raw Rc from above
+    unsafe { drop(Rc::<BytesMut>::from_raw(data as _)) }
+  }
+
+  // SAFETY: We are using the BytesMut backing store here
+  let backing_store_shared = unsafe {
+    v8::ArrayBuffer::new_backing_store_from_ptr(
+      ptr as _, len, drop_rc, rc as _,
+    )
+  }
+  .make_shared();
+  let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
+  v8::Uint8Array::new(scope, ab, 0, len).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+});
+
+//
+// Serde
+//
+
+impl<'a, T: serde::Serialize> RustToV8Fallible<'a>
+  for RustToV8Marker<SerdeMarker, T>
+{
+  #[inline(always)]
+  fn to_v8_fallible(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+  ) -> serde_v8::Result<v8::Local<'a, v8::Value>> {
+    serde_v8::to_v8(scope, self.0)
+  }
+}
+
+//
+// Globals
+//
+
+impl<'a, T> RustToV8<'a> for v8::Global<T>
+where
+  v8::Local<'a, v8::Value>: From<v8::Local<'a, T>>,
+{
+  fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> v8::Local<'a, v8::Value> {
+    v8::Local::new(scope, self).into()
+  }
+}
+
+//
+// Locals
+//
+
+impl<'a, T> RustToV8NoScope<'a> for v8::Local<'a, T>
+where
+  v8::Local<'a, v8::Value>: From<v8::Local<'a, T>>,
+{
+  #[inline(always)]
+  fn to_v8(self) -> v8::Local<'a, v8::Value> {
+    self.into()
+  }
+}
+
+impl<'a, T> RustToV8<'a> for Option<v8::Local<'a, T>>
+where
+  v8::Local<'a, v8::Value>: From<v8::Local<'a, T>>,
+{
+  #[inline(always)]
+  fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> v8::Local<'a, v8::Value> {
+    if let Some(v) = self {
+      v.to_v8()
+    } else {
+      v8::null(scope).into()
+    }
+  }
+}
+
+impl<'a, T> RustToV8RetVal<'a> for Option<v8::Local<'a, T>>
+where
+  v8::Local<'a, v8::Value>: From<v8::Local<'a, T>>,
+{
+  fn to_v8_rv(self, rv: &mut v8::ReturnValue<'a>) {
+    if let Some(v) = self {
+      rv.set(v.to_v8())
+    } else {
+      rv.set_null()
+    }
+  }
+}
diff --git a/ops/op2/README.md b/ops/op2/README.md
index f212d73..23d2957 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -747,9 +747,9 @@ bool
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Bool
+
 </td><td>
 
 </td></tr>
@@ -763,9 +763,9 @@ i8
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Int32
+
 </td><td>
 
 </td></tr>
@@ -779,9 +779,9 @@ u8
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Uint32
+
 </td><td>
 
 </td></tr>
@@ -795,9 +795,9 @@ i16
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Int32
+
 </td><td>
 
 </td></tr>
@@ -811,9 +811,9 @@ u16
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Uint32
+
 </td><td>
 
 </td></tr>
@@ -827,9 +827,9 @@ i32
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Int32
+
 </td><td>
 
 </td></tr>
@@ -843,9 +843,9 @@ u32
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Uint32
+
 </td><td>
 
 </td></tr>
@@ -859,11 +859,11 @@ Uint32
 </td><td>
 ✅
 </td><td>
-✅
-</td><td>
-Int32
+
 </td><td>
 SMI is internally represented as a signed integer, but unsigned `#[smi]` types will be bit-converted to unsigned values for the Rust call. JavaScript code will continue to see signed integers.
+</td><td>
+
 </td></tr>
 <tr>
 <td>
@@ -875,9 +875,9 @@ SMI is internally represented as a signed integer, but unsigned `#[smi]` types w
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-BigInt
+
 </td><td>
 
 </td></tr>
@@ -891,9 +891,9 @@ BigInt
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-BigInt
+
 </td><td>
 
 </td></tr>
@@ -907,9 +907,9 @@ BigInt
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-BigInt
+
 </td><td>
 
 </td></tr>
@@ -923,9 +923,9 @@ BigInt
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-BigInt
+
 </td><td>
 
 </td></tr>
@@ -939,9 +939,9 @@ f32
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Number
+
 </td><td>
 
 </td></tr>
@@ -955,9 +955,9 @@ f64
 </td><td>
 ✅
 </td><td>
-✅
+
 </td><td>
-Number
+
 </td><td>
 
 </td></tr>
@@ -971,9 +971,57 @@ Number
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-String
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[string] &str
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[string] Cow<str>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[string(onebyte)] Cow<[u8]>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
 </td><td>
 
 </td></tr>
@@ -987,9 +1035,9 @@ String
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-ArrayBuffer
+
 </td><td>
 
 </td></tr>
@@ -1003,9 +1051,9 @@ ArrayBuffer
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-ArrayBuffer
+
 </td><td>
 
 </td></tr>
@@ -1019,9 +1067,9 @@ ArrayBuffer
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-ArrayBuffer
+
 </td><td>
 
 </td></tr>
@@ -1037,7 +1085,7 @@ ArrayBuffer
 </td><td>
 
 </td><td>
-External
+
 </td><td>
 
 </td></tr>
@@ -1053,7 +1101,7 @@ External
 </td><td>
 
 </td><td>
-External
+
 </td><td>
 
 </td></tr>
@@ -1069,7 +1117,7 @@ v8::Local<v8::Value>
 </td><td>
 
 </td><td>
-any
+
 </td><td>
 
 </td></tr>
@@ -1085,7 +1133,7 @@ v8::Local<v8::String>
 </td><td>
 
 </td><td>
-String
+
 </td><td>
 
 </td></tr>
@@ -1101,7 +1149,7 @@ v8::Local<v8::Object>
 </td><td>
 
 </td><td>
-Object
+
 </td><td>
 
 </td></tr>
@@ -1117,7 +1165,7 @@ v8::Local<v8::Function>
 </td><td>
 
 </td><td>
-Function
+
 </td><td>
 
 </td></tr>
@@ -1133,7 +1181,87 @@ v8::Local<v8::...>
 </td><td>
 
 </td><td>
-...
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[global] v8::Global<v8::Value>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[global] v8::Global<v8::String>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[global] v8::Global<v8::Object>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[global] v8::Global<v8::Function>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[global] v8::Global<v8::...>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
 </td><td>
 
 </td></tr>
@@ -1147,9 +1275,9 @@ v8::Local<v8::...>
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-any
+
 </td><td>
 
 </td></tr>
@@ -1163,9 +1291,9 @@ any
 </td><td>
 
 </td><td>
-✅
+
 </td><td>
-any
+
 </td><td>
 
 </td></tr>
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 1f1ba8e..4a391be 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -16,6 +16,8 @@ use super::signature::Strings;
 use super::V8MappingError;
 use crate::op2::generator_state::gs_extract;
 use crate::op2::generator_state::gs_quote;
+use crate::op2::signature::ArgMarker;
+use crate::op2::signature::ArgSlowRetval;
 use crate::op2::signature::BufferMode;
 use proc_macro2::Ident;
 use proc_macro2::TokenStream;
@@ -512,133 +514,51 @@ pub fn return_value_infallible(
   generator_state: &mut GeneratorState,
   ret_type: &Arg,
 ) -> Result<TokenStream, V8MappingError> {
-  let GeneratorState {
-    deno_core,
-    scope,
-    result,
-    retval,
-    needs_retval,
-    needs_scope,
-    ..
-  } = generator_state;
-
   // In the future we may be able to make this false for void again
-  *needs_retval = true;
+  generator_state.needs_retval = true;
 
-  let res = match ret_type {
-    Arg::Void => {
-      // TODO(mmastrac): revisit this. Ideally we wouldn't need to set
-      // rv to null, but because of how serde_v8 works this is required
-      // to keep compatibility with existing assumptions in `deno_core`
-      // and `deno` itself.
-      quote! {#retval.set_null();}
-    }
-    Arg::Numeric(NumericArg::bool) => {
-      quote!(#retval.set_bool(#result as bool);)
-    }
-    Arg::Numeric(NumericArg::u8)
-    | Arg::Numeric(NumericArg::u16)
-    | Arg::Numeric(NumericArg::u32) => {
-      quote!(#retval.set_uint32(#result as u32);)
-    }
-    Arg::Numeric(NumericArg::i8)
-    | Arg::Numeric(NumericArg::i16)
-    | Arg::Numeric(NumericArg::i32)
-    | Arg::Numeric(NumericArg::__SMI__) => {
-      quote!(#retval.set_int32(#result as i32);)
-    }
-    Arg::Numeric(NumericArg::i64 | NumericArg::isize) => {
-      *needs_retval = true;
-      *needs_scope = true;
-      quote!(#retval.set(v8::BigInt::new_from_i64(&mut scope, #result as _).into());)
-    }
-    Arg::Numeric(NumericArg::u64 | NumericArg::usize) => {
-      *needs_retval = true;
-      *needs_scope = true;
-      quote!(#retval.set(v8::BigInt::new_from_u64(&mut #scope, #result as _).into());)
+  let result = match ret_type.marker() {
+    ArgMarker::Serde => {
+      gs_quote!(generator_state(deno_core, result) => (#deno_core::_ops::RustToV8Marker::<#deno_core::_ops::SerdeMarker, _>::from(#result)))
     }
-    Arg::Numeric(NumericArg::f32 | NumericArg::f64) => {
-      quote!(#retval.set_double(#result as _);)
+    ArgMarker::None => gs_quote!(generator_state(result) => (#result)),
+  };
+  let res = match ret_type.slow_retval() {
+    ArgSlowRetval::RetVal => {
+      gs_quote!(generator_state(deno_core, retval) => (#deno_core::_ops::RustToV8RetVal::to_v8_rv(#result, &mut #retval)))
     }
-    Arg::String(Strings::String) => {
-      *needs_scope = true;
-      quote! {
-        if #result.is_empty() {
-          #retval.set_empty_string();
-        } else {
-          // This should not fail in normal cases
-          // TODO(mmastrac): This has extra allocations that we need to get rid of, especially if the string
-          // is ASCII. We could make an "external Rust String" string in V8 from these and re-use the allocation.
-          let temp = #deno_core::v8::String::new(&mut #scope, &#result).unwrap();
-          #retval.set(temp.into());
-        }
-      }
+    ArgSlowRetval::RetValFallible => {
+      generator_state.needs_scope = true;
+      let err = format_ident!("{}_err", generator_state.retval);
+      let throw_exception = throw_type_error_string(generator_state, &err)?;
+
+      gs_quote!(generator_state(deno_core, scope, retval) => (match #deno_core::_ops::RustToV8Fallible::to_v8_fallible(#result, &mut #scope) {
+        Ok(v) => #retval.set(v),
+        Err(#err) => {
+          #throw_exception
+        },
+      }))
     }
-    Arg::String(Strings::CowByte) => {
-      *needs_scope = true;
-      quote! {
-        if #result.is_empty() {
-          #retval.set_empty_string();
-        } else {
-          let temp = #deno_core::v8::String::new_from_one_byte(&mut #scope, &#result, #deno_core::v8::NewStringType::Normal).unwrap();
-          #retval.set(temp.into());
-        }
-      }
+    ArgSlowRetval::V8Local => {
+      generator_state.needs_scope = true;
+      gs_quote!(generator_state(deno_core, scope, retval) => (#retval.set(#deno_core::_ops::RustToV8::to_v8(#result, &mut #scope))))
     }
-    Arg::V8Local(_) => {
-      quote! {
-        // We may have a non v8::Value here
-        #retval.set(#result.into())
-      }
+    ArgSlowRetval::V8LocalNoScope => {
+      gs_quote!(generator_state(deno_core, retval) => (#retval.set(#deno_core::_ops::RustToV8NoScope::to_v8(#result))))
     }
-    Arg::SerdeV8(_class) => {
-      *needs_scope = true;
-
-      let deno_core = deno_core.clone();
-      let scope = scope.clone();
-      let result = result.clone();
-      let retval = retval.clone();
-      let err = format_ident!("{}_err", retval);
+    ArgSlowRetval::V8LocalFalliable => {
+      generator_state.needs_scope = true;
+      let err = format_ident!("{}_err", generator_state.retval);
       let throw_exception = throw_type_error_string(generator_state, &err)?;
 
-      quote! {
-        let #result = match #deno_core::_ops::serde_rust_to_v8(&mut #scope, #result) {
-          Ok(t) => t,
-          Err(#err) => {
-            #throw_exception
-          }
-        };
-        #retval.set(#result.into())
-      }
-    }
-    Arg::Buffer(
-      Buffer::JsBuffer(BufferMode::Default)
-      | Buffer::Vec(NumericArg::u8)
-      | Buffer::BoxSlice(NumericArg::u8)
-      | Buffer::BytesMut(BufferMode::Default),
-    ) => {
-      *needs_scope = true;
-      quote! { #retval.set(#deno_core::_ops::ToV8Value::to_v8_value(#result, &mut #scope)); }
-    }
-    Arg::External(External::Ptr(_)) => {
-      *needs_scope = true;
-      quote! { #retval.set(#deno_core::v8::External::new(&mut #scope, #result as _).into()) }
-    }
-    arg if arg.is_option() => {
-      // We support all optional types by generating the infallible version in a branch
-      let some = return_value_infallible(
-        generator_state,
-        &ret_type.some_type().unwrap(),
-      )?;
-      gs_quote!(generator_state(result, retval) => {
-        if let Some(#result) = #result {
-          #some
-        } else {
-          #retval.set_null();
-        }
-      })
+      gs_quote!(generator_state(deno_core, scope, retval) => (match #deno_core::_ops::RustToV8Fallible::to_v8_fallible(#result, &mut #scope) {
+        Ok(v) => #retval.set(v),
+        Err(#err) => {
+          #throw_exception
+        },
+      }))
     }
-    _ => {
+    ArgSlowRetval::None => {
       return Err(V8MappingError::NoMapping(
         "a slow return value",
         ret_type.clone(),
@@ -655,51 +575,23 @@ pub fn return_value_v8_value(
   ret_type: &Arg,
 ) -> Result<TokenStream, V8MappingError> {
   gs_extract!(generator_state(deno_core, scope, result));
-  let res = match ret_type {
-    Arg::Void => {
-      quote!(Ok(#deno_core::v8::null(#scope).into()))
-    }
-    Arg::Numeric(NumericArg::bool) => {
-      quote!(Ok(#deno_core::v8::Boolean::new(#scope, #result).into()))
-    }
-    Arg::Numeric(
-      NumericArg::i8 | NumericArg::i16 | NumericArg::i32 | NumericArg::__SMI__,
-    ) => {
-      quote!(Ok(#deno_core::v8::Integer::new(#scope, #result as i32).into()))
-    }
-    Arg::Numeric(NumericArg::u8 | NumericArg::u16 | NumericArg::u32) => {
-      quote!(Ok(#deno_core::v8::Integer::new_from_unsigned(#scope, #result).into()))
-    }
-    Arg::Numeric(NumericArg::f32 | NumericArg::f64) => {
-      quote!(Ok(#deno_core::v8::Number::new(#scope, #result as _).into()))
+  let result = match ret_type.marker() {
+    ArgMarker::Serde => {
+      quote!(#deno_core::_ops::RustToV8Marker::<#deno_core::_ops::SerdeMarker, _>::from(#result))
     }
-    Arg::Numeric(NumericArg::i64 | NumericArg::isize) => {
-      quote!(Ok(#deno_core::v8::BigInt::new_from_i64(#scope, #result as _).into()))
-    }
-    Arg::Numeric(NumericArg::u64 | NumericArg::usize) => {
-      quote!(Ok(#deno_core::v8::BigInt::new_from_u64(#scope, #result as _).into()))
-    }
-    Arg::String(Strings::String) => {
-      quote!(match #deno_core::v8::String::new(#scope, &#result) {
-        Some(s) => Ok(s.into()),
-        None => Err(#deno_core::serde_v8::Error::Message("Failed to convert string return value".into()))
-      })
-    }
-    Arg::Buffer(
-      Buffer::JsBuffer(BufferMode::Default)
-      | Buffer::Vec(NumericArg::u8)
-      | Buffer::BoxSlice(NumericArg::u8)
-      | Buffer::BytesMut(BufferMode::Default),
-    ) => {
-      quote!(Ok(#deno_core::_ops::ToV8Value::to_v8_value(#result, #scope)))
+    ArgMarker::None => quote!(#result),
+  };
+  let res = match ret_type.slow_retval() {
+    ArgSlowRetval::RetVal | ArgSlowRetval::V8Local => {
+      quote!(Ok(#deno_core::_ops::RustToV8::to_v8(#result, #scope)))
     }
-    Arg::External(External::Ptr(_)) => {
-      quote!(Ok(#deno_core::v8::External::new(#scope, #result as _).into()))
+    ArgSlowRetval::V8LocalNoScope => {
+      quote!(Ok(#deno_core::_ops::RustToV8NoScope::to_v8(#result)))
     }
-    Arg::SerdeV8(_) => {
-      quote!(#deno_core::_ops::serde_rust_to_v8(#scope, #result))
+    ArgSlowRetval::RetValFallible | ArgSlowRetval::V8LocalFalliable => {
+      quote!(#deno_core::_ops::RustToV8Fallible::to_v8_fallible(#result, #scope))
     }
-    _ => {
+    ArgSlowRetval::None => {
       return Err(V8MappingError::NoMapping(
         "a v8 return value",
         ret_type.clone(),
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index b8064ee..ff957e6 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -312,6 +312,77 @@ impl Arg {
       _ => return None,
     })
   }
+
+  /// This must be kept in sync with the `RustToV8`/`RustToV8Fallible` implementations in `deno_core`. If
+  /// this falls out of sync, you will see compile errors.
+  pub fn slow_retval(&self) -> ArgSlowRetval {
+    if let Some(some) = self.some_type() {
+      // If this is an optional return value, we use the same return type as the underlying object.
+      match some.slow_retval() {
+        // We need a scope in the case of an option so we can allocate a null
+        ArgSlowRetval::V8LocalNoScope => ArgSlowRetval::RetVal,
+        rv => rv,
+      }
+    } else {
+      match self {
+        Arg::Numeric(
+          NumericArg::i64
+          | NumericArg::u64
+          | NumericArg::isize
+          | NumericArg::usize,
+        ) => ArgSlowRetval::V8Local,
+        Arg::Void | Arg::Numeric(_) => ArgSlowRetval::RetVal,
+        Arg::External(_) => ArgSlowRetval::V8Local,
+        // Fast return value path for empty strings
+        Arg::String(_) => ArgSlowRetval::RetValFallible,
+        Arg::SerdeV8(_) => ArgSlowRetval::V8LocalFalliable,
+        // No scope required for these
+        Arg::V8Local(_) => ArgSlowRetval::V8LocalNoScope,
+        Arg::V8Global(_) => ArgSlowRetval::V8Local,
+        Arg::Buffer(
+          Buffer::JsBuffer(BufferMode::Default)
+          | Buffer::Vec(NumericArg::u8)
+          | Buffer::BoxSlice(NumericArg::u8)
+          | Buffer::BytesMut(BufferMode::Default),
+        ) => ArgSlowRetval::V8LocalFalliable,
+        _ => ArgSlowRetval::None,
+      }
+    }
+  }
+
+  /// Does this type have a marker (used for specialization of serialization/deserialization)?
+  pub fn marker(&self) -> ArgMarker {
+    match self {
+      Arg::SerdeV8(_) => ArgMarker::Serde,
+      _ => ArgMarker::None,
+    }
+  }
+}
+
+#[derive(PartialEq, Eq)]
+/// How can this argument be represented?
+pub enum ArgSlowRetval {
+  /// The argument is not supported in the return position.
+  None,
+  /// The argument is supported as a fast path in `v8::ReturnValue`. Implies that there is also
+  /// a `V8Local` implementation in cases where there is no [`v8::ReturnValue`]. Does not require
+  /// a scope.
+  RetVal,
+  /// Like `RetVal`, but fallible. Unlike `RetVal`, requires a scope.
+  RetValFallible,
+  /// The argument is only supported as a `v8::Local`, and it may not fail (eg: integers, floats).
+  V8Local,
+  /// The argument is only supported as a `v8::Local`, and it does not allocate (ie: it is already
+  /// a `v8::Local`).
+  V8LocalNoScope,
+  /// The argument is only supported as a `v8::Local`, and it may fail (eg: strings, arrays).
+  V8LocalFalliable,
+}
+
+/// Specifies an ArgMarker wrapper for a type used for trait-based serialization.
+pub enum ArgMarker {
+  None,
+  Serde,
 }
 
 pub enum ParsedType {
@@ -924,10 +995,10 @@ fn parse_type_path(
       ( $( std :: str :: )? str ) => {
         Ok(CBare(TString(Strings::RefStr)))
       }
-      ( $( std :: borrow :: )? Cow < str > ) => {
+      ( $( std :: borrow :: )? Cow < $( $_lt:lifetime , )? str > ) => {
         Ok(CBare(TString(Strings::CowStr)))
       }
-      ( $( std :: borrow :: )? Cow < [ u8 ] > ) => {
+      ( $( std :: borrow :: )? Cow < $( $_lt:lifetime , )? [ u8 ] > ) => {
         Ok(CBare(TString(Strings::CowByte)))
       }
       ( $( std :: vec ::)? Vec < $ty:path > ) => {
diff --git a/ops/op2/signature_retval.rs b/ops/op2/signature_retval.rs
index 5ceedb0..8dbe7cd 100644
--- a/ops/op2/signature_retval.rs
+++ b/ops/op2/signature_retval.rs
@@ -56,6 +56,7 @@ fn unwrap_return(ty: &Type) -> Result<UnwrappedReturn, RetError> {
     }
     Type::Tuple(_) => Ok(UnwrappedReturn::Type(ty.clone())),
     Type::Ptr(_) => Ok(UnwrappedReturn::Type(ty.clone())),
+    Type::Reference(_) => Ok(UnwrappedReturn::Type(ty.clone())),
     _ => Err(RetError::InvalidType(ArgError::InvalidType(
       stringify_token(ty),
       "for return type",
diff --git a/ops/op2/valid_retvals.md b/ops/op2/valid_retvals.md
index 452c6c0..0e1adbb 100644
--- a/ops/op2/valid_retvals.md
+++ b/ops/op2/valid_retvals.md
@@ -1,31 +1,31 @@
-| Supported | Rust                             | Fastcall | Async | V8          | Notes                                                                                                                                                                                            |
-| --------- | -------------------------------- | -------- | ----- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| X         | bool                             | X        | X     | Bool        |                                                                                                                                                                                                  |
-| X         | i8                               | X        | X     | Int32       |                                                                                                                                                                                                  |
-| X         | u8                               | X        | X     | Uint32      |                                                                                                                                                                                                  |
-| X         | i16                              | X        | X     | Int32       |                                                                                                                                                                                                  |
-| X         | u16                              | X        | X     | Uint32      |                                                                                                                                                                                                  |
-| X         | i32                              | X        | X     | Int32       |                                                                                                                                                                                                  |
-| X         | u32                              | X        | X     | Uint32      |                                                                                                                                                                                                  |
-| X         | #[smi] ResourceId                | X        | X     | Int32       | SMI is internally represented as a signed integer, but unsigned `#[smi]` types will be bit-converted to unsigned values for the Rust call. JavaScript code will continue to see signed integers. |
-| X         | #[bigint] i64                    |          | X     | BigInt      |                                                                                                                                                                                                  |
-| X         | #[bigint] u64                    |          | X     | BigInt      |                                                                                                                                                                                                  |
-| X         | #[bigint] isize                  |          | X     | BigInt      |                                                                                                                                                                                                  |
-| X         | #[bigint] usize                  |          | X     | BigInt      |                                                                                                                                                                                                  |
-| X         | f32                              | X        | X     | Number      |                                                                                                                                                                                                  |
-| X         | f64                              | X        | X     | Number      |                                                                                                                                                                                                  |
-| X         | #[string] String                 |          | X     | String      |                                                                                                                                                                                                  |
-|           | #[string] &str                   |          |       | String      |                                                                                                                                                                                                  |
-|           | #[string] Cow<str>               |          |       | String      |                                                                                                                                                                                                  |
-|           | #[string(onebyte)] Cow<[u8]>     |          |       | String      |                                                                                                                                                                                                  |
-| X         | #[buffer] Vec<u8>                |          | X     | ArrayBuffer |                                                                                                                                                                                                  |
-| X         | #[buffer] Box<[u8]>              |          | X     | ArrayBuffer |                                                                                                                                                                                                  |
-| X         | #[buffer] bytes::BytesMut        |          | X     | ArrayBuffer |                                                                                                                                                                                                  |
-| X         | *const std::ffi::c_void          | X        |       | External    |                                                                                                                                                                                                  |
-| X         | *mut std::ffi::c_void            | X        |       | External    |                                                                                                                                                                                                  |
-| X         | v8::Local<v8::Value>             |          |       | any         |                                                                                                                                                                                                  |
-| X         | v8::Local<v8::**V8**>            |          |       | **V8**      |                                                                                                                                                                                                  |
-|           | #[global] v8::Global<v8::Value>  |          |       | any         |                                                                                                                                                                                                  |
-|           | #[global] v8::Global<v8::**V8**> |          |       | **V8**      |                                                                                                                                                                                                  |
-| X         | #[serde] SerdeType               |          | X     | any         |                                                                                                                                                                                                  |
-| X         | #[serde] (Tuple, Tuple)          |          | X     | any         |                                                                                                                                                                                                  |
+| Supported | Rust                             | Fastcall | V8          | Notes                                                                                                                                                                                            |
+| --------- | -------------------------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| X         | bool                             | X        | Bool        |                                                                                                                                                                                                  |
+| X         | i8                               | X        | Int32       |                                                                                                                                                                                                  |
+| X         | u8                               | X        | Uint32      |                                                                                                                                                                                                  |
+| X         | i16                              | X        | Int32       |                                                                                                                                                                                                  |
+| X         | u16                              | X        | Uint32      |                                                                                                                                                                                                  |
+| X         | i32                              | X        | Int32       |                                                                                                                                                                                                  |
+| X         | u32                              | X        | Uint32      |                                                                                                                                                                                                  |
+| X         | #[smi] ResourceId                | X        | Int32       | SMI is internally represented as a signed integer, but unsigned `#[smi]` types will be bit-converted to unsigned values for the Rust call. JavaScript code will continue to see signed integers. |
+| X         | #[bigint] i64                    |          | BigInt      |                                                                                                                                                                                                  |
+| X         | #[bigint] u64                    |          | BigInt      |                                                                                                                                                                                                  |
+| X         | #[bigint] isize                  |          | BigInt      |                                                                                                                                                                                                  |
+| X         | #[bigint] usize                  |          | BigInt      |                                                                                                                                                                                                  |
+| X         | f32                              | X        | Number      |                                                                                                                                                                                                  |
+| X         | f64                              | X        | Number      |                                                                                                                                                                                                  |
+| X         | #[string] String                 |          | String      |                                                                                                                                                                                                  |
+| X         | #[string] &str                   |          | String      |                                                                                                                                                                                                  |
+| X         | #[string] Cow<str>               |          | String      |                                                                                                                                                                                                  |
+| X         | #[string(onebyte)] Cow<[u8]>     |          | String      |                                                                                                                                                                                                  |
+| X         | #[buffer] Vec<u8>                |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | #[buffer] Box<[u8]>              |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | #[buffer] bytes::BytesMut        |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | *const std::ffi::c_void          | X        | External    |                                                                                                                                                                                                  |
+| X         | *mut std::ffi::c_void            | X        | External    |                                                                                                                                                                                                  |
+| X         | v8::Local<v8::Value>             |          | any         |                                                                                                                                                                                                  |
+| X         | v8::Local<v8::**V8**>            |          | **V8**      |                                                                                                                                                                                                  |
+| X         | #[global] v8::Global<v8::Value>  |          | any         |                                                                                                                                                                                                  |
+| X         | #[global] v8::Global<v8::**V8**> |          | **V8**      |                                                                                                                                                                                                  |
+| X         | #[serde] SerdeType               |          | any         |                                                                                                                                                                                                  |
+| X         | #[serde] (Tuple, Tuple)          |          | any         |                                                                                                                                                                                                  |


Test patch:
diff --git a/ops/op2/test_cases/async/async_arg_return.out b/ops/op2/test_cases/async/async_arg_return.out
index 5ba4e56..b1222a1 100644
--- a/ops/op2/test_cases/async/async_arg_return.out
+++ b/ops/op2/test_cases/async/async_arg_return.out
@@ -59,11 +59,9 @@ impl op_async {
                 false,
                 promise_id,
                 result,
-                |scope, result| {
-                    Ok(deno_core::v8::Integer::new(scope, result as i32).into())
-                },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
-            rv.set_int32(result as i32);
+            deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
         }
     }
     #[inline(always)]
diff --git a/ops/op2/test_cases/async/async_arg_return_result.out b/ops/op2/test_cases/async/async_arg_return_result.out
index 0004436..8bfdc9b 100644
--- a/ops/op2/test_cases/async/async_arg_return_result.out
+++ b/ops/op2/test_cases/async/async_arg_return_result.out
@@ -59,14 +59,10 @@ impl op_async {
                 false,
                 promise_id,
                 result,
-                |scope, result| {
-                    Ok(deno_core::v8::Integer::new(scope, result as i32).into())
-                },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
             match result {
-                Ok(result) => {
-                    rv.set_int32(result as i32);
-                }
+                Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
                 Err(err) => {
                     let err = err.into();
                     let exception = deno_core::error::to_v8_error(
diff --git a/ops/op2/test_cases/async/async_jsbuffer.out b/ops/op2/test_cases/async/async_jsbuffer.out
index 28e1638..405c72d 100644
--- a/ops/op2/test_cases/async/async_jsbuffer.out
+++ b/ops/op2/test_cases/async/async_jsbuffer.out
@@ -67,10 +67,22 @@ impl op_async_v8_buffer {
                 promise_id,
                 result,
                 |scope, result| {
-                    Ok(deno_core::_ops::ToV8Value::to_v8_value(result, scope))
+                    deno_core::_ops::RustToV8Fallible::to_v8_fallible(result, scope)
                 },
             ) {
-            rv.set(deno_core::_ops::ToV8Value::to_v8_value(result, &mut scope));
+            match deno_core::_ops::RustToV8Fallible::to_v8_fallible(result, &mut scope) {
+                Ok(v) => rv.set(v),
+                Err(rv_err) => {
+                    let msg = deno_core::v8::String::new(
+                            &mut scope,
+                            &format!("{}", deno_core::anyhow::Error::from(rv_err)),
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return;
+                }
+            }
         }
     }
     #[inline(always)]
diff --git a/ops/op2/test_cases/async/async_opstate.out b/ops/op2/test_cases/async/async_opstate.out
index 5cc0152..6047755 100644
--- a/ops/op2/test_cases/async/async_opstate.out
+++ b/ops/op2/test_cases/async/async_opstate.out
@@ -47,14 +47,10 @@ impl op_async_opstate {
                 false,
                 promise_id,
                 result,
-                |scope, result| {
-                    Ok(deno_core::v8::Integer::new(scope, result as i32).into())
-                },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
             match result {
-                Ok(result) => {
-                    rv.set_int32(result as i32);
-                }
+                Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
                 Err(err) => {
                     let err = err.into();
                     let exception = deno_core::error::to_v8_error(
diff --git a/ops/op2/test_cases/async/async_result.out b/ops/op2/test_cases/async/async_result.out
index 8c8ee0c..0180b0a 100644
--- a/ops/op2/test_cases/async/async_result.out
+++ b/ops/op2/test_cases/async/async_result.out
@@ -43,14 +43,10 @@ impl op_async {
                 false,
                 promise_id,
                 result,
-                |scope, result| {
-                    Ok(deno_core::v8::Integer::new(scope, result as i32).into())
-                },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
             match result {
-                Ok(result) => {
-                    rv.set_int32(result as i32);
-                }
+                Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
                 Err(err) => {
                     let err = err.into();
                     let exception = deno_core::error::to_v8_error(
diff --git a/ops/op2/test_cases/async/async_result_impl.out b/ops/op2/test_cases/async/async_result_impl.out
index 727cf67..0d62a17 100644
--- a/ops/op2/test_cases/async/async_result_impl.out
+++ b/ops/op2/test_cases/async/async_result_impl.out
@@ -72,14 +72,10 @@ impl op_async_result_impl {
                 false,
                 promise_id,
                 result,
-                |scope, result| {
-                    Ok(deno_core::v8::Integer::new(scope, result as i32).into())
-                },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
             match result {
-                Ok(result) => {
-                    rv.set_int32(result as i32);
-                }
+                Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
                 Err(err) => {
                     let err = err.into();
                     let exception = deno_core::error::to_v8_error(
diff --git a/ops/op2/test_cases/async/async_result_smi.out b/ops/op2/test_cases/async/async_result_smi.out
index b13822a..fb3fbf5 100644
--- a/ops/op2/test_cases/async/async_result_smi.out
+++ b/ops/op2/test_cases/async/async_result_smi.out
@@ -59,14 +59,10 @@ impl op_async {
                 false,
                 promise_id,
                 result,
-                |scope, result| {
-                    Ok(deno_core::v8::Integer::new(scope, result as i32).into())
-                },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
             match result {
-                Ok(result) => {
-                    rv.set_int32(result as i32);
-                }
+                Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
                 Err(err) => {
                     let err = err.into();
                     let exception = deno_core::error::to_v8_error(
diff --git a/ops/op2/test_cases/async/async_v8_global.out b/ops/op2/test_cases/async/async_v8_global.out
index dbc9667..2f61ad9 100644
--- a/ops/op2/test_cases/async/async_v8_global.out
+++ b/ops/op2/test_cases/async/async_v8_global.out
@@ -61,9 +61,9 @@ impl op_async_v8_global {
                 false,
                 promise_id,
                 result,
-                |scope, result| { Ok(deno_core::v8::null(scope).into()) },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
-            rv.set_null();
+            deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
         }
     }
     #[inline(always)]
diff --git a/ops/op2/test_cases/async/async_void.out b/ops/op2/test_cases/async/async_void.out
index 4a8e332..c1cf9b3 100644
--- a/ops/op2/test_cases/async/async_void.out
+++ b/ops/op2/test_cases/async/async_void.out
@@ -43,9 +43,9 @@ impl op_async {
                 false,
                 promise_id,
                 result,
-                |scope, result| { Ok(deno_core::v8::null(scope).into()) },
+                |scope, result| { Ok(deno_core::_ops::RustToV8::to_v8(result, scope)) },
             ) {
-            rv.set_null();
+            deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
         }
     }
     #[inline(always)]
diff --git a/ops/op2/test_cases/sync/add.out b/ops/op2/test_cases/sync/add.out
index 64f644a..b39cb49 100644
--- a/ops/op2/test_cases/sync/add.out
+++ b/ops/op2/test_cases/sync/add.out
@@ -80,7 +80,7 @@ impl op_add {
             let arg1 = arg1 as _;
             Self::call(arg0, arg1)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(a: u32, b: u32) -> u32 {
diff --git a/ops/op2/test_cases/sync/add_options.out b/ops/op2/test_cases/sync/add_options.out
index 86c4d5e..2f04db9 100644
--- a/ops/op2/test_cases/sync/add_options.out
+++ b/ops/op2/test_cases/sync/add_options.out
@@ -65,7 +65,7 @@ impl op_test_add_option {
             };
             Self::call(arg0, arg1)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     pub fn call(a: u32, b: Option<u32>) -> u32 {
diff --git a/ops/op2/test_cases/sync/bool.out b/ops/op2/test_cases/sync/bool.out
index 6558889..e8a8231 100644
--- a/ops/op2/test_cases/sync/bool.out
+++ b/ops/op2/test_cases/sync/bool.out
@@ -52,7 +52,7 @@ impl op_bool {
             let arg0 = arg0.is_true();
             Self::call(arg0)
         };
-        rv.set_bool(result as bool);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     pub fn call(arg: bool) -> bool {
diff --git a/ops/op2/test_cases/sync/bool_result.out b/ops/op2/test_cases/sync/bool_result.out
index 79f476a..8dfe836 100644
--- a/ops/op2/test_cases/sync/bool_result.out
+++ b/ops/op2/test_cases/sync/bool_result.out
@@ -86,9 +86,7 @@ impl op_bool {
             Self::call(arg0)
         };
         match result {
-            Ok(result) => {
-                rv.set_bool(result as bool);
-            }
+            Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
             Err(err) => {
                 let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                 let err = err.into();
diff --git a/ops/op2/test_cases/sync/buffers.out b/ops/op2/test_cases/sync/buffers.out
index b317b06..2644caa 100644
--- a/ops/op2/test_cases/sync/buffers.out
+++ b/ops/op2/test_cases/sync/buffers.out
@@ -102,7 +102,7 @@ impl op_buffers {
             let arg1 = &mut arg1;
             Self::call(arg0, arg1)
         };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(_a: &[u8], _b: &mut [u8]) {}
diff --git a/ops/op2/test_cases/sync/buffers_copy.out b/ops/op2/test_cases/sync/buffers_copy.out
index 316fce9..8c4a947 100644
--- a/ops/op2/test_cases/sync/buffers_copy.out
+++ b/ops/op2/test_cases/sync/buffers_copy.out
@@ -132,7 +132,7 @@ impl op_buffers {
             let arg2 = arg2.to_vec().into();
             Self::call(arg0, arg1, arg2)
         };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(_a: Vec<u8>, _b: Box<[u8]>, _c: bytes::Bytes) {}
diff --git a/ops/op2/test_cases/sync/clippy_allow.out b/ops/op2/test_cases/sync/clippy_allow.out
index 1996eb8..f1de864 100644
--- a/ops/op2/test_cases/sync/clippy_allow.out
+++ b/ops/op2/test_cases/sync/clippy_allow.out
@@ -41,7 +41,7 @@ impl op_extra_annotation {
             &*info
         });
         let result = { Self::call() };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     /// This is a doc comment.
diff --git a/ops/op2/test_cases/sync/doc_comment.out b/ops/op2/test_cases/sync/doc_comment.out
index 2589d95..4e8023c 100644
--- a/ops/op2/test_cases/sync/doc_comment.out
+++ b/ops/op2/test_cases/sync/doc_comment.out
@@ -40,7 +40,7 @@ impl op_has_doc_comment {
             &*info
         });
         let result = { Self::call() };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     /// This is a doc comment.
diff --git a/ops/op2/test_cases/sync/generics.out b/ops/op2/test_cases/sync/generics.out
index eb267dd..f645c0b 100644
--- a/ops/op2/test_cases/sync/generics.out
+++ b/ops/op2/test_cases/sync/generics.out
@@ -39,7 +39,7 @@ impl<T: Trait> op_generics<T> {
             &*info
         });
         let result = { Self::call() };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     pub fn call() {}
@@ -86,7 +86,7 @@ impl<T: Trait + 'static> op_generics_static<T> {
             &*info
         });
         let result = { Self::call() };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     pub fn call() {}
@@ -133,7 +133,7 @@ impl<T: Trait + 'static> op_generics_static_where<T> {
             &*info
         });
         let result = { Self::call() };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     pub fn call()
diff --git a/ops/op2/test_cases/sync/nofast.out b/ops/op2/test_cases/sync/nofast.out
index 630e8ec..96ff079 100644
--- a/ops/op2/test_cases/sync/nofast.out
+++ b/ops/op2/test_cases/sync/nofast.out
@@ -60,7 +60,7 @@ impl op_nofast {
             let arg1 = arg1 as _;
             Self::call(arg0, arg1)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(a: u32, b: u32) -> u32 {
diff --git a/ops/op2/test_cases/sync/op_state_attr.out b/ops/op2/test_cases/sync/op_state_attr.out
index ec018dc..1e73a1c 100644
--- a/ops/op2/test_cases/sync/op_state_attr.out
+++ b/ops/op2/test_cases/sync/op_state_attr.out
@@ -69,7 +69,7 @@ impl op_state_rc {
             let arg1 = arg1.try_borrow::<Something>();
             Self::call(arg0, arg1)
         };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(_arg: &Something, _arg_opt: Option<&Something>) {}
diff --git a/ops/op2/test_cases/sync/op_state_rc.out b/ops/op2/test_cases/sync/op_state_rc.out
index d3cd40f..62e120c 100644
--- a/ops/op2/test_cases/sync/op_state_rc.out
+++ b/ops/op2/test_cases/sync/op_state_rc.out
@@ -63,7 +63,7 @@ impl op_state_rc {
             let arg0 = opstate.clone();
             Self::call(arg0)
         };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(_state: Rc<RefCell<OpState>>) {}
diff --git a/ops/op2/test_cases/sync/op_state_ref.out b/ops/op2/test_cases/sync/op_state_ref.out
index 11c1f66..f697839 100644
--- a/ops/op2/test_cases/sync/op_state_ref.out
+++ b/ops/op2/test_cases/sync/op_state_ref.out
@@ -63,7 +63,7 @@ impl op_state_rc {
             let arg0 = &opstate.borrow();
             Self::call(arg0)
         };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(_state: &OpState) {}
diff --git a/ops/op2/test_cases/sync/result_primitive.out b/ops/op2/test_cases/sync/result_primitive.out
index cf84857..3df832a 100644
--- a/ops/op2/test_cases/sync/result_primitive.out
+++ b/ops/op2/test_cases/sync/result_primitive.out
@@ -81,9 +81,7 @@ impl op_u32_with_result {
         }
         let result = { Self::call() };
         match result {
-            Ok(result) => {
-                rv.set_uint32(result as u32);
-            }
+            Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
             Err(err) => {
                 let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                 let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
diff --git a/ops/op2/test_cases/sync/result_scope.out b/ops/op2/test_cases/sync/result_scope.out
index 97a92fd..36fa87c 100644
--- a/ops/op2/test_cases/sync/result_scope.out
+++ b/ops/op2/test_cases/sync/result_scope.out
@@ -35,9 +35,7 @@ impl op_void_with_result {
             Self::call(arg0)
         };
         match result {
-            Ok(result) => {
-                rv.set_null();
-            }
+            Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
             Err(err) => {
                 let opctx = unsafe {
                     &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data())
diff --git a/ops/op2/test_cases/sync/result_void.out b/ops/op2/test_cases/sync/result_void.out
index 38fadd2..6065f1c 100644
--- a/ops/op2/test_cases/sync/result_void.out
+++ b/ops/op2/test_cases/sync/result_void.out
@@ -81,9 +81,7 @@ impl op_void_with_result {
         }
         let result = { Self::call() };
         match result {
-            Ok(result) => {
-                rv.set_null();
-            }
+            Ok(result) => deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv),
             Err(err) => {
                 let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                 let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
diff --git a/ops/op2/test_cases/sync/serde_v8.out b/ops/op2/test_cases/sync/serde_v8.out
index c50a4d2..1a3adf9 100644
--- a/ops/op2/test_cases/sync/serde_v8.out
+++ b/ops/op2/test_cases/sync/serde_v8.out
@@ -47,8 +47,14 @@ impl op_serde_v8 {
             };
             Self::call(arg0)
         };
-        let result = match deno_core::_ops::serde_rust_to_v8(&mut scope, result) {
-            Ok(t) => t,
+        match deno_core::_ops::RustToV8Fallible::to_v8_fallible(
+            deno_core::_ops::RustToV8Marker::<
+                deno_core::_ops::SerdeMarker,
+                _,
+            >::from(result),
+            &mut scope,
+        ) {
+            Ok(v) => rv.set(v),
             Err(rv_err) => {
                 let msg = deno_core::v8::String::new(
                         &mut scope,
@@ -59,8 +65,7 @@ impl op_serde_v8 {
                 scope.throw_exception(exc);
                 return;
             }
-        };
-        rv.set(result.into())
+        }
     }
     #[inline(always)]
     pub fn call(_input: Input) -> Output {
diff --git a/ops/op2/test_cases/sync/smi.out b/ops/op2/test_cases/sync/smi.out
index 36d8347..d1577af 100644
--- a/ops/op2/test_cases/sync/smi.out
+++ b/ops/op2/test_cases/sync/smi.out
@@ -112,7 +112,7 @@ impl op_smi_unsigned_return {
             let arg3 = arg3 as _;
             Self::call(arg0, arg1, arg2, arg3)
         };
-        rv.set_int32(result as i32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(a: Int16, b: Int32, c: Uint16, d: Uint32) -> Uint32 {
@@ -234,7 +234,7 @@ impl op_smi_signed_return {
             let arg3 = arg3 as _;
             Self::call(arg0, arg1, arg2, arg3)
         };
-        rv.set_int32(result as i32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(a: Int16, b: Int32, c: Uint16, d: Uint32) -> Int32 {
diff --git a/ops/op2/test_cases/sync/string_cow.out b/ops/op2/test_cases/sync/string_cow.out
index d2954e4..4f839d0 100644
--- a/ops/op2/test_cases/sync/string_cow.out
+++ b/ops/op2/test_cases/sync/string_cow.out
@@ -66,7 +66,7 @@ impl op_string_cow {
             let arg0 = deno_core::_ops::to_str(&mut scope, &arg0, &mut arg0_temp);
             Self::call(arg0)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(s: Cow<str>) -> u32 {
diff --git a/ops/op2/test_cases/sync/string_onebyte.out b/ops/op2/test_cases/sync/string_onebyte.out
index 1fab1ff..71d04d0 100644
--- a/ops/op2/test_cases/sync/string_onebyte.out
+++ b/ops/op2/test_cases/sync/string_onebyte.out
@@ -71,7 +71,7 @@ impl op_string_onebyte {
             };
             Self::call(arg0)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(s: Cow<[u8]>) -> u32 {
diff --git a/ops/op2/test_cases/sync/string_option_return.out b/ops/op2/test_cases/sync/string_option_return.out
index c7b7b63..1dc204d 100644
--- a/ops/op2/test_cases/sync/string_option_return.out
+++ b/ops/op2/test_cases/sync/string_option_return.out
@@ -39,15 +39,18 @@ impl op_string_return {
             };
             Self::call(arg0)
         };
-        if let Some(result) = result {
-            if result.is_empty() {
-                rv.set_empty_string();
-            } else {
-                let temp = deno_core::v8::String::new(&mut scope, &result).unwrap();
-                rv.set(temp.into());
+        match deno_core::_ops::RustToV8Fallible::to_v8_fallible(result, &mut scope) {
+            Ok(v) => rv.set(v),
+            Err(rv_err) => {
+                let msg = deno_core::v8::String::new(
+                        &mut scope,
+                        &format!("{}", deno_core::anyhow::Error::from(rv_err)),
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
             }
-        } else {
-            rv.set_null();
         }
     }
     #[inline(always)]
diff --git a/ops/op2/test_cases/sync/string_owned.out b/ops/op2/test_cases/sync/string_owned.out
index e77324e..670840e 100644
--- a/ops/op2/test_cases/sync/string_owned.out
+++ b/ops/op2/test_cases/sync/string_owned.out
@@ -57,7 +57,7 @@ impl op_string_owned {
             let arg0 = deno_core::_ops::to_string(&mut scope, &arg0);
             Self::call(arg0)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(s: String) -> u32 {
diff --git a/ops/op2/test_cases/sync/string_ref.out b/ops/op2/test_cases/sync/string_ref.out
index e5fa41c..5c334de 100644
--- a/ops/op2/test_cases/sync/string_ref.out
+++ b/ops/op2/test_cases/sync/string_ref.out
@@ -66,7 +66,7 @@ impl op_string_owned {
             let arg0 = &deno_core::_ops::to_str(&mut scope, &arg0, &mut arg0_temp);
             Self::call(arg0)
         };
-        rv.set_uint32(result as u32);
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     fn call(s: &str) -> u32 {
diff --git a/ops/op2/test_cases/sync/string_return.out b/ops/op2/test_cases/sync/string_return.out
index a2f51ac..58995a4 100644
--- a/ops/op2/test_cases/sync/string_return.out
+++ b/ops/op2/test_cases/sync/string_return.out
@@ -28,11 +28,18 @@ impl op_string_return {
             &*info
         });
         let result = { Self::call() };
-        if result.is_empty() {
-            rv.set_empty_string();
-        } else {
-            let temp = deno_core::v8::String::new(&mut scope, &result).unwrap();
-            rv.set(temp.into());
+        match deno_core::_ops::RustToV8Fallible::to_v8_fallible(result, &mut scope) {
+            Ok(v) => rv.set(v),
+            Err(rv_err) => {
+                let msg = deno_core::v8::String::new(
+                        &mut scope,
+                        &format!("{}", deno_core::anyhow::Error::from(rv_err)),
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            }
         }
     }
     #[inline(always)]
@@ -40,3 +47,103 @@ impl op_string_return {
         "".into()
     }
 }
+
+#[allow(non_camel_case_types)]
+pub struct op_string_return_ref {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_return_ref {
+    const NAME: &'static str = stringify!(op_string_return_ref);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_string_return_ref),
+        false,
+        false,
+        false,
+        0usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_string_return_ref {
+    pub const fn name() -> &'static str {
+        stringify!(op_string_return_ref)
+    }
+    #[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 result = { Self::call() };
+        match deno_core::_ops::RustToV8Fallible::to_v8_fallible(result, &mut scope) {
+            Ok(v) => rv.set(v),
+            Err(rv_err) => {
+                let msg = deno_core::v8::String::new(
+                        &mut scope,
+                        &format!("{}", deno_core::anyhow::Error::from(rv_err)),
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            }
+        }
+    }
+    #[inline(always)]
+    pub fn call() -> &'static str {
+        ""
+    }
+}
+
+#[allow(non_camel_case_types)]
+pub struct op_string_return_cow {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_return_cow {
+    const NAME: &'static str = stringify!(op_string_return_cow);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_string_return_cow),
+        false,
+        false,
+        false,
+        0usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_string_return_cow {
+    pub const fn name() -> &'static str {
+        stringify!(op_string_return_cow)
+    }
+    #[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 result = { Self::call() };
+        match deno_core::_ops::RustToV8Fallible::to_v8_fallible(result, &mut scope) {
+            Ok(v) => rv.set(v),
+            Err(rv_err) => {
+                let msg = deno_core::v8::String::new(
+                        &mut scope,
+                        &format!("{}", deno_core::anyhow::Error::from(rv_err)),
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            }
+        }
+    }
+    #[inline(always)]
+    pub fn call<'a>() -> Cow<'a, str> {
+        "".into()
+    }
+}
diff --git a/ops/op2/test_cases/sync/string_return.rs b/ops/op2/test_cases/sync/string_return.rs
index bd4262d..26e379a 100644
--- a/ops/op2/test_cases/sync/string_return.rs
+++ b/ops/op2/test_cases/sync/string_return.rs
@@ -1,9 +1,22 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
 #![deny(warnings)]
 deno_ops_compile_test_runner::prelude!();
+use std::borrow::Cow;
 
 #[op2]
 #[string]
 pub fn op_string_return() -> String {
     "".into()
 }
+
+#[op2]
+#[string]
+pub fn op_string_return_ref() -> &'static str {
+    ""
+}
+
+#[op2]
+#[string]
+pub fn op_string_return_cow<'a>() -> Cow<'a, str> {
+    "".into()
+}
diff --git a/ops/op2/test_cases/sync/v8_global.out b/ops/op2/test_cases/sync/v8_global.out
index b1331a6..e8ad497 100644
--- a/ops/op2/test_cases/sync/v8_global.out
+++ b/ops/op2/test_cases/sync/v8_global.out
@@ -23,17 +23,13 @@ impl op_global {
         <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 opctx = unsafe {
-            &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
-                as *const deno_core::_ops::OpCtx)
-        };
-        let mut scope = unsafe { &mut *opctx.isolate };
         let result = {
             let arg0 = args.get(0usize as i32);
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
@@ -53,8 +49,10 @@ impl op_global {
             let arg0 = deno_core::v8::Global::new(&mut scope, arg0);
             Self::call(arg0)
         };
-        rv.set_null();
+        rv.set(deno_core::_ops::RustToV8::to_v8(result, &mut scope))
     }
     #[inline(always)]
-    pub fn call(_g: v8::Global<v8::String>) {}
+    pub fn call(g: v8::Global<v8::String>) -> v8::Global<v8::String> {
+        g
+    }
 }
diff --git a/ops/op2/test_cases/sync/v8_global.rs b/ops/op2/test_cases/sync/v8_global.rs
index 89cf2df..5af404e 100644
--- a/ops/op2/test_cases/sync/v8_global.rs
+++ b/ops/op2/test_cases/sync/v8_global.rs
@@ -5,4 +5,7 @@ deno_ops_compile_test_runner::prelude!();
 use deno_core::v8;
 
 #[op2]
-pub fn op_global(#[global] _g: v8::Global<v8::String>) {}
+#[global]
+pub fn op_global(#[global] g: v8::Global<v8::String>) -> v8::Global<v8::String> {
+    g
+}
diff --git a/ops/op2/test_cases/sync/v8_handlescope.out b/ops/op2/test_cases/sync/v8_handlescope.out
index 5801d34..2ee7d81 100644
--- a/ops/op2/test_cases/sync/v8_handlescope.out
+++ b/ops/op2/test_cases/sync/v8_handlescope.out
@@ -49,7 +49,7 @@ impl op_handlescope {
             let arg0 = &mut scope;
             Self::call(arg0, arg1)
         };
-        rv.set(result.into())
+        rv.set(deno_core::_ops::RustToV8NoScope::to_v8(result))
     }
     #[inline(always)]
     fn call<'a>(
diff --git a/ops/op2/test_cases/sync/v8_lifetime.out b/ops/op2/test_cases/sync/v8_lifetime.out
index d508606..010be55 100644
--- a/ops/op2/test_cases/sync/v8_lifetime.out
+++ b/ops/op2/test_cases/sync/v8_lifetime.out
@@ -48,7 +48,7 @@ impl op_v8_lifetime {
             let arg0 = arg0;
             Self::call(arg0)
         };
-        rv.set(result.into())
+        rv.set(deno_core::_ops::RustToV8NoScope::to_v8(result))
     }
     #[inline(always)]
     pub fn call<'s>(_s: v8::Local<'s, v8::String>) -> v8::Local<'s, v8::String> {
diff --git a/ops/op2/test_cases/sync/v8_ref_option.out b/ops/op2/test_cases/sync/v8_ref_option.out
index 8e4e65f..0284392 100644
--- a/ops/op2/test_cases/sync/v8_ref_option.out
+++ b/ops/op2/test_cases/sync/v8_ref_option.out
@@ -110,7 +110,7 @@ impl op_v8_lifetime {
             };
             Self::call(arg0, arg1)
         };
-        rv.set_null();
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
     }
     #[inline(always)]
     pub fn call<'s>(_s: Option<&v8::String>, _s2: Option<&v8::String>) {}
diff --git a/ops/op2/test_cases/sync/v8_string.out b/ops/op2/test_cases/sync/v8_string.out
index 99273fa..f7181a5 100644
--- a/ops/op2/test_cases/sync/v8_string.out
+++ b/ops/op2/test_cases/sync/v8_string.out
@@ -64,7 +64,7 @@ impl op_v8_string {
             let arg1 = arg1;
             Self::call(arg0, arg1)
         };
-        rv.set(result.into())
+        rv.set(deno_core::_ops::RustToV8NoScope::to_v8(result))
     }
     #[inline(always)]
     fn call<'a>(