Repository: denoland/deno_core
Pull request #60 (2023-07-20 02:13:08)

feat(ops): op2 support for buffers in async ops
Supports owned buffers in and out:

```
#[op2] #[buffer] async fn(#[buffer] input: JsSlice) -> JsSlice {}
#[op2] #[buffer] async fn(#[buffer] input: JsSlice) -> Vec<u8> {}
#[op2] #[buffer] async fn(#[buffer] input: JsSlice) -> Box<[u8]> {}
```




Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7d8afab..7780259 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1037,9 +1037,9 @@ dependencies = [
 
 [[package]]
 name = "prettyplease"
-version = "0.2.10"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387"
+checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
 dependencies = [
  "proc-macro2",
  "syn 2.0.26",
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index f39f3a7..9768562 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -11,6 +11,8 @@ 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::future::ready;
@@ -476,6 +478,98 @@ where
   Ok(slice)
 }
 
+/// Retrieve a [`serde_v8::V8Slice`] from a typed array in an [`v8::ArrayBufferView`].
+pub fn to_v8_slice_detachable<'a, T>(
+  scope: &mut v8::HandleScope,
+  input: v8::Local<'a, v8::Value>,
+) -> Result<serde_v8::V8Slice, &'static str>
+where
+  v8::Local<'a, T>: TryFrom<v8::Local<'a, v8::Value>>,
+  v8::Local<'a, v8::ArrayBufferView>: From<v8::Local<'a, T>>,
+{
+  let (store, offset, length) = if let Ok(buf) = v8::Local::<T>::try_from(input)
+  {
+    let buf: v8::Local<v8::ArrayBufferView> = buf.into();
+    let Some(buffer) = buf.buffer(scope) else {
+      return Err("buffer missing");
+    };
+    let res = (
+      buffer.get_backing_store(),
+      buf.byte_offset(),
+      buf.byte_length(),
+    );
+    if !buffer.is_detachable() {
+      return Err("invalid type; expected: detachable");
+    }
+    buffer.detach(None);
+    res
+  } else {
+    return Err("expected typed ArrayBufferView");
+  };
+  let slice =
+    unsafe { serde_v8::V8Slice::from_parts(store, offset..(offset + length)) };
+  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)
+  }
+}
+
 #[cfg(test)]
 mod tests {
   use crate::error::generic_error;
@@ -491,6 +585,7 @@ mod tests {
   use futures::Future;
   use serde::Deserialize;
   use serde::Serialize;
+  use serde_v8::JsBuffer;
   use std::borrow::Cow;
   use std::cell::Cell;
   use std::cell::RefCell;
@@ -548,6 +643,8 @@ mod tests {
       op_async_sleep_error,
       op_async_result_impl,
       op_async_state_rc,
+      op_async_buffer,
+      op_async_buffer_vec,
       op_async_buffer_impl,
     ],
     state = |state| {
@@ -1497,6 +1594,20 @@ mod tests {
     Ok(())
   }
 
+  #[op2(async, core)]
+  #[buffer]
+  async fn op_async_buffer(#[buffer] input: JsBuffer) -> JsBuffer {
+    input
+  }
+
+  #[op2(async, core)]
+  #[buffer]
+  async fn op_async_buffer_vec(#[buffer] input: JsBuffer) -> Vec<u8> {
+    let mut output = input.to_vec();
+    output.reverse();
+    output
+  }
+
   #[op2(async, core)]
   fn op_async_buffer_impl(#[buffer] input: &[u8]) -> impl Future<Output = u32> {
     let l = input.len();
@@ -1507,7 +1618,19 @@ mod tests {
   pub async fn test_op_async_buffer() -> Result<(), Box<dyn std::error::Error>>
   {
     run_async_test(
-      5,
+      2,
+      "op_async_buffer",
+      "let output = await op_async_buffer(new Uint8Array([1,2,3])); assert(output.length == 3); assert(output[0] == 1);",
+    )
+    .await?;
+    run_async_test(
+      2,
+      "op_async_buffer_vec",
+      "let output = await op_async_buffer_vec(new Uint8Array([3,2,1])); assert(output.length == 3); assert(output[0] == 1);",
+    )
+    .await?;
+    run_async_test(
+      2,
       "op_async_buffer_impl",
       "assert(await op_async_buffer_impl(new Uint8Array(10)) == 10)",
     )
diff --git a/ops/Cargo.toml b/ops/Cargo.toml
index be93b4c..8822bd0 100644
--- a/ops/Cargo.toml
+++ b/ops/Cargo.toml
@@ -32,5 +32,5 @@ v8.workspace = true
 
 [dev-dependencies]
 pretty_assertions.workspace = true
-prettyplease = "0.2.10"
+prettyplease = "0.2.12"
 testing_macros = "0.2.11"
diff --git a/ops/op2/README.md b/ops/op2/README.md
index ef752c7..dbff9e0 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -41,7 +41,7 @@ fn op_xyz(promise_id: i32, ...) -> Option<X> {}
 
 # Parameters
 
-<!-- START -->
+<!-- START ARGS -->
 <table><tr><th>Rust</th><th>Fastcall</th><th>v8</th></tr>
 <tr>
 <td>
@@ -144,6 +144,34 @@ Uint32, Int32, Number, BigInt
 <tr>
 <td>
 
+```rust
+i64
+```
+
+</td><td>
+✅
+</td><td>
+Uint32, Int32, Number, BigInt
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+u64
+```
+
+</td><td>
+✅
+</td><td>
+Uint32, Int32, Number, BigInt
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
 ```rust
 isize
 ```
@@ -550,6 +578,34 @@ Safe, but forces a copy.
 <tr>
 <td>
 
+```rust
+#[buffer] JsBuffer
+```
+
+</td><td>
+
+</td><td>
+ArrayBuffer, ArrayBufferView (resizable=false)
+</td><td>
+⚠️ JS may modify the contents of slices obtained from buffer.
+</td></tr>
+<tr>
+<td>
+
+```rust
+#[buffer(detach)] JsBuffer
+```
+
+</td><td>
+
+</td><td>
+ArrayBuffer, ArrayBufferView (resizable=true,false)
+</td><td>
+Safe.
+</td></tr>
+<tr>
+<td>
+
 ```rust
 &OpState
 ```
@@ -618,3 +674,251 @@ Extracts an object from `OpState`.
 Extracts an object from `OpState`.
 </td></tr>
 </table>
+
+<!-- END ARGS -->
+
+# Return Values
+
+<!-- START RV -->
+<table><tr><th>Rust</th><th>Fastcall</th><th>v8</th></tr>
+<tr>
+<td>
+
+```rust
+bool
+```
+
+</td><td>
+✅
+</td><td>
+Bool
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+i8
+```
+
+</td><td>
+✅
+</td><td>
+Int32
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+u8
+```
+
+</td><td>
+✅
+</td><td>
+Uint32
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+i16
+```
+
+</td><td>
+✅
+</td><td>
+Int32
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+u16
+```
+
+</td><td>
+✅
+</td><td>
+Uint32
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+i32
+```
+
+</td><td>
+✅
+</td><td>
+Int32
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+u32
+```
+
+</td><td>
+✅
+</td><td>
+Uint32
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+f32
+```
+
+</td><td>
+✅
+</td><td>
+Number
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+f64
+```
+
+</td><td>
+✅
+</td><td>
+Number
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+#[string] String
+```
+
+</td><td>
+
+</td><td>
+String
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+v8::Local<v8::Value>
+```
+
+</td><td>
+
+</td><td>
+any
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+v8::Local<v8::String>
+```
+
+</td><td>
+
+</td><td>
+String
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+v8::Local<v8::Object>
+```
+
+</td><td>
+
+</td><td>
+Object
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+v8::Local<v8::Function>
+```
+
+</td><td>
+
+</td><td>
+Function
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+v8::Local<v8::...>
+```
+
+</td><td>
+
+</td><td>
+...
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+#[serde] SerdeType
+```
+
+</td><td>
+
+</td><td>
+any
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```rust
+#[serde] (Tuple, Tuple)
+```
+
+</td><td>
+
+</td><td>
+any
+</td><td>
+
+</td></tr>
+</table>
+
+<!-- END RV -->
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 1803e86..681ecc8 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -4,6 +4,7 @@ use super::dispatch_shared::v8_to_arg;
 use super::generator_state::GeneratorState;
 use super::signature::Arg;
 use super::signature::Buffer;
+use super::signature::BufferMode;
 use super::signature::NumericArg;
 use super::signature::ParsedSignature;
 use super::signature::RefType;
@@ -307,7 +308,7 @@ fn map_v8_fastcall_arg_to_arg(
     Arg::Buffer(Buffer::BoxSlice(NumericArg::u8)) => {
       quote!(let #arg_ident = unsafe { #arg_ident.as_mut().unwrap() }.get_storage_if_aligned().unwrap().to_vec().into_boxed_slice();)
     }
-    Arg::Buffer(Buffer::Bytes) => {
+    Arg::Buffer(Buffer::Bytes(BufferMode::Copy)) => {
       quote!(let #arg_ident = unsafe { #arg_ident.as_mut().unwrap() }.get_storage_if_aligned().unwrap().to_vec().into();)
     }
     Arg::Ref(RefType::Ref, Special::OpState) => {
@@ -410,7 +411,7 @@ fn map_arg_to_v8_fastcall_type(
       Buffer::Slice(_, NumericArg::u8)
       | Buffer::Vec(NumericArg::u8)
       | Buffer::BoxSlice(NumericArg::u8)
-      | Buffer::Bytes,
+      | Buffer::Bytes(BufferMode::Copy),
     ) => V8FastCallType::Uint8Array,
     Arg::Buffer(_) => return Ok(None),
     // Virtual OpState arguments
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 3cb2bf0..e50e0b4 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -14,6 +14,7 @@ use super::MacroConfig;
 use super::V8MappingError;
 use crate::op2::generator_state::gs_extract;
 use crate::op2::generator_state::gs_quote;
+use crate::op2::signature::BufferMode;
 use proc_macro2::Ident;
 use proc_macro2::TokenStream;
 use quote::format_ident;
@@ -411,28 +412,27 @@ pub fn from_arg_buffer(
   let err = format_ident!("{}_err", arg_ident);
   let throw_exception = throw_type_error_static_string(generator_state, &err)?;
 
-  let GeneratorState {
-    deno_core,
-    scope,
-    needs_scope,
-    ..
-  } = generator_state;
-
-  *needs_scope = true;
+  generator_state.needs_scope = true;
 
   // TODO(mmastrac): Other buffer types
   let array = NumericArg::u8
     .v8_array_type()
     .expect("Could not retrieve the v8 type");
 
-  let make_v8slice = quote! {
-    let mut #arg_ident = match unsafe { #deno_core::_ops::to_v8_slice::<#deno_core::v8::#array>(&mut #scope, #arg_ident) } {
+  let to_v8_slice = if matches!(buffer, Buffer::JsBuffer(BufferMode::Detach)) {
+    quote!(to_v8_slice_detachable)
+  } else {
+    quote!(to_v8_slice)
+  };
+
+  let make_v8slice = gs_quote!(generator_state(deno_core, scope) => {
+    let mut #arg_ident = match unsafe { #deno_core::_ops::#to_v8_slice::<#deno_core::v8::#array>(&mut #scope, #arg_ident) } {
       Ok(#arg_ident) => #arg_ident,
       Err(#err) => {
         #throw_exception
       }
     };
-  };
+  });
 
   let make_arg = match buffer {
     Buffer::Slice(_, NumericArg::u8) => {
@@ -444,10 +444,12 @@ pub fn from_arg_buffer(
     Buffer::BoxSlice(NumericArg::u8) => {
       quote!(let #arg_ident = #arg_ident.to_boxed_slice();)
     }
-    Buffer::Bytes => {
-      // TODO(mmastrac): This assumes #[buffer(copy)]
+    Buffer::Bytes(BufferMode::Copy) => {
       quote!(let #arg_ident = #arg_ident.to_vec().into();)
     }
+    Buffer::JsBuffer(BufferMode::Default | BufferMode::Detach) => {
+      gs_quote!(generator_state(deno_core) => (let #arg_ident = #deno_core::serde_v8::JsBuffer::from_parts(#arg_ident);))
+    }
     _ => {
       return Err(V8MappingError::NoMapping(
         "a buffer argument",
@@ -499,37 +501,34 @@ pub fn return_value_infallible(
     ..
   } = generator_state;
 
+  // In the future we may be able to make this false for void again
+  *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.
-      *needs_retval = true;
       quote! {#retval.set_null();}
     }
     Arg::Numeric(NumericArg::bool) => {
-      *needs_retval = true;
       quote!(#retval.set_bool(#result as bool);)
     }
     Arg::Numeric(NumericArg::u8)
     | Arg::Numeric(NumericArg::u16)
     | Arg::Numeric(NumericArg::u32) => {
-      *needs_retval = true;
       quote!(#retval.set_uint32(#result as u32);)
     }
     Arg::Numeric(NumericArg::i8)
     | Arg::Numeric(NumericArg::i16)
     | Arg::Numeric(NumericArg::i32) => {
-      *needs_retval = true;
       quote!(#retval.set_int32(#result as i32);)
     }
     Arg::Numeric(NumericArg::f32 | NumericArg::f64) => {
-      *needs_retval = true;
       quote!(#retval.set_double(#result as _);)
     }
     Arg::Special(Special::String) => {
-      *needs_retval = true;
       *needs_scope = true;
       quote! {
         if #result.is_empty() {
@@ -544,7 +543,6 @@ pub fn return_value_infallible(
       }
     }
     Arg::OptionNumeric(n) => {
-      *needs_retval = true;
       *needs_scope = true;
       let some = return_value_infallible(generator_state, &Arg::Numeric(*n))?;
       gs_quote!(generator_state(result, retval) => {
@@ -556,7 +554,6 @@ pub fn return_value_infallible(
       })
     }
     Arg::Option(Special::String) => {
-      *needs_retval = true;
       *needs_scope = true;
       let some = return_value_infallible(
         generator_state,
@@ -571,7 +568,6 @@ pub fn return_value_infallible(
       })
     }
     Arg::OptionV8Local(_) => {
-      *needs_retval = true;
       quote! {
         if let Some(#result) = #result {
           // We may have a non v8::Value here
@@ -582,14 +578,12 @@ pub fn return_value_infallible(
       }
     }
     Arg::V8Local(_) => {
-      *needs_retval = true;
       quote! {
         // We may have a non v8::Value here
         #retval.set(#result.into())
       }
     }
     Arg::SerdeV8(_class) => {
-      *needs_retval = true;
       *needs_scope = true;
 
       let deno_core = deno_core.clone();
@@ -609,6 +603,13 @@ pub fn return_value_infallible(
         #retval.set(#result.into())
       }
     }
+    Arg::Buffer(
+      Buffer::JsBuffer(BufferMode::Default)
+      | Buffer::Vec(NumericArg::u8)
+      | Buffer::BoxSlice(NumericArg::u8),
+    ) => {
+      quote! { #retval.set(#deno_core::_ops::ToV8Value::to_v8_value(#result, &mut #scope)); }
+    }
     _ => {
       return Err(V8MappingError::NoMapping(
         "a slow return value",
@@ -639,6 +640,13 @@ pub fn return_value_v8_value(
     Arg::Numeric(NumericArg::u8 | NumericArg::u16 | NumericArg::u32) => {
       quote!(Ok(#deno_core::v8::Integer::new_from_unsigned(#scope, #result).into()))
     }
+    Arg::Buffer(
+      Buffer::JsBuffer(BufferMode::Default)
+      | Buffer::Vec(NumericArg::u8)
+      | Buffer::BoxSlice(NumericArg::u8),
+    ) => {
+      quote!(Ok(#deno_core::_ops::ToV8Value::to_v8_value(#result, #scope)))
+    }
     _ => {
       return Err(V8MappingError::NoMapping(
         "a v8 return value",
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 5bbc2df..3a95126 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -427,14 +427,7 @@ deno_ops_compile_test_runner::prelude!();";
     }
   }
 
-  #[test]
-  fn test_valid_args_md() {
-    let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
-    let md = include_str!("valid_args.md");
-    let separator = "\n<!-- START -->\n";
-    let header = include_str!("README.md").split(separator).next().unwrap();
-    let mut actual = format!("{header}{separator}<table><tr><th>Rust</th><th>Fastcall</th><th>v8</th></tr>\n");
-
+  fn parse_md(md: &str, mut f: impl FnMut(&str, Vec<&str>)) {
     // Skip the header and table line
     for line in md.split('\n').skip(2).filter(|s| {
       !s.trim().is_empty() && !s.split('|').nth(1).unwrap().trim().is_empty()
@@ -454,39 +447,132 @@ deno_ops_compile_test_runner::prelude!();";
           .skip(2)
           .map(|s| s.trim())
           .collect::<Vec<_>>();
-        let type_param = components.first().unwrap().to_owned();
-        let fastcall = components.get(1).unwrap().to_owned();
-        let fast = fastcall == "X";
-        let v8 = components.get(2).unwrap().to_owned();
-        let notes = components.get(3).unwrap().to_owned();
-        let (attr, ty) = if type_param.starts_with('#') {
-          type_param.split_once(' ').expect(
-            "Expected an attribute separated by a space (ie: #[attr] type)",
-          )
-        } else {
-          ("", type_param)
-        };
-
-        if !line.contains("...") {
-          let function = format!("fn op_test({} x: {}) {{}}", attr, ty);
-          let function =
-            syn2::parse_str::<ItemFn>(&function).expect("Failed to parse type");
-          let sig = parse_signature(vec![], function.sig.clone())
-            .expect("Failed to parse signature");
-          println!("Parsed signature: {sig:?}");
-          generate_op2(
-            MacroConfig {
-              fast,
-              ..Default::default()
-            },
-            function,
-          )
-          .expect("Failed to generate op");
-        }
-        actual += &format!("<tr>\n<td>\n\n```rust\n{}\n```\n\n</td><td>\n{}\n</td><td>\n{}\n</td><td>\n{}\n</td></tr>\n", type_param, if fast { "✅" } else { "" }, v8, notes);
+        f(&line, components)
+      }
+    }
+  }
+
+  fn split_readme<'a>(
+    readme: &'a str,
+    start_separator: &str,
+    end_separator: &str,
+  ) -> (&'a str, &'a str) {
+    let mut parts = readme.split(start_separator);
+    let header = parts.next().unwrap();
+    let remainder = parts.next().unwrap().split(end_separator).nth(1).unwrap();
+    (header, remainder)
+  }
+
+  pub static README_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
+
+  #[test]
+  fn test_valid_args_md() {
+    let _readme_lock = README_LOCK.lock().unwrap();
+    let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
+    let md = include_str!("valid_args.md");
+    let separator = "\n<!-- START ARGS -->\n";
+    let end_separator = "\n<!-- END ARGS -->\n";
+    let (header, remainder) =
+      split_readme(include_str!("README.md"), separator, end_separator);
+
+    let mut actual = format!("{header}{separator}<table><tr><th>Rust</th><th>Fastcall</th><th>v8</th></tr>\n");
+
+    parse_md(md, |line, components| {
+      let type_param = components.first().unwrap().to_owned();
+      let fastcall = components.get(1).unwrap().to_owned();
+      let fast = fastcall == "X";
+      let v8 = components.get(2).unwrap().to_owned();
+      let notes = components.get(3).unwrap().to_owned();
+      let (attr, ty) = if type_param.starts_with('#') {
+        type_param.split_once(' ').expect(
+          "Expected an attribute separated by a space (ie: #[attr] type)",
+        )
+      } else {
+        ("", type_param)
+      };
+
+      if !line.contains("...") {
+        let function = format!("fn op_test({} x: {}) {{}}", attr, ty);
+        let function =
+          syn2::parse_str::<ItemFn>(&function).expect("Failed to parse type");
+        let sig = parse_signature(vec![], function.sig.clone())
+          .expect("Failed to parse signature");
+        println!("Parsed signature: {sig:?}");
+        generate_op2(
+          MacroConfig {
+            fast,
+            ..Default::default()
+          },
+          function,
+        )
+        .expect("Failed to generate op");
       }
+      actual += &format!("<tr>\n<td>\n\n```rust\n{}\n```\n\n</td><td>\n{}\n</td><td>\n{}\n</td><td>\n{}\n</td></tr>\n", type_param, if fast { "✅" } else { "" }, v8, notes);
+    });
+    actual += "</table>\n";
+    actual += end_separator;
+    actual += remainder;
+
+    if update_expected {
+      std::fs::write("op2/README.md", actual)
+        .expect("Failed to write expectation file");
+    } else {
+      let expected = std::fs::read_to_string("op2/README.md")
+        .expect("Failed to read expectation file");
+      assert_eq!(
+        expected, actual,
+        "Failed to match expectation. Use UPDATE_EXPECTED=1."
+      );
     }
+  }
+
+  #[test]
+  fn test_valid_retvals_md() {
+    let _readme_lock = README_LOCK.lock().unwrap();
+    let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
+    let md = include_str!("valid_retvals.md");
+    let separator = "\n<!-- START RV -->\n";
+    let end_separator = "\n<!-- END RV -->\n";
+    let (header, remainder) =
+      split_readme(include_str!("README.md"), separator, end_separator);
+    let mut actual = format!("{header}{separator}<table><tr><th>Rust</th><th>Fastcall</th><th>v8</th></tr>\n");
+
+    parse_md(md, |line, components| {
+      let type_param = components.first().unwrap().to_owned();
+      let fastcall = components.get(1).unwrap().to_owned();
+      let fast = fastcall == "X";
+      let v8 = components.get(2).unwrap().to_owned();
+      let notes = components.get(3).unwrap().to_owned();
+      let (attr, ty) = if type_param.starts_with('#') {
+        type_param.split_once(' ').expect(
+          "Expected an attribute separated by a space (ie: #[attr] type)",
+        )
+      } else {
+        ("", type_param)
+      };
+
+      if !line.contains("...") {
+        let function = format!("{} fn op_test() -> {} {{}}", attr, ty);
+        let function =
+          syn2::parse_str::<ItemFn>(&function).expect("Failed to parse type");
+        let sig = parse_signature(function.attrs.clone(), function.sig.clone())
+          .expect("Failed to parse signature");
+        println!("Parsed signature: {sig:?}");
+        generate_op2(
+          MacroConfig {
+            fast,
+            ..Default::default()
+          },
+          function,
+        )
+        .expect("Failed to generate op");
+      }
+      actual += &format!("<tr>\n<td>\n\n```rust\n{}\n```\n\n</td><td>\n{}\n</td><td>\n{}\n</td><td>\n{}\n</td></tr>\n", type_param, if fast { "✅" } else { "" }, v8, notes);
+    });
+
     actual += "</table>\n";
+    actual += end_separator;
+    actual += remainder;
 
     if update_expected {
       std::fs::write("op2/README.md", actual)
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 45f28cf..6871f40 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -157,33 +157,53 @@ pub enum Buffer {
   /// Owned, copy. [`Vec<u8>`], [`Vec<u32>`], etc...
   Vec(NumericArg),
   /// Maybe shared or a copy. Stored in `bytes::Bytes`
-  Bytes,
+  Bytes(BufferMode),
   /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::V8Slice`
-  V8Slice,
-  /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::JSBuffer`
-  JSBuffer,
+  V8Slice(BufferMode),
+  /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::JsBuffer`
+  JsBuffer(BufferMode),
 }
 
 impl Buffer {
-  fn is_valid_mode(&self, mode: BufferMode) -> bool {
+  fn is_valid_mode(&self, position: Position, mode: BufferMode) -> bool {
+    match position {
+      Position::Arg => match self {
+        Buffer::Bytes(..) => matches!(mode, BufferMode::Copy),
+        Buffer::JsBuffer(..) => matches!(
+          mode,
+          BufferMode::Copy | BufferMode::Detach | BufferMode::Default
+        ),
+        Buffer::V8Slice(..) => matches!(
+          mode,
+          BufferMode::Copy | BufferMode::Detach | BufferMode::Default
+        ),
+        Buffer::Vec(..) => matches!(mode, BufferMode::Copy),
+        Buffer::BoxSlice(..) => matches!(mode, BufferMode::Copy),
+        Buffer::Slice(..) => {
+          matches!(mode, BufferMode::Detach | BufferMode::Default)
+        }
+        Buffer::Ptr(..) => {
+          matches!(mode, BufferMode::Detach | BufferMode::Default)
+        }
+      },
+      Position::RetVal => match self {
+        Buffer::Bytes(..) => matches!(mode, BufferMode::Default),
+        Buffer::JsBuffer(..) => matches!(mode, BufferMode::Default),
+        Buffer::V8Slice(..) => matches!(mode, BufferMode::Default),
+        Buffer::Vec(..) => matches!(mode, BufferMode::Default),
+        Buffer::BoxSlice(..) => matches!(mode, BufferMode::Default),
+        Buffer::Slice(..) => false,
+        Buffer::Ptr(..) => false,
+      },
+    }
+  }
+
+  fn update_mode(&mut self, new_mode: BufferMode) {
     match self {
-      Buffer::Bytes => matches!(mode, BufferMode::Copy),
-      Buffer::JSBuffer => matches!(
-        mode,
-        BufferMode::Copy | BufferMode::Detach | BufferMode::Unsafe
-      ),
-      Buffer::V8Slice => matches!(
-        mode,
-        BufferMode::Copy | BufferMode::Detach | BufferMode::Unsafe
-      ),
-      Buffer::Vec(..) => matches!(mode, BufferMode::Copy),
-      Buffer::BoxSlice(..) => matches!(mode, BufferMode::Copy),
-      Buffer::Slice(..) => {
-        matches!(mode, BufferMode::Detach | BufferMode::Unsafe)
-      }
-      Buffer::Ptr(..) => {
-        matches!(mode, BufferMode::Detach | BufferMode::Unsafe)
-      }
+      Buffer::Bytes(ref mut mode) => *mode = new_mode,
+      Buffer::JsBuffer(ref mut mode) => *mode = new_mode,
+      Buffer::V8Slice(ref mut mode) => *mode = new_mode,
+      _ => {}
     }
   }
 }
@@ -354,6 +374,8 @@ pub struct ParsedSignature {
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum BufferMode {
+  /// Default mode.
+  Default,
   /// Unsafely shared buffers that may possibly change on the JavaScript side upon re-entry into
   /// V8. Rust code should not treat these as traditional buffers.
   Unsafe,
@@ -472,6 +494,15 @@ pub(crate) struct Attributes {
   primary: Option<AttributeModifier>,
 }
 
+/// Where is this type defined?
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum Position {
+  /// Argument
+  Arg,
+  /// Return value
+  RetVal,
+}
+
 impl Attributes {
   pub fn string() -> Self {
     Self {
@@ -682,7 +713,7 @@ fn parse_attribute(
       (#[smi]) => Some(AttributeModifier::Smi),
       (#[string]) => Some(AttributeModifier::String),
       (#[state]) => Some(AttributeModifier::State),
-      (#[buffer]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe)),
+      (#[buffer]) => Some(AttributeModifier::Buffer(BufferMode::Default)),
       (#[buffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe)),
       (#[buffer(copy)]) => Some(AttributeModifier::Buffer(BufferMode::Copy)),
       (#[buffer(detach)]) => Some(AttributeModifier::Buffer(BufferMode::Detach)),
@@ -710,6 +741,7 @@ fn parse_numeric_type(tp: &Path) -> Result<NumericArg, ArgError> {
 /// Parse a raw type into a container + type, allowing us to simplify the typechecks elsewhere in
 /// this code.
 fn parse_type_path(
+  position: Position,
   attrs: Attributes,
   is_ref: bool,
   tp: &TypePath,
@@ -720,7 +752,7 @@ fn parse_type_path(
   use syn2 as syn;
 
   let tokens = tp.clone().into_token_stream();
-  let res = if let Ok(numeric) = parse_numeric_type(&tp.path) {
+  let mut res = if let Ok(numeric) = parse_numeric_type(&tp.path) {
     CBare(TNumeric(numeric))
   } else {
     std::panic::catch_unwind(|| {
@@ -742,13 +774,13 @@ fn parse_type_path(
         Ok(CBare(TBuffer(Buffer::BoxSlice(parse_numeric_type(&ty)?))))
       }
       ( $( serde_v8 :: )? V8Slice ) => {
-        Ok(CBare(TBuffer(Buffer::V8Slice)))
+        Ok(CBare(TBuffer(Buffer::V8Slice(BufferMode::Default))))
       }
-      ( $( serde_v8 :: )? JSBuffer ) => {
-        Ok(CBare(TBuffer(Buffer::JSBuffer)))
+      ( $( serde_v8 :: )? JsBuffer ) => {
+        Ok(CBare(TBuffer(Buffer::JsBuffer(BufferMode::Default))))
       }
       ( $( bytes :: )? Bytes ) => {
-        Ok(CBare(TBuffer(Buffer::Bytes)))
+        Ok(CBare(TBuffer(Buffer::Bytes(BufferMode::Default))))
       }
       ( $( std :: ffi :: )? c_void ) => Ok(CBare(TNumeric(NumericArg::__VOID__))),
       ( OpState ) => Ok(CBare(TSpecial(Special::OpState))),
@@ -757,9 +789,9 @@ fn parse_type_path(
       ( v8 :: Local < $( $_scope:lifetime , )? v8 :: $v8:ident >) => Ok(CV8Local(TV8(parse_v8_type(&v8)?))),
       ( v8 :: Global < $( $_scope:lifetime , )? v8 :: $v8:ident >) => Ok(CV8Global(TV8(parse_v8_type(&v8)?))),
       ( v8 :: $v8:ident ) => Ok(CBare(TV8(parse_v8_type(&v8)?))),
-      ( Rc < RefCell < $ty:ty > > ) => Ok(CRcRefCell(TSpecial(parse_type_special(attrs, &ty)?))),
+      ( Rc < RefCell < $ty:ty > > ) => Ok(CRcRefCell(TSpecial(parse_type_special(position, attrs, &ty)?))),
       ( Option < $ty:ty > ) => {
-        match parse_type(attrs, &ty)? {
+        match parse_type(position, attrs, &ty)? {
           Arg::Special(special) => Ok(COption(TSpecial(special))),
           Arg::Numeric(numeric) => Ok(COption(TNumeric(numeric))),
           Arg::Buffer(buffer) => Ok(COption(TBuffer(buffer))),
@@ -803,14 +835,15 @@ fn parse_type_path(
         return Err(ArgError::MissingStringAttribute);
       }
     }
-    CBare(TBuffer(buffer)) => {
+    CBare(TBuffer(ref mut buffer)) => {
       if let Some(AttributeModifier::Buffer(mode)) = attrs.primary {
-        if !buffer.is_valid_mode(mode) {
+        if !buffer.is_valid_mode(position, mode) {
           return Err(ArgError::InvalidBufferMode(
             format!("{mode:?}"),
             stringify_token(tp),
           ));
         }
+        buffer.update_mode(mode)
       } else {
         return Err(ArgError::MissingBufferAttribute);
       }
@@ -843,10 +876,11 @@ fn parse_v8_type(v8: &Ident) -> Result<V8Arg, ArgError> {
 }
 
 fn parse_type_special(
+  position: Position,
   attrs: Attributes,
   ty: &Type,
 ) -> Result<Special, ArgError> {
-  match parse_type(attrs, ty)? {
+  match parse_type(position, attrs, ty)? {
     Arg::Special(special) => Ok(special),
     _ => Err(ArgError::InvalidType(stringify_token(ty))),
   }
@@ -880,6 +914,7 @@ fn parse_type_state(ty: &Type) -> Result<Arg, ArgError> {
 }
 
 pub(crate) fn parse_type(
+  position: Position,
   attrs: Attributes,
   ty: &Type,
 ) -> Result<Arg, ArgError> {
@@ -894,13 +929,15 @@ pub(crate) fn parse_type(
         }
         Type::Path(of) => {
           // If this type will parse without #[serde] (or with #[string]), it is illegal to use this type with #[serde]
-          if parse_type_path(Attributes::default(), false, of).is_ok() {
+          if parse_type_path(position, Attributes::default(), false, of).is_ok()
+          {
             return Err(ArgError::InvalidSerdeAttributeType(stringify_token(
               ty,
             )));
           }
           // If this type will parse without #[serde] (or with #[string]), it is illegal to use this type with #[serde]
-          if parse_type_path(Attributes::string(), false, of).is_ok() {
+          if parse_type_path(position, Attributes::string(), false, of).is_ok()
+          {
             return Err(ArgError::InvalidSerdeAttributeType(stringify_token(
               ty,
             )));
@@ -964,7 +1001,7 @@ pub(crate) fn parse_type(
               numeric => {
                 if let Some(AttributeModifier::Buffer(mode)) = attrs.primary {
                   let buffer = Buffer::Slice(mut_type, numeric);
-                  if !buffer.is_valid_mode(mode) {
+                  if !buffer.is_valid_mode(position, mode) {
                     return Err(ArgError::InvalidBufferMode(
                       format!("{mode:?}"),
                       stringify_token(ty),
@@ -980,7 +1017,7 @@ pub(crate) fn parse_type(
             Err(ArgError::InvalidType(stringify_token(ty)))
           }
         }
-        Type::Path(of) => match parse_type_path(attrs, true, of)? {
+        Type::Path(of) => match parse_type_path(position, attrs, true, of)? {
           CBare(TSpecial(Special::RefStr)) => Ok(Arg::Special(Special::RefStr)),
           COption(TSpecial(Special::RefStr)) => {
             Ok(Arg::Option(Special::RefStr))
@@ -999,14 +1036,14 @@ pub(crate) fn parse_type(
         RefType::Ref
       };
       match &*of.elem {
-        Type::Path(of) => match parse_type_path(attrs, false, of)? {
+        Type::Path(of) => match parse_type_path(position, attrs, false, of)? {
           CBare(TNumeric(numeric)) if numeric == NumericArg::__VOID__ => {
             Ok(Arg::External(External::Ptr(mut_type)))
           }
           CBare(TNumeric(numeric)) => {
             if let Some(AttributeModifier::Buffer(mode)) = attrs.primary {
               let buffer = Buffer::Ptr(mut_type, numeric);
-              if !buffer.is_valid_mode(mode) {
+              if !buffer.is_valid_mode(position, mode) {
                 return Err(ArgError::InvalidBufferMode(
                   format!("{mode:?}"),
                   stringify_token(ty),
@@ -1022,7 +1059,7 @@ pub(crate) fn parse_type(
         _ => Err(ArgError::InvalidType(stringify_token(ty))),
       }
     }
-    Type::Path(of) => match parse_type_path(attrs, false, of)? {
+    Type::Path(of) => match parse_type_path(position, attrs, false, of)? {
       CBare(TNumeric(numeric)) => Ok(Arg::Numeric(numeric)),
       CBare(TSpecial(special)) => Ok(Arg::Special(special)),
       CBare(TBuffer(buffer)) => Ok(Arg::Buffer(buffer)),
@@ -1045,7 +1082,7 @@ fn parse_arg(arg: FnArg) -> Result<Arg, ArgError> {
   let FnArg::Typed(typed) = arg else {
     return Err(ArgError::InvalidSelf);
   };
-  parse_type(parse_attributes(&typed.attrs)?, &typed.ty)
+  parse_type(Position::Arg, parse_attributes(&typed.attrs)?, &typed.ty)
 }
 
 #[cfg(test)]
@@ -1221,8 +1258,8 @@ mod tests {
     (State(Ref, Something), OptionState(Ref, Something)) -> Infallible(Void)
   );
   test!(
-    #[buffer(copy)] fn op_buffers(#[buffer(copy)] a: Vec<u8>, #[buffer(copy)] b: Box<[u8]>, #[buffer(copy)] c: bytes::Bytes, #[buffer] d: V8Slice, #[buffer] e: JSBuffer) -> Vec<u8>;
-    (Buffer(Vec(u8)), Buffer(BoxSlice(u8)), Buffer(Bytes), Buffer(V8Slice), Buffer(JSBuffer)) -> Infallible(Buffer(Vec(u8)))
+    #[buffer] fn op_buffers(#[buffer(copy)] a: Vec<u8>, #[buffer(copy)] b: Box<[u8]>, #[buffer(copy)] c: bytes::Bytes, #[buffer] d: V8Slice, #[buffer] e: JsBuffer, #[buffer(detach)] f: JsBuffer) -> Vec<u8>;
+    (Buffer(Vec(u8)), Buffer(BoxSlice(u8)), Buffer(Bytes(Copy)), Buffer(V8Slice(Default)), Buffer(JsBuffer(Default)), Buffer(JsBuffer(Detach))) -> Infallible(Buffer(Vec(u8)))
   );
   test!(
     async fn op_async_void();
diff --git a/ops/op2/signature_retval.rs b/ops/op2/signature_retval.rs
index df2e00a..64bff7a 100644
--- a/ops/op2/signature_retval.rs
+++ b/ops/op2/signature_retval.rs
@@ -70,12 +70,18 @@ pub(crate) fn parse_return(
   let res = match rt {
     ReturnType::Default => RetVal::Infallible(Arg::Void),
     ReturnType::Type(_, rt) => match unwrap_return(rt)? {
-      Type(ty) => RetVal::Infallible(parse_type(attrs, &ty)?),
+      Type(ty) => RetVal::Infallible(parse_type(Position::RetVal, attrs, &ty)?),
       Result(ty) => match unwrap_return(&ty)? {
-        Type(ty) => RetVal::Result(parse_type(attrs, &ty)?),
+        Type(ty) => RetVal::Result(parse_type(Position::RetVal, attrs, &ty)?),
         Future(ty) => match unwrap_return(&ty)? {
-          Type(ty) => RetVal::ResultFuture(parse_type(attrs, &ty)?),
-          Result(ty) => RetVal::ResultFutureResult(parse_type(attrs, &ty)?),
+          Type(ty) => {
+            RetVal::ResultFuture(parse_type(Position::RetVal, attrs, &ty)?)
+          }
+          Result(ty) => RetVal::ResultFutureResult(parse_type(
+            Position::RetVal,
+            attrs,
+            &ty,
+          )?),
           _ => {
             return Err(RetError::InvalidType(ArgError::InvalidType(
               stringify_token(rt),
@@ -89,8 +95,10 @@ pub(crate) fn parse_return(
         }
       },
       Future(ty) => match unwrap_return(&ty)? {
-        Type(ty) => RetVal::Future(parse_type(attrs, &ty)?),
-        Result(ty) => RetVal::FutureResult(parse_type(attrs, &ty)?),
+        Type(ty) => RetVal::Future(parse_type(Position::RetVal, attrs, &ty)?),
+        Result(ty) => {
+          RetVal::FutureResult(parse_type(Position::RetVal, attrs, &ty)?)
+        }
         _ => {
           return Err(RetError::InvalidType(ArgError::InvalidType(
             stringify_token(rt),
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index fd1aaf7..9e3e7a7 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -7,6 +7,8 @@
 | X         | u16                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
 | X         | i32                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
 | X         | u32                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | i64                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | u64                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
 | X         | isize                            | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
 | X         | usize                            | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
 | X         | f32                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
@@ -30,8 +32,8 @@
 |           | #[buffer] V8Slice                | X        | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
 |           | #[buffer(detach)] V8Slice        | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
 |           | #[buffer] V8ResizableSlice       | X        | ArrayBuffer, ArrayBufferView (resizable=true)       | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
-|           | #[buffer] JSBuffer               | X        | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
-|           | #[buffer(detach)] JSBuffer       | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
+| X         | #[buffer] JsBuffer               |          | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
+| X         | #[buffer(detach)] JsBuffer       |          | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
 |           | #[buffer(unsafe)] bytes::Bytes   | X        | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of the buffer.                                                                                                                                                                    |
 |           | #[buffer(detach)] bytes::Bytes   | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
 | X         | &OpState                         | X        |                                                     |                                                                                                                                                                                                                |
diff --git a/ops/op2/valid_retvals.md b/ops/op2/valid_retvals.md
new file mode 100644
index 0000000..f96c728
--- /dev/null
+++ b/ops/op2/valid_retvals.md
@@ -0,0 +1,24 @@
+| 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                        |       |
+|           | i64                              | X        | Uint32, Int32, Number, BigInt |       |
+|           | u64                              | X        | Uint32, Int32, Number, BigInt |       |
+|           | isize                            | X        | Uint32, Int32, Number, BigInt |       |
+|           | usize                            | X        | Uint32, Int32, Number, BigInt |       |
+| X         | f32                              | X        | Number                        |       |
+| X         | f64                              | X        | Number                        |       |
+| X         | #[string] String                 |          | String                        |       |
+|           | #[string] &str                   |          | String                        |       |
+|           | #[string] Cow<str>               |          | String                        |       |
+| 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               |          | any                           |       |
+| X         | #[serde] (Tuple, Tuple)          |          | any                           |       |
diff --git a/serde_v8/magic/buffer.rs b/serde_v8/magic/buffer.rs
index 032a3be..76598c6 100644
--- a/serde_v8/magic/buffer.rs
+++ b/serde_v8/magic/buffer.rs
@@ -13,6 +13,17 @@ pub struct JsBuffer(V8Slice);
 
 impl_magic!(JsBuffer);
 
+impl JsBuffer {
+  pub fn from_parts(slice: V8Slice) -> Self {
+    Self(slice)
+  }
+
+  /// Returns the buffer to its parts.
+  pub fn into_parts(self) -> V8Slice {
+    self.0
+  }
+}
+
 impl Debug for JsBuffer {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     f.debug_list().entries(self.0.as_ref().iter()).finish()
@@ -65,6 +76,12 @@ impl From<JsBuffer> for bytes::Bytes {
   }
 }
 
+impl From<V8Slice> for JsBuffer {
+  fn from(value: V8Slice) -> Self {
+    JsBuffer(value)
+  }
+}
+
 // NOTE(bartlomieju): we use Option here, because `to_v8()` uses `&mut self`
 // instead of `self` which is dictated by the `serde` API.
 #[derive(Debug)]
diff --git a/serde_v8/magic/v8slice.rs b/serde_v8/magic/v8slice.rs
index dc6e4b8..9ea38c6 100644
--- a/serde_v8/magic/v8slice.rs
+++ b/serde_v8/magic/v8slice.rs
@@ -93,6 +93,11 @@ impl V8Slice {
   pub fn to_boxed_slice(&self) -> Box<[u8]> {
     self.to_vec().into_boxed_slice()
   }
+
+  /// Returns the slice to the parts it came from.
+  pub fn into_parts(self) -> (v8::SharedRef<v8::BackingStore>, Range<usize>) {
+    (self.store, self.range)
+  }
 }
 
 pub(crate) fn to_ranged_buffer<'s>(


Test patch:
diff --git a/core/runtime/tests/ops.rs b/core/runtime/tests/ops.rs
index 9ffe1b4..6a644d2 100644
--- a/core/runtime/tests/ops.rs
+++ b/core/runtime/tests/ops.rs
@@ -15,7 +15,7 @@ use std::sync::atomic::Ordering;
 async fn test_async_opstate_borrow() {
   struct InnerState(u64);
 
-  #[op]
+  #[op2(async, core)]
   async fn op_async_borrow(
     op_state: Rc<RefCell<OpState>>,
   ) -> Result<(), Error> {
@@ -53,9 +53,9 @@ async fn test_async_opstate_borrow() {
 
 #[tokio::test]
 async fn test_sync_op_serialize_object_with_numbers_as_keys() {
-  #[op]
+  #[op2(core)]
   fn op_sync_serialize_object_with_numbers_as_keys(
-    value: serde_json::Value,
+    #[serde] value: serde_json::Value,
   ) -> Result<(), Error> {
     assert_eq!(
       value.to_string(),
@@ -95,9 +95,9 @@ lines: {
 
 #[tokio::test]
 async fn test_async_op_serialize_object_with_numbers_as_keys() {
-  #[op]
+  #[op2(async, core)]
   async fn op_async_serialize_object_with_numbers_as_keys(
-    value: serde_json::Value,
+    #[serde] value: serde_json::Value,
   ) -> Result<(), Error> {
     assert_eq!(
       value.to_string(),
@@ -138,7 +138,8 @@ lines: {
 
 #[test]
 fn test_op_return_serde_v8_error() {
-  #[op]
+  #[op2(core)]
+  #[serde]
   fn op_err() -> Result<std::collections::BTreeMap<u64, u64>, anyhow::Error> {
     Ok([(1, 2), (3, 4)].into_iter().collect()) // Maps can't have non-string keys in serde_v8
   }
@@ -206,15 +207,16 @@ fn test_op_disabled() {
 
 #[test]
 fn test_op_detached_buffer() {
-  use serde_v8::DetachedBuffer;
-
-  #[op]
-  fn op_sum_take(b: DetachedBuffer) -> Result<u64, anyhow::Error> {
-    Ok(b.as_ref().iter().clone().map(|x| *x as u64).sum())
+  #[op2(core)]
+  fn op_sum_take(#[buffer(detach)] b: JsBuffer) -> Result<u32, anyhow::Error> {
+    Ok(b.as_ref().iter().clone().map(|x| *x as u32).sum())
   }
 
-  #[op]
-  fn op_boomerang(b: DetachedBuffer) -> Result<DetachedBuffer, anyhow::Error> {
+  #[op2(core)]
+  #[buffer]
+  fn op_boomerang(
+    #[buffer(detach)] b: JsBuffer,
+  ) -> Result<JsBuffer, anyhow::Error> {
     Ok(b)
   }
 
@@ -252,6 +254,14 @@ fn test_op_detached_buffer() {
       if (a2.byteLength > 0 || a2b.byteLength > 0) {
         throw new Error("expecting a2 & a2b to be detached, a3 re-attached");
       }
+      "#,
+    )
+    .unwrap();
+
+  runtime
+    .execute_script_static(
+      "test.js",
+      r#"
       const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 });
       const w32 = new Uint32Array(wmem.buffer);
       w32[0] = 1; w32[1] = 2; w32[2] = 3;
@@ -260,7 +270,7 @@ fn test_op_detached_buffer() {
           let sum = Deno.core.ops.op_sum_take(w32.subarray(0, 2));
           return false;
         } catch(e) {
-          return e.message.includes('invalid type; expected: detachable');
+          return e.message.includes('expected');
         }
       });
       if (!assertWasmThrow()) {
@@ -314,14 +324,16 @@ fn duplicate_op_names() {
   mod a {
     use super::*;
 
-    #[op]
-    fn op_test() -> Result<String, Error> {
+    #[op2(core)]
+    #[string]
+    pub fn op_test() -> Result<String, Error> {
       Ok(String::from("Test"))
     }
   }
 
-  #[op]
-  fn op_test() -> Result<String, Error> {
+  #[op2(core)]
+  #[string]
+  pub fn op_test() -> Result<String, Error> {
     Ok(String::from("Test"))
   }
 
@@ -334,7 +346,8 @@ fn duplicate_op_names() {
 
 #[test]
 fn ops_in_js_have_proper_names() {
-  #[op]
+  #[op2(core)]
+  #[string]
   fn op_test_sync() -> Result<String, Error> {
     Ok(String::from("Test"))
   }
diff --git a/ops/op2/test_cases/async/async_arg_return.out b/ops/op2/test_cases/async/async_arg_return.out
index b091ba1..d3efb53 100644
--- a/ops/op2/test_cases/async/async_arg_return.out
+++ b/ops/op2/test_cases/async/async_arg_return.out
@@ -37,17 +37,17 @@ impl op_async {
         let result = {
             let arg0 = args.get(1usize as i32);
             let Some(arg0) = deno_core::_ops::to_i32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected i32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected i32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0 as _;
             Self::call(arg0)
         };
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 97ef5c8..dcdfc32 100644
--- a/ops/op2/test_cases/async/async_arg_return_result.out
+++ b/ops/op2/test_cases/async/async_arg_return_result.out
@@ -37,17 +37,17 @@ impl op_async {
         let result = {
             let arg0 = args.get(1usize as i32);
             let Some(arg0) = deno_core::_ops::to_i32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected i32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected i32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0 as _;
             Self::call(arg0)
         };
diff --git a/ops/op2/test_cases/async/async_jsbuffer.out b/ops/op2/test_cases/async/async_jsbuffer.out
new file mode 100644
index 0000000..0ebcd37
--- /dev/null
+++ b/ops/op2/test_cases/async/async_jsbuffer.out
@@ -0,0 +1,79 @@
+#[allow(non_camel_case_types)]
+pub struct op_async_v8_buffer {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_async_v8_buffer {
+    const NAME: &'static str = stringify!(op_async_v8_buffer);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_async_v8_buffer),
+        true,
+        false,
+        false,
+        2usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_async_v8_buffer {
+    pub const fn name() -> &'static str {
+        stringify!(op_async_v8_buffer)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+            &*info
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+                as *const deno_core::_ops::OpCtx)
+        };
+        let result = {
+            let arg0 = args.get(1usize as i32);
+            let mut arg0 = match unsafe {
+                deno_core::_ops::to_v8_slice::<
+                    deno_core::v8::Uint8Array,
+                >(&mut scope, arg0)
+            } {
+                Ok(arg0) => arg0,
+                Err(arg0_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                    let msg = deno_core::v8::String::new_from_one_byte(
+                            &mut scope,
+                            arg0_err.as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return;
+                }
+            };
+            let arg0 = deno_core::serde_v8::JsBuffer::from_parts(arg0);
+            Self::call(arg0)
+        };
+        let promise_id = deno_core::_ops::to_i32_option(&args.get(0))
+            .unwrap_or_default();
+        if let Some(result)
+            = deno_core::_ops::map_async_op_infallible(
+                opctx,
+                promise_id,
+                result,
+                |scope, result| {
+                    Ok(deno_core::_ops::ToV8Value::to_v8_value(result, scope))
+                },
+            ) {
+            rv.set(deno_core::_ops::ToV8Value::to_v8_value(result, &mut scope));
+        }
+    }
+    #[inline(always)]
+    pub async fn call(buf: JsBuffer) -> JsBuffer {
+        buf
+    }
+}
diff --git a/ops/op2/test_cases/async/async_jsbuffer.rs b/ops/op2/test_cases/async/async_jsbuffer.rs
new file mode 100644
index 0000000..2be678d
--- /dev/null
+++ b/ops/op2/test_cases/async/async_jsbuffer.rs
@@ -0,0 +1,11 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+#![deny(warnings)]
+deno_ops_compile_test_runner::prelude!();
+
+use deno_core::JsBuffer;
+
+#[op2(async)]
+#[buffer]
+pub async fn op_async_v8_buffer(#[buffer] buf: JsBuffer) -> JsBuffer {
+    buf
+}
diff --git a/ops/op2/test_cases/async/async_result_impl.out b/ops/op2/test_cases/async/async_result_impl.out
index 107db01..7e7fcee 100644
--- a/ops/op2/test_cases/async/async_result_impl.out
+++ b/ops/op2/test_cases/async/async_result_impl.out
@@ -37,17 +37,17 @@ impl op_async_result_impl {
         let result = {
             let arg0 = args.get(1usize as i32);
             let Some(arg0) = deno_core::_ops::to_i32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected i32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected i32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0 as _;
             Self::call(arg0)
         };
diff --git a/ops/op2/test_cases/async/async_v8_global.out b/ops/op2/test_cases/async/async_v8_global.out
new file mode 100644
index 0000000..4b783de
--- /dev/null
+++ b/ops/op2/test_cases/async/async_v8_global.out
@@ -0,0 +1,70 @@
+#[allow(non_camel_case_types)]
+pub struct op_async_v8_global {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_async_v8_global {
+    const NAME: &'static str = stringify!(op_async_v8_global);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_async_v8_global),
+        true,
+        false,
+        false,
+        2usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_async_v8_global {
+    pub const fn name() -> &'static str {
+        stringify!(op_async_v8_global)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+            &*info
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+                as *const deno_core::_ops::OpCtx)
+        };
+        let result = {
+            let arg0 = args.get(1usize as i32);
+            let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
+                deno_core::v8::String,
+            >(arg0) else {
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
+            let arg0 = deno_core::v8::Global::new(&mut scope, arg0);
+            Self::call(arg0)
+        };
+        let promise_id = deno_core::_ops::to_i32_option(&args.get(0))
+            .unwrap_or_default();
+        if let Some(result)
+            = deno_core::_ops::map_async_op_infallible(
+                opctx,
+                promise_id,
+                result,
+                |scope, result| { Ok(deno_core::v8::null(scope).into()) },
+            ) {
+            rv.set_null();
+        }
+    }
+    #[inline(always)]
+    pub async fn call(_s: v8::Global<v8::String>) {}
+}
diff --git a/ops/op2/test_cases/async/async_v8_global.rs b/ops/op2/test_cases/async/async_v8_global.rs
new file mode 100644
index 0000000..47671aa
--- /dev/null
+++ b/ops/op2/test_cases/async/async_v8_global.rs
@@ -0,0 +1,8 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+#![deny(warnings)]
+deno_ops_compile_test_runner::prelude!();
+
+use deno_core::v8;
+
+#[op2(async)]
+pub async fn op_async_v8_global(#[global] _s: v8::Global<v8::String>) {}
diff --git a/ops/op2/test_cases/compiler_pass/async.rs b/ops/op2/test_cases/compiler_pass/async.rs
index fab40ca..de63161 100644
--- a/ops/op2/test_cases/compiler_pass/async.rs
+++ b/ops/op2/test_cases/compiler_pass/async.rs
@@ -7,6 +7,7 @@ use std::cell::RefCell;
 use std::future::Future;
 use deno_core::error::AnyError;
 use deno_core::OpState;
+use deno_core::JsBuffer;
 
 // Collect a few examples that we'll smoke test when not running on the CI.
 
@@ -41,3 +42,9 @@ pub fn op_async5(x: i32) -> Result<impl Future<Output = std::io::Result<i32>>, A
 pub async fn op_async_opstate(state: Rc<RefCell<OpState>>) -> std::io::Result<i32> {
     Ok(*state.borrow().borrow::<i32>())
 }
+
+#[op2(async)]
+#[buffer]
+pub async fn op_async_buffer(#[buffer] buf: JsBuffer) -> JsBuffer {
+  buf
+}
\ No newline at end of file
diff --git a/ops/op2/test_cases/sync/add.out b/ops/op2/test_cases/sync/add.out
index 48ef215..186fa58 100644
--- a/ops/op2/test_cases/sync/add.out
+++ b/ops/op2/test_cases/sync/add.out
@@ -52,31 +52,31 @@ impl op_add {
         let result = {
             let arg0 = args.get(0usize as i32);
             let Some(arg0) = deno_core::_ops::to_u32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected u32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0 as _;
             let arg1 = args.get(1usize as i32);
             let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected u32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg1 = arg1 as _;
             Self::call(arg0, arg1)
         };
diff --git a/ops/op2/test_cases/sync/add_options.out b/ops/op2/test_cases/sync/add_options.out
index 56134d4..72647da 100644
--- a/ops/op2/test_cases/sync/add_options.out
+++ b/ops/op2/test_cases/sync/add_options.out
@@ -32,23 +32,6 @@ impl op_test_add_option {
         let result = {
             let arg0 = args.get(0usize as i32);
             let Some(arg0) = deno_core::_ops::to_u32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected u32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
-            let arg0 = arg0 as _;
-            let arg1 = args.get(1usize as i32);
-            let arg1 = if arg1.is_null_or_undefined() {
-                None
-            } else {
-                let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
                 let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                 let msg = deno_core::v8::String::new_from_one_byte(
                         &mut scope,
@@ -60,6 +43,23 @@ impl op_test_add_option {
                 scope.throw_exception(exc);
                 return;
             };
+            let arg0 = arg0 as _;
+            let arg1 = args.get(1usize as i32);
+            let arg1 = if arg1.is_null_or_undefined() {
+                None
+            } else {
+                let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                    let msg = deno_core::v8::String::new_from_one_byte(
+                            &mut scope,
+                            "expected u32".as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return;
+                };
                 let arg1 = arg1 as _;
                 Some(arg1)
             };
diff --git a/ops/op2/test_cases/sync/nofast.out b/ops/op2/test_cases/sync/nofast.out
index 9ff984b..5a7a554 100644
--- a/ops/op2/test_cases/sync/nofast.out
+++ b/ops/op2/test_cases/sync/nofast.out
@@ -32,31 +32,31 @@ impl op_nofast {
         let result = {
             let arg0 = args.get(0usize as i32);
             let Some(arg0) = deno_core::_ops::to_u32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected u32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0 as _;
             let arg1 = args.get(1usize as i32);
             let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected u32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg1 = arg1 as _;
             Self::call(arg0, arg1)
         };
diff --git a/ops/op2/test_cases/sync/smi.out b/ops/op2/test_cases/sync/smi.out
index 387cc65..0b6d0d8 100644
--- a/ops/op2/test_cases/sync/smi.out
+++ b/ops/op2/test_cases/sync/smi.out
@@ -52,31 +52,31 @@ impl op_add {
         let result = {
             let arg0 = args.get(0usize as i32);
             let Some(arg0) = deno_core::_ops::to_i32_option(&arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected i32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected i32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0 as _;
             let arg1 = args.get(1usize as i32);
             let Some(arg1) = deno_core::_ops::to_u32_option(&arg1) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected u32".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected u32".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg1 = arg1 as _;
             Self::call(arg0, arg1)
         };
diff --git a/ops/op2/test_cases/sync/v8_global.out b/ops/op2/test_cases/sync/v8_global.out
index 1f532f6..7f0c247 100644
--- a/ops/op2/test_cases/sync/v8_global.out
+++ b/ops/op2/test_cases/sync/v8_global.out
@@ -39,17 +39,17 @@ impl op_global {
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
                 deno_core::v8::String,
             >(arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = deno_core::v8::Global::new(&mut scope, arg0);
             Self::call(arg0)
         };
diff --git a/ops/op2/test_cases/sync/v8_handlescope.out b/ops/op2/test_cases/sync/v8_handlescope.out
index ddab355..a26aa60 100644
--- a/ops/op2/test_cases/sync/v8_handlescope.out
+++ b/ops/op2/test_cases/sync/v8_handlescope.out
@@ -35,16 +35,16 @@ impl op_handlescope {
             let Ok(mut arg1) = deno_core::_ops::v8_try_convert::<
                 deno_core::v8::String,
             >(arg1) else {
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg1 = arg1;
             let arg0 = &mut scope;
             Self::call(arg0, arg1)
diff --git a/ops/op2/test_cases/sync/v8_lifetime.out b/ops/op2/test_cases/sync/v8_lifetime.out
index 1906e24..e9002d1 100644
--- a/ops/op2/test_cases/sync/v8_lifetime.out
+++ b/ops/op2/test_cases/sync/v8_lifetime.out
@@ -34,17 +34,17 @@ impl op_v8_lifetime {
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
                 deno_core::v8::String,
             >(arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = arg0;
             Self::call(arg0)
         };
diff --git a/ops/op2/test_cases/sync/v8_ref_option.out b/ops/op2/test_cases/sync/v8_ref_option.out
index 68ce74c..7649c9e 100644
--- a/ops/op2/test_cases/sync/v8_ref_option.out
+++ b/ops/op2/test_cases/sync/v8_ref_option.out
@@ -40,18 +40,20 @@ impl op_v8_lifetime {
         let result = {
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert_option::<
                 deno_core::v8::String,
-            >(arg0) else { fast_api_callback_options.fallback = true;
-            return ::std::default::Default::default();
-        };
+            >(arg0) else {
+                fast_api_callback_options.fallback = true;
+                return ::std::default::Default::default();
+            };
             let arg0 = match &arg0 {
                 None => None,
                 Some(v) => Some(::std::ops::Deref::deref(v)),
             };
             let Ok(mut arg1) = deno_core::_ops::v8_try_convert_option::<
                 deno_core::v8::String,
-            >(arg1) else { fast_api_callback_options.fallback = true;
-            return ::std::default::Default::default();
-        };
+            >(arg1) else {
+                fast_api_callback_options.fallback = true;
+                return ::std::default::Default::default();
+            };
             let arg1 = match &arg1 {
                 None => None,
                 Some(v) => Some(::std::ops::Deref::deref(v)),
@@ -72,17 +74,17 @@ impl op_v8_lifetime {
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert_option::<
                 deno_core::v8::String,
             >(arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = match &arg0 {
                 None => None,
                 Some(v) => Some(::std::ops::Deref::deref(v)),
@@ -91,17 +93,17 @@ impl op_v8_lifetime {
             let Ok(mut arg1) = deno_core::_ops::v8_try_convert_option::<
                 deno_core::v8::String,
             >(arg1) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg1 = match &arg1 {
                 None => None,
                 Some(v) => Some(::std::ops::Deref::deref(v)),
diff --git a/ops/op2/test_cases/sync/v8_string.out b/ops/op2/test_cases/sync/v8_string.out
index 6065247..9f4d927 100644
--- a/ops/op2/test_cases/sync/v8_string.out
+++ b/ops/op2/test_cases/sync/v8_string.out
@@ -34,33 +34,33 @@ impl op_v8_string {
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert::<
                 deno_core::v8::String,
             >(arg0) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg0 = &arg0;
             let arg1 = args.get(1usize as i32);
             let Ok(mut arg1) = deno_core::_ops::v8_try_convert::<
                 deno_core::v8::String,
             >(arg1) else {
-            let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
-            let msg = deno_core::v8::String::new_from_one_byte(
-                    &mut scope,
-                    "expected String".as_bytes(),
-                    deno_core::v8::NewStringType::Normal,
-                )
-                .unwrap();
-            let exc = deno_core::v8::Exception::error(&mut scope, msg);
-            scope.throw_exception(exc);
-            return;
-        };
+                let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                let msg = deno_core::v8::String::new_from_one_byte(
+                        &mut scope,
+                        "expected String".as_bytes(),
+                        deno_core::v8::NewStringType::Normal,
+                    )
+                    .unwrap();
+                let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            };
             let arg1 = arg1;
             Self::call(arg0, arg1)
         };