Repository: denoland/deno_core
Pull request #241 (2023-09-26 15:27:36)

feat(ops): support v8::ArrayBuffer in op2
Supported using the syntax: `#[arraybuffer] buffer: &[u8]` (or `&mut [u8]`). All buffer types supported by `#[buffer]` are supported by `#[arraybuffer]`.

This is slightly slower than typed arrays during fastcalls because we need to extract the data pointer from the underlying v8 `ArrayBuffer` handle.

```
running 28 tests
test baseline                               ... bench:         688 ns/iter (+/- 17)
test bench_op_arraybuffer                   ... bench:       5,907 ns/iter (+/- 1,379)
test bench_op_buffer                        ... bench:       3,620 ns/iter (+/- 82)
test bench_op_external                      ... bench:       2,827 ns/iter (+/- 72)
test bench_op_external_nofast               ... bench:      11,778 ns/iter (+/- 336)
test bench_op_option_u32                    ... bench:       8,583 ns/iter (+/- 212)
test bench_op_string                        ... bench:      10,917 ns/iter (+/- 373)
test bench_op_string_bytestring             ... bench:      59,730 ns/iter (+/- 1,602)
test bench_op_string_large_1000             ... bench:     107,768 ns/iter (+/- 3,063)
test bench_op_string_large_1000000          ... bench:     399,192 ns/iter (+/- 13,257)
test bench_op_string_large_utf8_1000        ... bench:   2,109,778 ns/iter (+/- 46,314)
test bench_op_string_large_utf8_1000000     ... bench:  11,247,604 ns/iter (+/- 247,500)
test bench_op_string_old                    ... bench:      12,784 ns/iter (+/- 1,516)
test bench_op_string_old_large_1000         ... bench:     130,352 ns/iter (+/- 2,780)
test bench_op_string_old_large_1000000      ... bench:     237,169 ns/iter (+/- 5,121)
test bench_op_string_old_large_utf8_1000    ... bench:   2,910,666 ns/iter (+/- 64,687)
test bench_op_string_old_large_utf8_1000000 ... bench:  14,759,333 ns/iter (+/- 921,988)
test bench_op_string_onebyte                ... bench:       4,241 ns/iter (+/- 87)
test bench_op_string_onebyte_large_1000     ... bench:     105,557 ns/iter (+/- 2,438)
test bench_op_string_onebyte_large_1000000  ... bench:     197,767 ns/iter (+/- 12,050)
test bench_op_string_option_u32             ... bench:      36,461 ns/iter (+/- 957)
test bench_op_u32                           ... bench:       2,822 ns/iter (+/- 82)
test bench_op_v8_global                     ... bench:      29,296 ns/iter (+/- 1,210)
test bench_op_v8_global_scope               ... bench:      40,189 ns/iter (+/- 1,281)
test bench_op_v8_local                      ... bench:       4,429 ns/iter (+/- 97)
test bench_op_v8_local_nofast               ... bench:       8,418 ns/iter (+/- 170)
test bench_op_v8_local_scope                ... bench:      17,694 ns/iter (+/- 347)
test bench_op_void                          ... bench:       2,789 ns/iter (+/- 90)
```


Patch:
diff --git a/core/benches/ops/sync.rs b/core/benches/ops/sync.rs
index d9f6410..fa8d8de 100644
--- a/core/benches/ops/sync.rs
+++ b/core/benches/ops/sync.rs
@@ -24,6 +24,9 @@ deno_core::extension!(
     op_make_external,
     op_external,
     op_external_nofast,
+    op_buffer,
+    op_buffer_nofast,
+    op_arraybuffer,
   ],
   state = |state| {
     state.put(1234u32);
@@ -100,6 +103,15 @@ pub fn op_external(_input: *const c_void) {}
 #[op2(nofast)]
 pub fn op_external_nofast(_input: *const c_void) {}
 
+#[op2(fast)]
+pub fn op_buffer(#[buffer] _buffer: &[u8]) {}
+
+#[op2(nofast)]
+pub fn op_buffer_nofast(#[buffer] _buffer: &[u8]) {}
+
+#[op2(fast)]
+pub fn op_arraybuffer(#[arraybuffer] _buffer: &[u8]) {}
+
 fn bench_op(
   b: &mut Bencher,
   count: usize,
@@ -138,6 +150,8 @@ const LARGE_STRING_1000000 = '*'.repeat(1000000);
 const LARGE_STRING_1000 = '*'.repeat(1000);
 const LARGE_STRING_UTF8_1000000 = '\u1000'.repeat(1000000);
 const LARGE_STRING_UTF8_1000 = '\u1000'.repeat(1000);
+const BUFFER = new Uint8Array(1024);
+const ARRAYBUFFER = new ArrayBuffer(1024);
 const {{ {op}: op }} = Deno.core.ensureFastOps();
 const {{ op_make_external }} = Deno.core.ensureFastOps();
 const EXTERNAL = op_make_external();
@@ -380,6 +394,30 @@ fn bench_op_external_nofast(b: &mut Bencher) {
   );
 }
 
+fn bench_op_buffer(b: &mut Bencher) {
+  bench_op(b, BENCH_COUNT, "op_buffer", 1, "op_buffer(BUFFER)");
+}
+
+fn bench_op_buffer_nofast(b: &mut Bencher) {
+  bench_op(
+    b,
+    BENCH_COUNT,
+    "op_buffer_nofast",
+    1,
+    "op_buffer_nofast(BUFFER)",
+  );
+}
+
+fn bench_op_arraybuffer(b: &mut Bencher) {
+  bench_op(
+    b,
+    BENCH_COUNT,
+    "op_arraybuffer",
+    1,
+    "op_arraybuffer(ARRAYBUFFER)",
+  );
+}
+
 benchmark_group!(
   benches,
   baseline,
@@ -408,6 +446,9 @@ benchmark_group!(
   bench_op_v8_global_scope,
   bench_op_external,
   bench_op_external_nofast,
+  bench_op_buffer,
+  bench_op_buffer_nofast,
+  bench_op_arraybuffer,
 );
 
 benchmark_main!(benches);
diff --git a/core/examples/op2.js b/core/examples/op2.js
new file mode 100644
index 0000000..5ff2153
--- /dev/null
+++ b/core/examples/op2.js
@@ -0,0 +1,3 @@
+globalThis.op2_sample = {
+  "use_state": (f) => Deno.core.ops.op_use_state(f),
+};
diff --git a/core/examples/op2.rs b/core/examples/op2.rs
new file mode 100644
index 0000000..101e180
--- /dev/null
+++ b/core/examples/op2.rs
@@ -0,0 +1,68 @@
+use anyhow::Context;
+use deno_core::anyhow::Error;
+use deno_core::extension;
+use deno_core::op2;
+use deno_core::resolve_path;
+use deno_core::v8;
+use deno_core::FsModuleLoader;
+use deno_core::JsRuntime;
+use deno_core::OpState;
+use std::rc::Rc;
+
+#[op2]
+fn op_use_state(
+  state: &mut OpState,
+  #[global] callback: v8::Global<v8::Function>,
+) -> Result<(), Error> {
+  state.put(callback);
+  Ok(())
+}
+
+extension!(
+  op2_sample,
+  ops = [op_use_state],
+  esm_entry_point = "ext:op2_sample/op2.js",
+  esm = [ dir "examples", "op2.js" ],
+  docs = "A small example demonstrating op2 usage.", "Contains one op."
+);
+
+fn main() -> Result<(), Error> {
+  let module_name = "test.js";
+  let module_code = "
+      op2_sample.use_state(() => {
+          console.log('Hello World');
+      });
+  "
+  .to_string();
+
+  let mut js_runtime = JsRuntime::new(deno_core::RuntimeOptions {
+    module_loader: Some(Rc::new(FsModuleLoader)),
+    extensions: vec![op2_sample::init_ops_and_esm()],
+    ..Default::default()
+  });
+
+  let main_module = resolve_path(
+    module_name,
+    &std::env::current_dir()
+      .context("Unable to get current working directory")?,
+  )?;
+
+  let future = async move {
+    let mod_id = js_runtime
+      .load_main_module(
+        &main_module,
+        Some(deno_core::FastString::from(module_code)),
+      )
+      .await?;
+
+    let result = js_runtime.mod_evaluate(mod_id);
+    js_runtime.run_event_loop(false).await?;
+    result.await?
+  };
+
+  tokio::runtime::Builder::new_current_thread()
+    .enable_all()
+    .build()
+    .unwrap()
+    .block_on(future)
+}
diff --git a/core/extensions.rs b/core/extensions.rs
index 64f89d5..2cd0ec6 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -232,6 +232,7 @@ macro_rules! or {
 ///   my_extension,
 ///   ops = [ op_xyz ],
 ///   esm = [ "my_script.js" ],
+///   docs = "A small sample extension"
 /// );
 /// ```
 ///
@@ -249,6 +250,7 @@ macro_rules! or {
 ///  * event_loop_middleware: an event-loop middleware function (see [`ExtensionBuilder::event_loop_middleware`])
 ///  * global_template_middleware: a global template middleware function (see [`ExtensionBuilder::global_template_middleware`])
 ///  * global_object_middleware: a global object middleware function (see [`ExtensionBuilder::global_object_middleware`])
+///  * docs: comma separated list of toplevel #[doc=...] tags to be applied to the extension's resulting struct
 #[macro_export]
 macro_rules! extension {
   (
@@ -269,11 +271,24 @@ macro_rules! extension {
     $(, global_object_middleware = $global_object_middleware_fn:expr )?
     $(, external_references = [ $( $external_reference:expr ),* $(,)? ] )?
     $(, customizer = $customizer_fn:expr )?
+    $(, docs = $($docblocks:expr),+)?
     $(,)?
   ) => {
-    /// Extension struct for
-    #[doc = stringify!($name)]
-    /// .
+    $( $(#[doc = $docblocks])+ )?
+    ///
+    /// An extension for use with the Deno JS runtime.
+    /// To use it, provide it as an argument when instantiating your runtime:
+    ///
+    /// ```rust,ignore
+    /// use deno_core::{ JsRuntime, RuntimeOptions };
+    ///
+    #[doc = concat!("let mut extensions = vec![", stringify!($name), "::init_ops_and_esm()];")]
+    /// let mut js_runtime = JsRuntime::new(RuntimeOptions {
+    ///   extensions,
+    ///   ..Default::default()
+    /// });
+    /// ```
+    ///
     #[allow(non_camel_case_types)]
     pub struct $name {
     }
@@ -358,6 +373,12 @@ macro_rules! extension {
       }
 
       #[allow(dead_code)]
+      /// Legacy function for extension instantiation.
+      /// Please use `init_ops_and_esm` or `init_ops` instead
+      ///
+      /// # Returns
+      /// an Extension object that can be used during instantiation of a JsRuntime
+      #[deprecated(since="0.216.0", note="please use `init_ops_and_esm` or `init_ops` instead")]
       pub fn init_js_only $( <  $( $param : $type + 'static ),* > )? () -> $crate::Extension
       $( where $( $bound : $bound_type ),+ )?
       {
@@ -368,6 +389,13 @@ macro_rules! extension {
       }
 
       #[allow(dead_code)]
+      /// Initialize this extension for runtime or snapshot creation. Use this
+      /// function if the runtime or snapshot is not created from a (separate)
+      /// snapshot, or that snapshot does not contain this extension. Otherwise
+      /// use `init_ops()` instead.
+      ///
+      /// # Returns
+      /// an Extension object that can be used during instantiation of a JsRuntime
       pub fn init_ops_and_esm $( <  $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
       $( where $( $bound : $bound_type ),+ )?
       {
@@ -379,6 +407,13 @@ macro_rules! extension {
       }
 
       #[allow(dead_code)]
+      /// Initialize this extension for runtime or snapshot creation, excluding
+      /// its JavaScript sources and evaluation. This is used when the runtime
+      /// or snapshot is created from a (separate) snapshot which includes this
+      /// extension in order to avoid evaluating the JavaScript twice.
+      ///
+      /// # Returns
+      /// an Extension object that can be used during instantiation of a JsRuntime
       pub fn init_ops $( <  $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
       $( where $( $bound : $bound_type ),+ )?
       {
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 13b68dc..1edb382 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -594,6 +594,63 @@ where
   Ok(slice)
 }
 
+/// Retrieve a byte slice from a [`v8::ArrayBuffer`], avoiding the intermediate [`v8::BackingStore`].
+///
+/// # Safety
+///
+/// Callers must ensure that the returned slice does not outlive the [`v8::BackingStore`] of the
+/// [`v8::ArrayBuffer`].
+pub unsafe fn to_slice_buffer(
+  input: v8::Local<v8::Value>,
+) -> Result<&mut [u8], &'static str> {
+  let Ok(buf) = v8::Local::<v8::ArrayBuffer>::try_from(input) else {
+    return Err("expected ArrayBuffer");
+  };
+  let len = buf.byte_length();
+  let slice = if len > 0 {
+    if let Some(ptr) = buf.data() {
+      std::slice::from_raw_parts_mut(ptr.as_ptr() as _, len)
+    } else {
+      &mut []
+    }
+  } else {
+    &mut []
+  };
+  Ok(slice)
+}
+
+/// Retrieve a [`serde_v8::V8Slice`] from a [`v8::ArrayBuffer`].
+pub fn to_v8_slice_buffer(
+  input: v8::Local<v8::Value>,
+) -> Result<serde_v8::V8Slice<u8>, &'static str> {
+  let Ok(buf) = v8::Local::<v8::ArrayBuffer>::try_from(input) else {
+    return Err("expected ArrayBuffer");
+  };
+  let slice = unsafe {
+    serde_v8::V8Slice::from_parts(buf.get_backing_store(), 0..buf.byte_length())
+  };
+  Ok(slice)
+}
+
+/// Retrieve a [`serde_v8::V8Slice`] from a [`v8::ArrayBuffer`].
+pub fn to_v8_slice_buffer_detachable(
+  input: v8::Local<v8::Value>,
+) -> Result<serde_v8::V8Slice<u8>, &'static str> {
+  let (store, length) =
+    if let Ok(buf) = v8::Local::<v8::ArrayBuffer>::try_from(input) {
+      let res = (buf.get_backing_store(), buf.byte_length());
+      if !buf.is_detachable() {
+        return Err("invalid type; expected: detachable");
+      }
+      buf.detach(None);
+      res
+    } else {
+      return Err("expected ArrayBuffer");
+    };
+  let slice = unsafe { serde_v8::V8Slice::from_parts(store, 0..length) };
+  Ok(slice)
+}
+
 #[cfg(test)]
 mod tests {
   use crate::error::generic_error;
@@ -672,6 +729,7 @@ mod tests {
       op_buffer_slice_unsafe_callback,
       op_buffer_copy,
       op_buffer_bytesmut,
+      op_arraybuffer_slice,
       op_external_make,
       op_external_process,
       op_external_make_null,
@@ -1644,6 +1702,41 @@ mod tests {
     Ok(())
   }
 
+  #[op2(core, fast)]
+  pub fn op_arraybuffer_slice(
+    #[arraybuffer] input: &[u8],
+    #[number] inlen: usize,
+    #[arraybuffer] output: &mut [u8],
+    #[number] outlen: usize,
+  ) {
+    assert_eq!(inlen, input.len());
+    assert_eq!(outlen, output.len());
+    if inlen > 0 && outlen > 0 {
+      output[0] = input[0];
+    }
+  }
+
+  #[tokio::test]
+  pub async fn test_op_arraybuffer_slice(
+  ) -> Result<(), Box<dyn std::error::Error>> {
+    // Zero-length buffers
+    run_test2(
+      10000,
+      "op_arraybuffer_slice",
+      "op_arraybuffer_slice(new ArrayBuffer(0), 0, new ArrayBuffer(0), 0);",
+    )?;
+    run_test2(
+      10000,
+      "op_arraybuffer_slice",
+      r"let inbuf = new ArrayBuffer(10);
+      (new Uint8Array(inbuf))[0] = 1;
+      let outbuf = new ArrayBuffer(10);
+      op_arraybuffer_slice(inbuf, 10, outbuf, 10);
+      assert((new Uint8Array(outbuf))[0] == 1);",
+    )?;
+    Ok(())
+  }
+
   // TODO(mmastrac): This is a dangerous op that we'll use to test resizable buffers in a later pass.
   #[op2(core)]
   pub fn op_buffer_slice_unsafe_callback(
diff --git a/core/runtime/ops_rust_to_v8.rs b/core/runtime/ops_rust_to_v8.rs
index 658f1c1..dc5050e 100644
--- a/core/runtime/ops_rust_to_v8.rs
+++ b/core/runtime/ops_rust_to_v8.rs
@@ -138,7 +138,8 @@ where
 /// `v8::BigInt` objects.
 #[repr(transparent)]
 pub struct RustToV8Marker<M, T>(T, PhantomData<M>);
-impl<M, T> From<T> for RustToV8Marker<M, T> {
+impl<M: Marker, T> From<T> for RustToV8Marker<M, T> {
+  #[inline(always)]
   fn from(value: T) -> Self {
     RustToV8Marker(value, PhantomData)
   }
@@ -146,12 +147,21 @@ impl<M, T> From<T> for RustToV8Marker<M, T> {
 
 /// This struct should be serialized using `serde`.
 pub struct SerdeMarker;
+impl Marker for SerdeMarker {}
 
 /// This primitive should be serialized as an SMI.
 pub struct SmiMarker;
+impl Marker for SmiMarker {}
 
 /// This primitive should be serialized as a number.
 pub struct NumberMarker;
+impl Marker for NumberMarker {}
+
+/// This buffer should be serialized as an ArrayBuffer.
+pub struct ArrayBufferMarker;
+impl Marker for ArrayBufferMarker {}
+
+trait Marker {}
 
 /// Helper macro for [`RustToV8`] to reduce boilerplate.
 ///
@@ -318,27 +328,40 @@ to_v8_retval_fallible!(Cow<'a, [u8]>: |value, scope, rv| {
 to_v8_fallible!(serde_v8::V8Slice<u8>: |value, scope| {
   value.into_v8_local(scope).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
 });
+to_v8!(RustToV8Marker<ArrayBufferMarker, serde_v8::V8Slice<u8>>: |value, scope| {
+  value.0.into_v8_unsliced_arraybuffer_local(scope)
+});
 to_v8_fallible!(serde_v8::V8Slice<u32>: |value, scope| {
   value.into_v8_local(scope).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
 });
+to_v8!(RustToV8Marker<ArrayBufferMarker, serde_v8::JsBuffer>: |value, scope| {
+  RustToV8Marker::<ArrayBufferMarker, _>::from(value.0.into_parts()).to_v8(scope)
+});
 to_v8_fallible!(serde_v8::JsBuffer: |value, scope| {
   value.into_parts().to_v8_fallible(scope)
 });
-to_v8_fallible!(Box<[u8]>: |buf, scope| {
+to_v8!(RustToV8Marker<ArrayBufferMarker, Box<[u8]>>: |buf, scope| {
+  let buf = buf.0;
   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()))
+    v8::ArrayBuffer::new(scope, 0)
   } 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()))
+    v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared)
   }
 });
+to_v8_fallible!(Box<[u8]>: |buf, scope| {
+  let len = buf.len();
+  let ab = unsafe { v8::Local::cast(RustToV8Marker::<ArrayBufferMarker, _>::from(buf).to_v8(scope)) };
+  v8::Uint8Array::new(scope, ab, 0, len).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+});
+to_v8!(RustToV8Marker<ArrayBufferMarker, Vec<u8>>: |value, scope| {
+  RustToV8Marker::<ArrayBufferMarker, _>::from(value.0.into_boxed_slice()).to_v8(scope)
+});
 to_v8_fallible!(Vec<u8>: |value, scope| value.into_boxed_slice().to_v8_fallible(scope));
-to_v8_fallible!(BytesMut: |value, scope| {
+to_v8!(RustToV8Marker<ArrayBufferMarker, BytesMut>: |value, scope| {
+  let value = value.0;
   let ptr = value.as_ptr();
   let len = value.len() as _;
   let rc = Rc::into_raw(Rc::new(value)) as *const c_void;
@@ -355,7 +378,11 @@ to_v8_fallible!(BytesMut: |value, scope| {
     )
   }
   .make_shared();
-  let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
+  v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared)
+});
+to_v8_fallible!(BytesMut: |buf, scope| {
+  let len = buf.len();
+  let ab = unsafe { v8::Local::cast(RustToV8Marker::<ArrayBufferMarker, _>::from(buf).to_v8(scope)) };
   v8::Uint8Array::new(scope, ab, 0, len).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
 });
 
diff --git a/ops/op2/README.md b/ops/op2/README.md
index 5571701..84ef9e6 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -536,6 +536,104 @@ any
 <tr>
 <td>
 
+```text
+#[arraybuffer] &mut [u8]
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer] &[u8]
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer] *mut u8
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer] *const u8
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer(copy)] Vec<u8>
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+Safe, but forces a copy.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer(copy)] Box<[u8]>
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+Safe, but forces a copy.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer(copy)] bytes::Bytes
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer (resizable=true,false)
+</td><td>
+Safe, but forces a copy.
+</td></tr>
+<tr>
+<td>
+
 ```text
 #[buffer] &mut [u8]
 ```
@@ -1218,6 +1316,70 @@ f64
 <tr>
 <td>
 
+```text
+#[arraybuffer] V8Slice<u8>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer] Vec<u8>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer] Box<[u8]>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
+```text
+#[arraybuffer] bytes::BytesMut
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
 ```text
 #[buffer] V8Slice<u8>
 ```
diff --git a/ops/op2/dispatch_async.rs b/ops/op2/dispatch_async.rs
index c790729..04240af 100644
--- a/ops/op2/dispatch_async.rs
+++ b/ops/op2/dispatch_async.rs
@@ -12,11 +12,11 @@ use super::dispatch_slow::with_opctx;
 use super::dispatch_slow::with_opstate;
 use super::dispatch_slow::with_retval;
 use super::dispatch_slow::with_scope;
+use super::generator_state::gs_quote;
 use super::generator_state::GeneratorState;
 use super::signature::ParsedSignature;
 use super::signature::RetVal;
 use super::V8MappingError;
-use crate::op2::generator_state::gs_quote;
 use proc_macro2::TokenStream;
 use quote::quote;
 
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 29748e9..6d06651 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -1,6 +1,9 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use super::dispatch_shared::byte_slice_to_buffer;
+use super::dispatch_shared::fast_api_typed_array_to_buffer;
 use super::dispatch_shared::v8_intermediate_to_arg;
 use super::dispatch_shared::v8_to_arg;
+use super::dispatch_shared::v8slice_to_buffer;
 use super::generator_state::GeneratorState;
 use super::signature::Arg;
 use super::signature::Buffer;
@@ -39,6 +42,10 @@ pub(crate) enum V8FastCallType {
   Float64Array,
   SeqOneByteString,
   CallbackOptions,
+  /// ArrayBuffers are currently supported in fastcalls by passing a V8Value and manually unwrapping
+  /// the buffer. In the future, V8 may be able to support ArrayBuffer fastcalls in the same way that
+  /// a TypedArray overload works and we may be able to adjust the support here.
+  ArrayBuffer,
   /// Used for virtual arguments that do not contribute a raw argument
   Virtual,
 }
@@ -74,6 +81,9 @@ impl V8FastCallType {
       V8FastCallType::Float64Array => {
         quote!(*mut #deno_core::v8::fast_api::FastApiTypedArray<f64>)
       }
+      V8FastCallType::ArrayBuffer => {
+        quote!(#deno_core::v8::Local<#deno_core::v8::ArrayBuffer>)
+      }
       V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
   }
@@ -96,6 +106,7 @@ impl V8FastCallType {
       V8FastCallType::Uint32Array => unreachable!(),
       V8FastCallType::Float64Array => unreachable!(),
       V8FastCallType::SeqOneByteString => quote!(CType::SeqOneByteString),
+      V8FastCallType::ArrayBuffer => unreachable!(),
       V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
   }
@@ -118,6 +129,7 @@ impl V8FastCallType {
       V8FastCallType::Uint32Array => quote!(Type::TypedArray(CType::Uint32)),
       V8FastCallType::Float64Array => quote!(Type::TypedArray(CType::Float64)),
       V8FastCallType::SeqOneByteString => quote!(Type::SeqOneByteString),
+      V8FastCallType::ArrayBuffer => quote!(Type::V8Value),
       V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
   }
@@ -320,26 +332,35 @@ fn map_v8_fastcall_arg_to_arg(
   } = generator_state;
 
   let arg_temp = format_ident!("{}_temp", arg_ident);
-  let buf = quote!((
-    // SAFETY: we are certain the implied lifetime is valid here as the slices never escape the
-    // fastcall
-    unsafe { #deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(#arg_ident) }.expect("Invalid buffer"))
-  );
+
   let res = match arg {
-    Arg::Buffer(Buffer::Slice(_, NumericArg::u8 | NumericArg::u32)) => {
-      quote!(let #arg_ident = #buf;)
-    }
-    Arg::Buffer(Buffer::Ptr(_, NumericArg::u8 | NumericArg::u32)) => {
-      quote!(let #arg_ident = if #buf.len() == 0 { ::std::ptr::null_mut() } else { #buf.as_mut_ptr() as _ };)
-    }
-    Arg::Buffer(Buffer::Vec(NumericArg::u8 | NumericArg::u32)) => {
-      quote!(let #arg_ident = #buf.to_vec();)
+    Arg::ArrayBuffer(buffer @ (Buffer::V8Slice(..) | Buffer::JsBuffer(..))) => {
+      *needs_fast_api_callback_options = true;
+      let buf = v8slice_to_buffer(deno_core, arg_ident, &arg_temp, *buffer)?;
+      quote!(
+        let Ok(mut #arg_temp) = #deno_core::_ops::to_v8_slice_buffer(#arg_ident.into()) else {
+          #fast_api_callback_options.fallback = true;
+          // SAFETY: All fast return types have zero as a valid value
+          return unsafe { std::mem::zeroed() };
+        };
+        #buf
+      )
     }
-    Arg::Buffer(Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)) => {
-      quote!(let #arg_ident = #buf.to_vec().into_boxed_slice();)
+    Arg::ArrayBuffer(buffer) => {
+      *needs_fast_api_callback_options = true;
+      let buf = byte_slice_to_buffer(arg_ident, &arg_temp, *buffer)?;
+      quote!(
+        // SAFETY: This slice doesn't outlive the function
+        let Ok(mut #arg_temp) = (unsafe { #deno_core::_ops::to_slice_buffer(#arg_ident.into()) }) else {
+          #fast_api_callback_options.fallback = true;
+          // SAFETY: All fast return types have zero as a valid value
+          return unsafe { std::mem::zeroed() };
+        };
+        #buf
+      )
     }
-    Arg::Buffer(Buffer::Bytes(BufferMode::Copy)) => {
-      quote!(let #arg_ident = #buf.to_vec().into();)
+    Arg::Buffer(buffer) => {
+      fast_api_typed_array_to_buffer(deno_core, arg_ident, arg_ident, *buffer)?
     }
     Arg::Ref(RefType::Ref, Special::OpState) => {
       *needs_opctx = true;
@@ -467,6 +488,14 @@ fn map_arg_to_v8_fastcall_type(
       | Buffer::BoxSlice(NumericArg::u32),
     ) => V8FastCallType::Uint32Array,
     Arg::Buffer(_) => return Ok(None),
+    Arg::ArrayBuffer(
+      Buffer::Slice(_, NumericArg::u8)
+      | Buffer::Ptr(_, NumericArg::u8)
+      | Buffer::Vec(NumericArg::u8)
+      | Buffer::BoxSlice(NumericArg::u8)
+      | Buffer::Bytes(BufferMode::Copy),
+    ) => V8FastCallType::ArrayBuffer,
+    Arg::ArrayBuffer(_) => return Ok(None),
     // Virtual OpState arguments
     Arg::RcRefCell(Special::OpState)
     | Arg::Ref(_, Special::OpState)
@@ -478,6 +507,7 @@ fn map_arg_to_v8_fastcall_type(
     Arg::OptionNumeric(..)
     | Arg::Option(_)
     | Arg::OptionString(_)
+    | Arg::OptionArrayBuffer(_)
     | Arg::OptionBuffer(_)
     | Arg::SerdeV8(_)
     | Arg::Ref(..) => return Ok(None),
@@ -565,7 +595,7 @@ fn map_retval_to_v8_fastcall_type(
     | Arg::V8Local(_)
     | Arg::OptionV8Local(_)
     | Arg::OptionV8Ref(..) => return Ok(None),
-    Arg::Buffer(..) => return Ok(None),
+    Arg::ArrayBuffer(..) | Arg::Buffer(..) => return Ok(None),
     Arg::External(..) => V8FastCallType::Pointer,
     _ => {
       return Err(V8MappingError::NoMapping(
diff --git a/ops/op2/dispatch_shared.rs b/ops/op2/dispatch_shared.rs
index d77c82a..ffc9c30 100644
--- a/ops/op2/dispatch_shared.rs
+++ b/ops/op2/dispatch_shared.rs
@@ -1,5 +1,8 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
 use super::signature::Arg;
+use super::signature::Buffer;
+use super::signature::BufferMode;
+use super::signature::NumericArg;
 use super::signature::RefType;
 use super::signature::V8Arg;
 use super::V8MappingError;
@@ -73,3 +76,95 @@ pub fn v8_to_arg(
     #extract_intermediate
   })
 }
+
+/// Given a V8Slice in `v8slice`, turns it into the appropriate slice type in `arg_ident`.
+pub fn v8slice_to_buffer(
+  deno_core: &TokenStream,
+  arg_ident: &Ident,
+  v8slice: &Ident,
+  buffer: Buffer,
+) -> Result<TokenStream, V8MappingError> {
+  let make_arg = match buffer {
+    Buffer::V8Slice(_, _) => {
+      quote!(let #arg_ident = #arg_ident;)
+    }
+    Buffer::Slice(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #v8slice.as_ref();)
+    }
+    Buffer::Slice(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #v8slice.as_mut();)
+    }
+    Buffer::Ptr(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = if #v8slice.len() == 0 { std::ptr::null() } else { #v8slice.as_ref().as_ptr() };)
+    }
+    Buffer::Ptr(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = if #v8slice.len() == 0 { std::ptr::null_mut() } else { #v8slice.as_mut().as_mut_ptr() };)
+    }
+    Buffer::Vec(NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #v8slice.to_vec();)
+    }
+    Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #v8slice.to_boxed_slice();)
+    }
+    Buffer::Bytes(BufferMode::Copy) => {
+      quote!(let #arg_ident = #v8slice.to_vec().into();)
+    }
+    Buffer::JsBuffer(BufferMode::Default | BufferMode::Detach) => {
+      quote!(let #arg_ident = #deno_core::serde_v8::JsBuffer::from_parts(#v8slice);)
+    }
+    _ => {
+      return Err(V8MappingError::NoMapping(
+        "a v8slice argument",
+        Arg::Buffer(buffer),
+      ))
+    }
+  };
+  Ok(make_arg)
+}
+
+pub fn byte_slice_to_buffer(
+  arg_ident: &Ident,
+  buf: &Ident,
+  buffer: Buffer,
+) -> Result<TokenStream, V8MappingError> {
+  let res = match buffer {
+    Buffer::Slice(_, NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #buf;)
+    }
+    Buffer::Ptr(_, NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = if #buf.len() == 0 { ::std::ptr::null_mut() } else { #buf.as_mut_ptr() as _ };)
+    }
+    Buffer::Vec(NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #buf.to_vec();)
+    }
+    Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
+      quote!(let #arg_ident = #buf.to_vec().into_boxed_slice();)
+    }
+    Buffer::Bytes(BufferMode::Copy) => {
+      quote!(let #arg_ident = #buf.to_vec().into();)
+    }
+    _ => {
+      return Err(V8MappingError::NoMapping(
+        "a fast typed array buffer argument",
+        Arg::Buffer(buffer),
+      ))
+    }
+  };
+
+  Ok(res)
+}
+
+pub fn fast_api_typed_array_to_buffer(
+  deno_core: &TokenStream,
+  arg_ident: &Ident,
+  input: &Ident,
+  buffer: Buffer,
+) -> Result<TokenStream, V8MappingError> {
+  let convert = byte_slice_to_buffer(arg_ident, input, buffer)?;
+  Ok(quote! {
+    // SAFETY: we are certain the implied lifetime is valid here as the slices never escape the
+    // fastcall
+    let #input = unsafe { #deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(#input) }.expect("Invalid buffer");
+    #convert
+  })
+}
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 0208dbb..dc46d6b 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -3,9 +3,15 @@ use super::config::MacroConfig;
 use super::dispatch_shared::v8_intermediate_to_arg;
 use super::dispatch_shared::v8_intermediate_to_global_arg;
 use super::dispatch_shared::v8_to_arg;
+use super::dispatch_shared::v8slice_to_buffer;
+use super::generator_state::gs_extract;
+use super::generator_state::gs_quote;
 use super::generator_state::GeneratorState;
 use super::signature::Arg;
+use super::signature::ArgMarker;
+use super::signature::ArgSlowRetval;
 use super::signature::Buffer;
+use super::signature::BufferMode;
 use super::signature::External;
 use super::signature::NumericArg;
 use super::signature::NumericFlag;
@@ -15,11 +21,6 @@ use super::signature::RetVal;
 use super::signature::Special;
 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;
 use quote::format_ident;
@@ -321,19 +322,21 @@ pub fn from_arg(
         };
       })
     }
-    Arg::Buffer(buffer) => {
+    Arg::Buffer(_) | Arg::ArrayBuffer(_) => {
       // Explicit temporary lifetime extension so we can take a reference
       let temp = format_ident!("{}_temp", arg_ident);
-      let buffer = from_arg_buffer(generator_state, &arg_ident, buffer, &temp)?;
+      let buffer =
+        from_arg_array_or_buffer(generator_state, &arg_ident, arg, &temp)?;
       quote! {
         let mut #temp;
         #buffer
       }
     }
-    Arg::OptionBuffer(buffer) => {
+    Arg::OptionBuffer(_) | Arg::OptionArrayBuffer(_) => {
       // Explicit temporary lifetime extension so we can take a reference
       let temp = format_ident!("{}_temp", arg_ident);
-      let some = from_arg_buffer(generator_state, &arg_ident, buffer, &temp)?;
+      let some =
+        from_arg_array_or_buffer(generator_state, &arg_ident, arg, &temp)?;
       quote! {
         let mut #temp;
         let #arg_ident = if #arg_ident.is_null_or_undefined() {
@@ -483,6 +486,23 @@ pub fn from_arg_option(
   )))
 }
 
+pub fn from_arg_array_or_buffer(
+  generator_state: &mut GeneratorState,
+  arg_ident: &Ident,
+  buffer: &Arg,
+  temp: &Ident,
+) -> Result<TokenStream, V8MappingError> {
+  match buffer {
+    Arg::Buffer(buffer) | Arg::OptionBuffer(buffer) => {
+      from_arg_buffer(generator_state, arg_ident, buffer, temp)
+    }
+    Arg::ArrayBuffer(buffer) | Arg::OptionArrayBuffer(buffer) => {
+      from_arg_arraybuffer(generator_state, arg_ident, buffer, temp)
+    }
+    _ => unreachable!(),
+  }
+}
+
 pub fn from_arg_buffer(
   generator_state: &mut GeneratorState,
   arg_ident: &Ident,
@@ -492,9 +512,8 @@ pub fn from_arg_buffer(
   let err = format_ident!("{}_err", arg_ident);
   let throw_exception = throw_type_error_static_string(generator_state, &err)?;
 
-  generator_state.needs_scope = true;
-
   let array = buffer.element();
+  generator_state.needs_scope = true;
 
   let to_v8_slice = if matches!(buffer, Buffer::JsBuffer(BufferMode::Detach)) {
     quote!(to_v8_slice_detachable)
@@ -511,39 +530,42 @@ pub fn from_arg_buffer(
     };
   });
 
-  let make_arg = match buffer {
-    Buffer::Slice(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
-      quote!(let #arg_ident = #temp.as_ref();)
-    }
-    Buffer::Slice(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
-      quote!(let #arg_ident = #temp.as_mut();)
-    }
-    Buffer::Ptr(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
-      quote!(let #arg_ident = if #temp.len() == 0 { std::ptr::null() } else { #temp.as_ref().as_ptr() };)
-    }
-    Buffer::Ptr(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
-      quote!(let #arg_ident = if #temp.len() == 0 { std::ptr::null_mut() } else { #temp.as_mut().as_mut_ptr() };)
-    }
-    Buffer::Vec(NumericArg::u8 | NumericArg::u32) => {
-      quote!(let #arg_ident = #temp.to_vec();)
-    }
-    Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
-      quote!(let #arg_ident = #temp.to_boxed_slice();)
-    }
-    Buffer::Bytes(BufferMode::Copy) => {
-      quote!(let #arg_ident = #temp.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(#temp);))
-    }
-    _ => {
-      return Err(V8MappingError::NoMapping(
-        "a buffer argument",
-        Arg::Buffer(*buffer),
-      ))
-    }
+  let make_arg =
+    v8slice_to_buffer(&generator_state.deno_core, arg_ident, temp, *buffer)?;
+
+  Ok(quote! {
+    #make_v8slice
+    #make_arg
+  })
+}
+
+pub fn from_arg_arraybuffer(
+  generator_state: &mut GeneratorState,
+  arg_ident: &Ident,
+  buffer: &Buffer,
+  temp: &Ident,
+) -> Result<TokenStream, V8MappingError> {
+  let err = format_ident!("{}_err", arg_ident);
+  let throw_exception = throw_type_error_static_string(generator_state, &err)?;
+
+  let to_v8_slice = if matches!(buffer, Buffer::JsBuffer(BufferMode::Detach)) {
+    quote!(to_v8_slice_buffer_detachable)
+  } else {
+    quote!(to_v8_slice_buffer)
   };
 
+  let make_v8slice = gs_quote!(generator_state(deno_core) => {
+    #temp = match unsafe { #deno_core::_ops::#to_v8_slice(#arg_ident) } {
+      Ok(#arg_ident) => #arg_ident,
+      Err(#err) => {
+        #throw_exception
+      }
+    };
+  });
+
+  let make_arg =
+    v8slice_to_buffer(&generator_state.deno_core, arg_ident, temp, *buffer)?;
+
   Ok(quote! {
     #make_v8slice
     #make_arg
@@ -581,6 +603,9 @@ pub fn return_value_infallible(
   generator_state.needs_retval = true;
 
   let result = match ret_type.marker() {
+    ArgMarker::ArrayBuffer => {
+      gs_quote!(generator_state(deno_core, result) => (#deno_core::_ops::RustToV8Marker::<#deno_core::_ops::ArrayBufferMarker, _>::from(#result)))
+    }
     ArgMarker::Serde => {
       gs_quote!(generator_state(deno_core, result) => (#deno_core::_ops::RustToV8Marker::<#deno_core::_ops::SerdeMarker, _>::from(#result)))
     }
@@ -645,6 +670,9 @@ pub fn return_value_v8_value(
 ) -> Result<TokenStream, V8MappingError> {
   gs_extract!(generator_state(deno_core, scope, result));
   let result = match ret_type.marker() {
+    ArgMarker::ArrayBuffer => {
+      quote!(#deno_core::_ops::RustToV8Marker::<#deno_core::_ops::ArrayBufferMarker, _>::from(#result))
+    }
     ArgMarker::Serde => {
       quote!(#deno_core::_ops::RustToV8Marker::<#deno_core::_ops::SerdeMarker, _>::from(#result))
     }
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 0e74b28..bbf9e4f 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -179,18 +179,28 @@ impl Buffer {
     &self,
     position: Position,
   ) -> &'static [AttributeModifier] {
+    use AttributeModifier::ArrayBuffer as A;
     use AttributeModifier::Buffer as B;
     use Buffer::*;
     use BufferMode::*;
     match position {
       Position::Arg => match self {
-        Bytes(..) | BytesMut(..) | Vec(..) | BoxSlice(..) => &[B(Copy)],
-        JsBuffer(..) | V8Slice(..) => &[B(Copy), B(Detach), B(Default)],
-        Slice(..) | Ptr(..) => &[B(Default)],
+        Bytes(..) | BytesMut(..) | Vec(..) | BoxSlice(..) => {
+          &[A(Copy), B(Copy)]
+        }
+        JsBuffer(..) | V8Slice(..) => &[
+          A(Copy),
+          A(Detach),
+          A(Default),
+          B(Copy),
+          B(Detach),
+          B(Default),
+        ],
+        Slice(..) | Ptr(..) => &[A(Default), B(Default)],
       },
       Position::RetVal => match self {
         Bytes(..) | BytesMut(..) | JsBuffer(..) | V8Slice(..) | Vec(..)
-        | BoxSlice(..) => &[B(Default)],
+        | BoxSlice(..) => &[A(Default), B(Default)],
         Slice(..) | Ptr(..) => &([]),
       },
     }
@@ -234,6 +244,7 @@ pub enum Arg {
   Void,
   Special(Special),
   String(Strings),
+  ArrayBuffer(Buffer),
   Buffer(Buffer),
   External(External),
   Ref(RefType, Special),
@@ -241,6 +252,7 @@ pub enum Arg {
   Option(Special),
   OptionString(Strings),
   OptionNumeric(NumericArg, NumericFlag),
+  OptionArrayBuffer(Buffer),
   OptionBuffer(Buffer),
   OptionV8Local(V8Arg),
   OptionV8Global(V8Arg),
@@ -255,6 +267,44 @@ pub enum Arg {
 }
 
 impl Arg {
+  const fn from_parsed(
+    parsed: ParsedTypeContainer,
+    attr: Attributes,
+  ) -> Result<Self, ()> {
+    use ParsedType::*;
+    use ParsedTypeContainer::*;
+    match parsed {
+      CBare(TNumeric(numeric)) => Ok(Arg::Numeric(numeric, NumericFlag::None)),
+      CBare(TSpecial(special)) => Ok(Arg::Special(special)),
+      CBare(TString(string)) => Ok(Arg::String(string)),
+      CBare(TBuffer(buffer))
+        if matches!(attr.primary, Some(AttributeModifier::ArrayBuffer(_))) =>
+      {
+        Ok(Arg::ArrayBuffer(buffer))
+      }
+      CBare(TBuffer(buffer)) => Ok(Arg::Buffer(buffer)),
+      COption(TNumeric(special)) => {
+        Ok(Arg::OptionNumeric(special, NumericFlag::None))
+      }
+      COption(TSpecial(special)) => Ok(Arg::Option(special)),
+      COption(TString(string)) => Ok(Arg::OptionString(string)),
+      COption(TBuffer(buffer))
+        if matches!(attr.primary, Some(AttributeModifier::ArrayBuffer(_))) =>
+      {
+        Ok(Arg::OptionArrayBuffer(buffer))
+      }
+      COption(TBuffer(buffer)) => Ok(Arg::OptionBuffer(buffer)),
+      CRcRefCell(TSpecial(special)) => Ok(Arg::RcRefCell(special)),
+      COptionV8Local(TV8(v8)) => Ok(Arg::OptionV8Local(v8)),
+      COptionV8Global(TV8(v8)) => Ok(Arg::OptionV8Global(v8)),
+      COption(TV8(v8)) => Ok(Arg::OptionV8Ref(RefType::Ref, v8)),
+      COption(TV8Mut(v8)) => Ok(Arg::OptionV8Ref(RefType::Mut, v8)),
+      CV8Local(TV8(v8)) => Ok(Arg::V8Local(v8)),
+      CV8Global(TV8(v8)) => Ok(Arg::V8Global(v8)),
+      _ => Err(()),
+    }
+  }
+
   /// Is this argument virtual? ie: does it come from the æther rather than a concrete JavaScript input
   /// argument?
   #[allow(clippy::match_like_matches_macro)]
@@ -332,6 +382,7 @@ impl Arg {
       Arg::OptionNumeric(t, flag) => Arg::Numeric(*t, *flag),
       Arg::Option(t) => Arg::Special(*t),
       Arg::OptionString(t) => Arg::String(*t),
+      Arg::OptionArrayBuffer(t) => Arg::ArrayBuffer(*t),
       Arg::OptionBuffer(t) => Arg::Buffer(*t),
       Arg::OptionState(r, t) => Arg::State(*r, t.clone()),
       _ => return None,
@@ -372,6 +423,16 @@ impl Arg {
         // No scope required for these
         Arg::V8Local(_) => ArgSlowRetval::V8LocalNoScope,
         Arg::V8Global(_) => ArgSlowRetval::V8Local,
+        Arg::ArrayBuffer(
+          Buffer::JsBuffer(BufferMode::Default)
+          | Buffer::Vec(NumericArg::u8 | NumericArg::u32)
+          | Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)
+          | Buffer::V8Slice(
+            BufferMode::Default,
+            NumericArg::u8 | NumericArg::u32,
+          )
+          | Buffer::BytesMut(BufferMode::Default),
+        ) => ArgSlowRetval::V8Local,
         Arg::Buffer(
           Buffer::JsBuffer(BufferMode::Default)
           | Buffer::Vec(NumericArg::u8 | NumericArg::u32)
@@ -382,6 +443,12 @@ impl Arg {
           )
           | Buffer::BytesMut(BufferMode::Default),
         ) => ArgSlowRetval::V8LocalFalliable,
+        Arg::OptionArrayBuffer(
+          Buffer::JsBuffer(BufferMode::Default)
+          | Buffer::Vec(NumericArg::u8 | NumericArg::u32)
+          | Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)
+          | Buffer::BytesMut(BufferMode::Default),
+        ) => ArgSlowRetval::V8Local,
         Arg::OptionBuffer(
           Buffer::JsBuffer(BufferMode::Default)
           | Buffer::Vec(NumericArg::u8 | NumericArg::u32)
@@ -396,6 +463,7 @@ impl Arg {
   /// Does this type have a marker (used for specialization of serialization/deserialization)?
   pub fn marker(&self) -> ArgMarker {
     match self {
+      Arg::ArrayBuffer(_) | Arg::OptionArrayBuffer(_) => ArgMarker::ArrayBuffer,
       Arg::SerdeV8(_) => ArgMarker::Serde,
       Arg::Numeric(NumericArg::__SMI__, _) => ArgMarker::Smi,
       Arg::Numeric(_, NumericFlag::Number) => ArgMarker::Number,
@@ -433,6 +501,8 @@ pub enum ArgMarker {
   Smi,
   /// This type should be serialized as a number.
   Number,
+  /// This buffer type should be serialized as an ArrayBuffer.
+  ArrayBuffer,
 }
 
 pub enum ParsedType {
@@ -617,6 +687,8 @@ pub enum AttributeModifier {
   State,
   /// #[buffer], for buffers.
   Buffer(BufferMode),
+  /// #[arraybuffer], for buffers backed directly by v8::ArrayBuffer rather than views.
+  ArrayBuffer(BufferMode),
   /// #[global], for [`v8::Global`]s
   Global,
   /// #[bigint], for u64/usize/i64/isize indicating value is a BigInt
@@ -629,7 +701,8 @@ impl AttributeModifier {
   fn name(&self) -> &'static str {
     match self {
       AttributeModifier::Bigint => "bigint",
-      AttributeModifier::Number => "bigint",
+      AttributeModifier::Number => "number",
+      AttributeModifier::ArrayBuffer(_) => "buffer",
       AttributeModifier::Buffer(_) => "buffer",
       AttributeModifier::Smi => "smi",
       AttributeModifier::Serde => "serde",
@@ -1018,6 +1091,10 @@ fn parse_attribute(
       (#[buffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe)),
       (#[buffer(copy)]) => Some(AttributeModifier::Buffer(BufferMode::Copy)),
       (#[buffer(detach)]) => Some(AttributeModifier::Buffer(BufferMode::Detach)),
+      (#[arraybuffer]) => Some(AttributeModifier::ArrayBuffer(BufferMode::Default)),
+      (#[arraybuffer(unsafe)]) => Some(AttributeModifier::ArrayBuffer(BufferMode::Unsafe)),
+      (#[arraybuffer(copy)]) => Some(AttributeModifier::ArrayBuffer(BufferMode::Copy)),
+      (#[arraybuffer(detach)]) => Some(AttributeModifier::ArrayBuffer(BufferMode::Detach)),
       (#[global]) => Some(AttributeModifier::Global),
       (#[allow ($_rule:path)]) => None,
       (#[doc = $_attr:literal]) => None,
@@ -1061,7 +1138,9 @@ fn parse_type_path(
   use ParsedTypeContainer::*;
 
   let buffer_mode = || match attrs.primary {
-    Some(AttributeModifier::Buffer(mode)) => Ok(mode),
+    Some(
+      AttributeModifier::Buffer(mode) | AttributeModifier::ArrayBuffer(mode),
+    ) => Ok(mode),
     _ => Err(ArgError::MissingAttribute("buffer", stringify_token(tp))),
   };
 
@@ -1115,7 +1194,7 @@ fn parse_type_path(
           Arg::Special(special) => Ok(COption(TSpecial(special))),
           Arg::String(string) => Ok(COption(TString(string))),
           Arg::Numeric(numeric, _) => Ok(COption(TNumeric(numeric))),
-          Arg::Buffer(buffer) => Ok(COption(TBuffer(buffer))),
+          Arg::Buffer(buffer) | Arg::ArrayBuffer(buffer) => Ok(COption(TBuffer(buffer))),
           Arg::V8Ref(RefType::Ref, v8) => Ok(COption(TV8(v8))),
           Arg::V8Ref(RefType::Mut, v8) => Ok(COption(TV8Mut(v8))),
           Arg::V8Local(v8) => Ok(COptionV8Local(TV8(v8))),
@@ -1259,6 +1338,7 @@ pub(crate) fn parse_type(
         return parse_type_state(ty);
       }
       AttributeModifier::String(_)
+      | AttributeModifier::ArrayBuffer(_)
       | AttributeModifier::Buffer(_)
       | AttributeModifier::Bigint
       | AttributeModifier::Global => {
@@ -1334,7 +1414,9 @@ pub(crate) fn parse_type(
                 let buffer = Buffer::Slice(mut_type, numeric);
                 let res = CBare(TBuffer(buffer));
                 res.validate_attributes(position, attrs, &of)?;
-                Ok(Arg::Buffer(buffer))
+                Arg::from_parsed(res, attrs).map_err(|_| {
+                  ArgError::InvalidType(stringify_token(ty), "for slice")
+                })
               }
             }
           } else {
@@ -1369,32 +1451,18 @@ pub(crate) fn parse_type(
             let buffer = Buffer::Ptr(mut_type, numeric);
             let res = CBare(TBuffer(buffer));
             res.validate_attributes(position, attrs, &of)?;
-            Ok(Arg::Buffer(buffer))
+            Arg::from_parsed(res, attrs).map_err(|_| {
+              ArgError::InvalidType(stringify_token(ty), "for pointer")
+            })
           }
         },
         _ => Err(ArgError::InvalidType(stringify_token(ty), "for pointer")),
       }
     }
-    Type::Path(of) => match parse_type_path(position, attrs, false, of)? {
-      CBare(TNumeric(numeric)) => Ok(Arg::Numeric(numeric, NumericFlag::None)),
-      CBare(TSpecial(special)) => Ok(Arg::Special(special)),
-      CBare(TString(string)) => Ok(Arg::String(string)),
-      CBare(TBuffer(buffer)) => Ok(Arg::Buffer(buffer)),
-      COption(TNumeric(special)) => {
-        Ok(Arg::OptionNumeric(special, NumericFlag::None))
-      }
-      COption(TSpecial(special)) => Ok(Arg::Option(special)),
-      COption(TString(string)) => Ok(Arg::OptionString(string)),
-      COption(TBuffer(buffer)) => Ok(Arg::OptionBuffer(buffer)),
-      CRcRefCell(TSpecial(special)) => Ok(Arg::RcRefCell(special)),
-      COptionV8Local(TV8(v8)) => Ok(Arg::OptionV8Local(v8)),
-      COptionV8Global(TV8(v8)) => Ok(Arg::OptionV8Global(v8)),
-      COption(TV8(v8)) => Ok(Arg::OptionV8Ref(RefType::Ref, v8)),
-      COption(TV8Mut(v8)) => Ok(Arg::OptionV8Ref(RefType::Mut, v8)),
-      CV8Local(TV8(v8)) => Ok(Arg::V8Local(v8)),
-      CV8Global(TV8(v8)) => Ok(Arg::V8Global(v8)),
-      _ => Err(ArgError::InvalidType(stringify_token(ty), "for path")),
-    },
+    Type::Path(of) => {
+      Arg::from_parsed(parse_type_path(position, attrs, false, of)?, attrs)
+        .map_err(|_| ArgError::InvalidType(stringify_token(ty), "for path"))
+    }
     _ => Err(ArgError::InvalidType(
       stringify_token(ty),
       "for top-level type",
@@ -1530,6 +1598,10 @@ mod tests {
     fn op_pointers(#[buffer] r#in: *const u8, #[buffer] out: *mut u8);
     (Buffer(Ptr(Ref, u8)), Buffer(Ptr(Mut, u8))) -> Infallible(Void)
   );
+  test!(
+    fn op_arraybuffer(#[arraybuffer] r#in: &[u8]);
+    (ArrayBuffer(Slice(Ref, u8))) -> Infallible(Void)
+  );
   test!(
     #[serde] fn op_serde(#[serde] input: package::SerdeInputType) -> Result<package::SerdeReturnType, Error>;
     (SerdeV8(package::SerdeInputType)) -> Result(SerdeV8(package::SerdeReturnType))
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index e5ab701..ac48e31 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -1,56 +1,63 @@
-| Supported | Rust                             | Fastcall | V8                                                  | Notes                                                                                                                                                                                                          |
-| --------- | -------------------------------- | -------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| X         | bool                             | X        | Bool                                                |                                                                                                                                                                                                                |
-| X         | i8                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | u8                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | i16                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | u16                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | i32                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | u32                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | #[smi] ResourceId                | X        | Uint32, Int32, Number, BigInt                       | 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        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | #[bigint] u64                    | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | #[bigint] isize                  | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | #[bigint] usize                  | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | f32                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | f64                              | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
-| X         | #[string] String                 | X        | String                                              | Fastcall available only if string is Latin-1. Will always create an allocated, UTF-8 copy of the String data.                                                                                                  |
-| X         | #[string] &str                   | X        | String                                              | Fastcall available only if string is Latin-1. Will create an owned `String` copy of the String data if it doesn't fit on the stack. Will never allocate in a fastcall, but will copy Latin-1 -> UTF-8.         |
-| X         | #[string] Cow<str>               | X        | String                                              | Fastcall available only if string is Latin-1. Will create a `Cow::Owned` copy of the String data if it doesn't fit on the stack. Will always be `Cow::Borrowed` in a fastcall, but will copy Latin-1 -> UTF-8. |
-| X         | #[string(onebyte)] Cow<[u8]>     | X        | String                                              | Fastest `String`-type method. If the string is not Latin-1, will throw a TypeError.                                                                                                                            |
-| X         | &v8::Value                       | X        | any                                                 |                                                                                                                                                                                                                |
-| X         | &v8::**V8**                      | X        | **V8**                                              |                                                                                                                                                                                                                |
-| X         | v8::Local<v8::Value>             | X        | any                                                 |                                                                                                                                                                                                                |
-| X         | v8::Local<v8::**V8**>            | X        | **V8**                                              |                                                                                                                                                                                                                |
-| X         | #[global] v8::Global<v8::Value>  |          | any                                                 | ⚠️ Slower than `v8::Local`.                                                                                                                                                                                     |
-| X         | #[global] v8::Global<v8::**V8**> |          | **V8**                                              | ⚠️ Slower than `v8::Local`.                                                                                                                                                                                     |
-| X         | #[serde] SerdeType               |          | any                                                 | ⚠️ May be slow.                                                                                                                                                                                                 |
-| X         | #[serde] (Tuple, Tuple)          |          | any                                                 | ⚠️ May be slow.                                                                                                                                                                                                 |
-| X         | #[buffer] &mut [u8]              | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
-| X         | #[buffer] &[u8]                  | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
-| X         | #[buffer] *mut u8                | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.                                                |
-| X         | #[buffer] *const u8              | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.                                                |
-| X         | #[buffer(copy)] Vec<u8>          | X        | UInt8Array (resizable=true,false)                   | Safe, but forces a copy.                                                                                                                                                                                       |
-| X         | #[buffer(copy)] Box<[u8]>        | X        | UInt8Array (resizable=true,false)                   | Safe, but forces a copy.                                                                                                                                                                                       |
-| X         | #[buffer(copy)] bytes::Bytes     | X        | UInt8Array (resizable=true,false)                   | Safe, but forces a copy.                                                                                                                                                                                       |
-| X         | #[buffer] &mut [u32]             | X        | UInt32Array (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
-| X         | #[buffer] &[u32]                 | X        | UInt32Array (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
-| X         | #[buffer(copy)] Vec<u32>         | X        | UInt32Array (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
-| X         | #[buffer(copy)] Box<[u32]>       | X        | UInt32Array (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
-|           | #[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.                                                                                                                                                   |
-| 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         | *const std::ffi::c_void          | X        | External                                            |                                                                                                                                                                                                                |
-| X         | *mut std::ffi::c_void            | X        | External                                            |                                                                                                                                                                                                                |
-| X         | &OpState                         | X        |                                                     |                                                                                                                                                                                                                |
-| X         | &mut OpState                     | X        |                                                     |                                                                                                                                                                                                                |
-| X         | Rc<RefCell<OpState>>             | X        |                                                     |                                                                                                                                                                                                                |
-| X         | #[state] &StateObject            | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
-| X         | #[state] &mut StateObject        | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
-| X         | &JsRuntimeState                  | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
-| X         | &mut JsRuntimeState              | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
-| X         | Rc<RefCell<JsRuntimeState>>      | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
+| Supported | Rust                              | Fastcall | V8                                                  | Notes                                                                                                                                                                                                          |
+| --------- | --------------------------------- | -------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| X         | bool                              | X        | Bool                                                |                                                                                                                                                                                                                |
+| X         | i8                                | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | u8                                | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | i16                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | u16                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | i32                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | u32                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | #[smi] ResourceId                 | X        | Uint32, Int32, Number, BigInt                       | 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        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | #[bigint] u64                     | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | #[bigint] isize                   | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | #[bigint] usize                   | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | f32                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | f64                               | X        | Uint32, Int32, Number, BigInt                       |                                                                                                                                                                                                                |
+| X         | #[string] String                  | X        | String                                              | Fastcall available only if string is Latin-1. Will always create an allocated, UTF-8 copy of the String data.                                                                                                  |
+| X         | #[string] &str                    | X        | String                                              | Fastcall available only if string is Latin-1. Will create an owned `String` copy of the String data if it doesn't fit on the stack. Will never allocate in a fastcall, but will copy Latin-1 -> UTF-8.         |
+| X         | #[string] Cow<str>                | X        | String                                              | Fastcall available only if string is Latin-1. Will create a `Cow::Owned` copy of the String data if it doesn't fit on the stack. Will always be `Cow::Borrowed` in a fastcall, but will copy Latin-1 -> UTF-8. |
+| X         | #[string(onebyte)] Cow<[u8]>      | X        | String                                              | Fastest `String`-type method. If the string is not Latin-1, will throw a TypeError.                                                                                                                            |
+| X         | &v8::Value                        | X        | any                                                 |                                                                                                                                                                                                                |
+| X         | &v8::**V8**                       | X        | **V8**                                              |                                                                                                                                                                                                                |
+| X         | v8::Local<v8::Value>              | X        | any                                                 |                                                                                                                                                                                                                |
+| X         | v8::Local<v8::**V8**>             | X        | **V8**                                              |                                                                                                                                                                                                                |
+| X         | #[global] v8::Global<v8::Value>   |          | any                                                 | ⚠️ Slower than `v8::Local`.                                                                                                                                                                                     |
+| X         | #[global] v8::Global<v8::**V8**>  |          | **V8**                                              | ⚠️ Slower than `v8::Local`.                                                                                                                                                                                     |
+| X         | #[serde] SerdeType                |          | any                                                 | ⚠️ May be slow.                                                                                                                                                                                                 |
+| X         | #[serde] (Tuple, Tuple)           |          | any                                                 | ⚠️ May be slow.                                                                                                                                                                                                 |
+| X         | #[arraybuffer] &mut [u8]          | X        | ArrayBuffer (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[arraybuffer] &[u8]              | X        | ArrayBuffer (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[arraybuffer] *mut u8            | X        | ArrayBuffer (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.                                                |
+| X         | #[arraybuffer] *const u8          | X        | ArrayBuffer (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.                                                |
+| X         | #[arraybuffer(copy)] Vec<u8>      | X        | ArrayBuffer (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[arraybuffer(copy)] Box<[u8]>    | X        | ArrayBuffer (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[arraybuffer(copy)] bytes::Bytes | X        | ArrayBuffer (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[buffer] &mut [u8]               | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[buffer] &[u8]                   | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[buffer] *mut u8                 | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.                                                |
+| X         | #[buffer] *const u8               | X        | UInt8Array (resizable=true,false)                   | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null.                                                |
+| X         | #[buffer(copy)] Vec<u8>           | X        | UInt8Array (resizable=true,false)                   | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[buffer(copy)] Box<[u8]>         | X        | UInt8Array (resizable=true,false)                   | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[buffer(copy)] bytes::Bytes      | X        | UInt8Array (resizable=true,false)                   | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[buffer] &mut [u32]              | X        | UInt32Array (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[buffer] &[u32]                  | X        | UInt32Array (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[buffer(copy)] Vec<u32>          | X        | UInt32Array (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
+| X         | #[buffer(copy)] Box<[u32]>        | X        | UInt32Array (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
+|           | #[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.                                                                                                                                                   |
+| 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         | *const std::ffi::c_void           | X        | External                                            |                                                                                                                                                                                                                |
+| X         | *mut std::ffi::c_void             | X        | External                                            |                                                                                                                                                                                                                |
+| X         | &OpState                          | X        |                                                     |                                                                                                                                                                                                                |
+| X         | &mut OpState                      | X        |                                                     |                                                                                                                                                                                                                |
+| X         | Rc<RefCell<OpState>>              | X        |                                                     |                                                                                                                                                                                                                |
+| X         | #[state] &StateObject             | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
+| X         | #[state] &mut StateObject         | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
+| X         | &JsRuntimeState                   | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
+| X         | &mut JsRuntimeState               | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
+| X         | Rc<RefCell<JsRuntimeState>>       | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
diff --git a/ops/op2/valid_retvals.md b/ops/op2/valid_retvals.md
index ededb55..1f87dad 100644
--- a/ops/op2/valid_retvals.md
+++ b/ops/op2/valid_retvals.md
@@ -22,6 +22,10 @@
 | X         | #[string] &str                   |          | String      |                                                                                                                                                                                                  |
 | X         | #[string] Cow<str>               |          | String      |                                                                                                                                                                                                  |
 | X         | #[string(onebyte)] Cow<[u8]>     |          | String      |                                                                                                                                                                                                  |
+| X         | #[arraybuffer] V8Slice<u8>       |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | #[arraybuffer] Vec<u8>           |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | #[arraybuffer] Box<[u8]>         |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | #[arraybuffer] bytes::BytesMut   |          | ArrayBuffer |                                                                                                                                                                                                  |
 | X         | #[buffer] V8Slice<u8>            |          | Uint8Array  |                                                                                                                                                                                                  |
 | X         | #[buffer] Vec<u8>                |          | Uint8Array  |                                                                                                                                                                                                  |
 | X         | #[buffer] Box<[u8]>              |          | Uint8Array  |                                                                                                                                                                                                  |
diff --git a/serde_v8/magic/v8slice.rs b/serde_v8/magic/v8slice.rs
index 9b417b4..78bdeb9 100644
--- a/serde_v8/magic/v8slice.rs
+++ b/serde_v8/magic/v8slice.rs
@@ -200,6 +200,15 @@ where
     )
   }
 
+  /// Takes this slice and converts it into a strongly-typed v8 array, ignoring the underlying range.
+  pub fn into_v8_unsliced_arraybuffer_local<'a>(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+  ) -> v8::Local<'a, v8::ArrayBuffer> {
+    let (store, _range) = self.into_parts();
+    v8::ArrayBuffer::with_backing_store(scope, &store)
+  }
+
   /// Returns the slice to the parts it came from.
   pub fn into_parts(self) -> (v8::SharedRef<v8::BackingStore>, Range<usize>) {
     (


Test patch:
diff --git a/ops/op2/test_cases/sync/buffers.out b/ops/op2/test_cases/sync/buffers.out
index 12eb1b6..08cc2c9 100644
--- a/ops/op2/test_cases/sync/buffers.out
+++ b/ops/op2/test_cases/sync/buffers.out
@@ -45,53 +45,41 @@ impl op_buffers {
         arg3: *mut deno_core::v8::fast_api::FastApiTypedArray<u8>,
     ) -> () {
         let result = {
-            let arg0 = (unsafe {
+            let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg0,
                 )
             }
-                .expect("Invalid buffer"));
-            let arg1 = (unsafe {
+                .expect("Invalid buffer");
+            let arg0 = arg0;
+            let arg1 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg1,
                 )
             }
-                .expect("Invalid buffer"));
-            let arg2 = if (unsafe {
+                .expect("Invalid buffer");
+            let arg1 = arg1;
+            let arg2 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg2,
                 )
             }
-                .expect("Invalid buffer"))
-                .len() == 0
-            {
+                .expect("Invalid buffer");
+            let arg2 = if arg2.len() == 0 {
                 ::std::ptr::null_mut()
             } else {
-                (unsafe {
-                    deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
-                        arg2,
-                    )
-                }
-                    .expect("Invalid buffer"))
-                    .as_mut_ptr() as _
+                arg2.as_mut_ptr() as _
             };
-            let arg3 = if (unsafe {
+            let arg3 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg3,
                 )
             }
-                .expect("Invalid buffer"))
-                .len() == 0
-            {
+                .expect("Invalid buffer");
+            let arg3 = if arg3.len() == 0 {
                 ::std::ptr::null_mut()
             } else {
-                (unsafe {
-                    deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
-                        arg3,
-                    )
-                }
-                    .expect("Invalid buffer"))
-                    .as_mut_ptr() as _
+                arg3.as_mut_ptr() as _
             };
             Self::call(arg0, arg1, arg2, arg3)
         };
@@ -246,53 +234,41 @@ impl op_buffers_32 {
         arg3: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
     ) -> () {
         let result = {
-            let arg0 = (unsafe {
+            let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg0,
                 )
             }
-                .expect("Invalid buffer"));
-            let arg1 = (unsafe {
+                .expect("Invalid buffer");
+            let arg0 = arg0;
+            let arg1 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg1,
                 )
             }
-                .expect("Invalid buffer"));
-            let arg2 = if (unsafe {
+                .expect("Invalid buffer");
+            let arg1 = arg1;
+            let arg2 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg2,
                 )
             }
-                .expect("Invalid buffer"))
-                .len() == 0
-            {
+                .expect("Invalid buffer");
+            let arg2 = if arg2.len() == 0 {
                 ::std::ptr::null_mut()
             } else {
-                (unsafe {
-                    deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
-                        arg2,
-                    )
-                }
-                    .expect("Invalid buffer"))
-                    .as_mut_ptr() as _
+                arg2.as_mut_ptr() as _
             };
-            let arg3 = if (unsafe {
+            let arg3 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg3,
                 )
             }
-                .expect("Invalid buffer"))
-                .len() == 0
-            {
+                .expect("Invalid buffer");
+            let arg3 = if arg3.len() == 0 {
                 ::std::ptr::null_mut()
             } else {
-                (unsafe {
-                    deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
-                        arg3,
-                    )
-                }
-                    .expect("Invalid buffer"))
-                    .as_mut_ptr() as _
+                arg3.as_mut_ptr() as _
             };
             Self::call(arg0, arg1, arg2, arg3)
         };
diff --git a/ops/op2/test_cases/sync/buffers_copy.out b/ops/op2/test_cases/sync/buffers_copy.out
index a68de40..c6f7307 100644
--- a/ops/op2/test_cases/sync/buffers_copy.out
+++ b/ops/op2/test_cases/sync/buffers_copy.out
@@ -43,29 +43,27 @@ impl op_buffers {
         arg2: *mut deno_core::v8::fast_api::FastApiTypedArray<u8>,
     ) -> () {
         let result = {
-            let arg0 = (unsafe {
+            let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg0,
                 )
             }
-                .expect("Invalid buffer"))
-                .to_vec();
-            let arg1 = (unsafe {
+                .expect("Invalid buffer");
+            let arg0 = arg0.to_vec();
+            let arg1 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg1,
                 )
             }
-                .expect("Invalid buffer"))
-                .to_vec()
-                .into_boxed_slice();
-            let arg2 = (unsafe {
+                .expect("Invalid buffer");
+            let arg1 = arg1.to_vec().into_boxed_slice();
+            let arg2 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg2,
                 )
             }
-                .expect("Invalid buffer"))
-                .to_vec()
-                .into();
+                .expect("Invalid buffer");
+            let arg2 = arg2.to_vec().into();
             Self::call(arg0, arg1, arg2)
         };
         result as _
@@ -188,21 +186,20 @@ impl op_buffers_32 {
         arg1: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
     ) -> () {
         let result = {
-            let arg0 = (unsafe {
+            let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg0,
                 )
             }
-                .expect("Invalid buffer"))
-                .to_vec();
-            let arg1 = (unsafe {
+                .expect("Invalid buffer");
+            let arg0 = arg0.to_vec();
+            let arg1 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
                     arg1,
                 )
             }
-                .expect("Invalid buffer"))
-                .to_vec()
-                .into_boxed_slice();
+                .expect("Invalid buffer");
+            let arg1 = arg1.to_vec().into_boxed_slice();
             Self::call(arg0, arg1)
         };
         result as _
diff --git a/ops/op2/test_cases/sync/buffers_out.out b/ops/op2/test_cases/sync/buffers_out.out
new file mode 100644
index 0000000..caaaf2c
--- /dev/null
+++ b/ops/op2/test_cases/sync/buffers_out.out
@@ -0,0 +1,225 @@
+#[allow(non_camel_case_types)]
+struct op_buffers {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_buffers {
+    const NAME: &'static str = stringify!(op_buffers);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_buffers),
+        false,
+        false,
+        false,
+        1usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_buffers {
+    pub const fn name() -> &'static str {
+        stringify!(op_buffers)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+            &*info
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let result = {
+            let arg0 = args.get(0usize as i32);
+            let mut arg0_temp;
+            arg0_temp = match unsafe {
+                deno_core::_ops::to_v8_slice::<u8>(&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::type_error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return;
+                }
+            };
+            let arg0 = deno_core::serde_v8::JsBuffer::from_parts(arg0_temp);
+            Self::call(arg0)
+        };
+        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::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            }
+        }
+    }
+    #[inline(always)]
+    fn call(buffer: JsBuffer) -> JsBuffer {
+        buffer
+    }
+}
+
+#[allow(non_camel_case_types)]
+struct op_buffers_option {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_buffers_option {
+    const NAME: &'static str = stringify!(op_buffers_option);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_buffers_option),
+        false,
+        false,
+        false,
+        1usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_buffers_option {
+    pub const fn name() -> &'static str {
+        stringify!(op_buffers_option)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+            &*info
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let result = {
+            let arg0 = args.get(0usize as i32);
+            let mut arg0_temp;
+            let arg0 = if arg0.is_null_or_undefined() {
+                None
+            } else {
+                arg0_temp = match unsafe {
+                    deno_core::_ops::to_v8_slice::<u8>(&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::type_error(&mut scope, msg);
+                        scope.throw_exception(exc);
+                        return;
+                    }
+                };
+                let arg0 = deno_core::serde_v8::JsBuffer::from_parts(arg0_temp);
+                Some(arg0)
+            };
+            Self::call(arg0)
+        };
+        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::type_error(&mut scope, msg);
+                scope.throw_exception(exc);
+                return;
+            }
+        }
+    }
+    #[inline(always)]
+    fn call(buffer: Option<JsBuffer>) -> Option<JsBuffer> {
+        buffer
+    }
+}
+
+#[allow(non_camel_case_types)]
+struct op_arraybuffers {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_arraybuffers {
+    const NAME: &'static str = stringify!(op_arraybuffers);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_arraybuffers),
+        false,
+        false,
+        false,
+        1usize as u8,
+        Self::v8_fn_ptr as _,
+        None,
+    );
+}
+impl op_arraybuffers {
+    pub const fn name() -> &'static str {
+        stringify!(op_arraybuffers)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+        let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+            &*info
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let result = {
+            let arg0 = args.get(0usize as i32);
+            let mut arg0_temp;
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice_buffer(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::type_error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return;
+                }
+            };
+            let arg0 = deno_core::serde_v8::JsBuffer::from_parts(arg0_temp);
+            Self::call(arg0)
+        };
+        rv.set(
+            deno_core::_ops::RustToV8::to_v8(
+                deno_core::_ops::RustToV8Marker::<
+                    deno_core::_ops::ArrayBufferMarker,
+                    _,
+                >::from(result),
+                &mut scope,
+            ),
+        )
+    }
+    #[inline(always)]
+    fn call(buffer: JsBuffer) -> JsBuffer {
+        buffer
+    }
+}
diff --git a/ops/op2/test_cases/sync/buffers_out.rs b/ops/op2/test_cases/sync/buffers_out.rs
new file mode 100644
index 0000000..c284dfb
--- /dev/null
+++ b/ops/op2/test_cases/sync/buffers_out.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+#![deny(warnings)]
+deno_ops_compile_test_runner::prelude!();
+
+use deno_core::JsBuffer;
+
+#[op2]
+#[buffer]
+fn op_buffers(#[buffer] buffer: JsBuffer) -> JsBuffer {
+  buffer
+}
+
+#[op2]
+#[buffer]
+fn op_buffers_option(#[buffer] buffer: Option<JsBuffer>) -> Option<JsBuffer> {
+  buffer
+}
+
+#[op2]
+#[arraybuffer]
+fn op_arraybuffers(#[arraybuffer] buffer: JsBuffer) -> JsBuffer {
+  buffer
+}
+
+// TODO(mmastrac): Option + Marker doesn't work yet
+
+// #[op2]
+// #[arraybuffer]
+// fn op_arraybuffers_option(#[arraybuffer] buffer: Option<JsBuffer>) -> Option<JsBuffer> {
+//   buffer
+// }