Repository: denoland/deno_core
Pull request #197 (2023-09-13 17:51:11)

feat(ops): support u32 slices


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0d7af1c..becf18a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -281,7 +281,7 @@ dependencies = [
 
 [[package]]
 name = "deno_core"
-version = "0.210.0"
+version = "0.211.0"
 dependencies = [
  "anyhow",
  "bencher",
diff --git a/Cargo.toml b/Cargo.toml
index d982443..16110dd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ repository = "https://github.com/denoland/deno_core"
 
 [workspace.dependencies]
 # Local dependencies
-deno_core = { version = "0.210.0", path = "./core" }
+deno_core = { version = "0.211.0", path = "./core" }
 deno_ops = { version = "0.88.0", path = "./ops" }
 serde_v8 = { version = "0.121.0", path = "./serde_v8" }
 
diff --git a/core/Cargo.toml b/core/Cargo.toml
index d3268f8..af942bf 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -2,7 +2,7 @@
 
 [package]
 name = "deno_core"
-version = "0.210.0"
+version = "0.211.0"
 authors.workspace = true
 edition.workspace = true
 license.workspace = true
diff --git a/core/io.rs b/core/io.rs
index 9d5e78f..e2ab5e6 100644
--- a/core/io.rs
+++ b/core/io.rs
@@ -24,7 +24,7 @@ pub struct BufView {
 enum BufViewInner {
   Empty,
   Bytes(bytes::Bytes),
-  JsBuffer(V8Slice),
+  JsBuffer(V8Slice<u8>),
 }
 
 impl BufView {
@@ -192,7 +192,7 @@ pub struct BufMutView {
 
 #[derive(Debug)]
 enum BufMutViewInner {
-  JsBuffer(V8Slice),
+  JsBuffer(V8Slice<u8>),
   Bytes(BytesMut),
 }
 
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
index bf7b8c2..ed73118 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -206,6 +206,11 @@ pub(crate) const BUILTIN_SOURCES: [ExtensionFileSource; 3] = include_js_files!(
 /// pending ops have completed.
 ///
 /// Use [`JsRuntimeForSnapshot`] to be able to create a snapshot.
+///
+/// Note: since V8 11.6, all runtimes must have a common parent thread that
+/// initalized the V8 platform. This can be done by calling
+/// [`JsRuntime::init_platform`] explicitly, or it will be done automatically on
+/// the calling thread when the first runtime is created.
 pub struct JsRuntime {
   pub(crate) inner: InnerIsolateState,
   pub(crate) allocations: IsolateAllocations,
@@ -447,6 +452,30 @@ pub struct CreateRealmOptions {
 }
 
 impl JsRuntime {
+  /// Explicitly initalizes the V8 platform using the passed platform. This
+  /// should only be called once per process. Further calls will be silently
+  /// ignored.
+  #[cfg(not(any(test, feature = "unsafe_runtime_options")))]
+  pub fn init_platform(v8_platform: Option<v8::SharedRef<v8::Platform>>) {
+    JsRuntime::init_v8(v8_platform, cfg!(test), false);
+  }
+
+  /// Explicitly initalizes the V8 platform using the passed platform. This
+  /// should only be called once per process. Further calls will be silently
+  /// ignored.
+  ///
+  /// The `expose_natives` flag is used to expose the v8 natives
+  /// (eg: %OptimizeFunctionOnNextCall) and GC control functions (`gc()`).
+  /// WARNING: This should not be used for production code as
+  /// this may expose the runtime to security vulnerabilities.
+  #[cfg(any(test, feature = "unsafe_runtime_options"))]
+  pub fn init_platform(
+    v8_platform: Option<v8::SharedRef<v8::Platform>>,
+    expose_natives: bool,
+  ) {
+    JsRuntime::init_v8(v8_platform, cfg!(test), expose_natives);
+  }
+
   /// Only constructor, configuration is done through `options`.
   pub fn new(mut options: RuntimeOptions) -> JsRuntime {
     JsRuntime::init_v8(
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 25be931..90e8ab7 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -9,6 +9,7 @@ use futures::future::FutureExt;
 use futures::task::noop_waker_ref;
 use serde::Deserialize;
 use serde_v8::from_v8;
+use serde_v8::V8Sliceable;
 use std::borrow::Cow;
 use std::cell::RefCell;
 use std::ffi::c_void;
@@ -524,25 +525,26 @@ pub fn serde_v8_to_rust<'a, T: Deserialize<'a>>(
 pub fn to_v8_slice<'a, T>(
   scope: &mut v8::HandleScope,
   input: v8::Local<'a, v8::Value>,
-) -> Result<serde_v8::V8Slice, &'static str>
+) -> Result<serde_v8::V8Slice<T>, &'static str>
 where
-  v8::Local<'a, T>: TryFrom<v8::Local<'a, v8::Value>>,
-  v8::Local<'a, v8::ArrayBufferView>: From<v8::Local<'a, T>>,
+  T: V8Sliceable,
+  v8::Local<'a, T::V8>: TryFrom<v8::Local<'a, v8::Value>>,
+  v8::Local<'a, v8::ArrayBufferView>: From<v8::Local<'a, T::V8>>,
 {
-  let (store, offset, length) = if let Ok(buf) = v8::Local::<T>::try_from(input)
-  {
-    let buf: v8::Local<v8::ArrayBufferView> = buf.into();
-    let Some(buffer) = buf.buffer(scope) else {
+  let (store, offset, length) =
+    if let Ok(buf) = v8::Local::<T::V8>::try_from(input) {
+      let buf: v8::Local<v8::ArrayBufferView> = buf.into();
+      let Some(buffer) = buf.buffer(scope) else {
       return Err("buffer missing");
     };
-    (
-      buffer.get_backing_store(),
-      buf.byte_offset(),
-      buf.byte_length(),
-    )
-  } else {
-    return Err("expected typed ArrayBufferView");
-  };
+      (
+        buffer.get_backing_store(),
+        buf.byte_offset(),
+        buf.byte_length(),
+      )
+    } else {
+      return Err("expected typed ArrayBufferView");
+    };
   let slice =
     unsafe { serde_v8::V8Slice::from_parts(store, offset..(offset + length)) };
   Ok(slice)
@@ -552,30 +554,31 @@ where
 pub fn to_v8_slice_detachable<'a, T>(
   scope: &mut v8::HandleScope,
   input: v8::Local<'a, v8::Value>,
-) -> Result<serde_v8::V8Slice, &'static str>
+) -> Result<serde_v8::V8Slice<T>, &'static str>
 where
-  v8::Local<'a, T>: TryFrom<v8::Local<'a, v8::Value>>,
-  v8::Local<'a, v8::ArrayBufferView>: From<v8::Local<'a, T>>,
+  T: V8Sliceable,
+  v8::Local<'a, T::V8>: TryFrom<v8::Local<'a, v8::Value>>,
+  v8::Local<'a, v8::ArrayBufferView>: From<v8::Local<'a, T::V8>>,
 {
-  let (store, offset, length) = if let Ok(buf) = v8::Local::<T>::try_from(input)
-  {
-    let buf: v8::Local<v8::ArrayBufferView> = buf.into();
-    let Some(buffer) = buf.buffer(scope) else {
+  let (store, offset, length) =
+    if let Ok(buf) = v8::Local::<T::V8>::try_from(input) {
+      let buf: v8::Local<v8::ArrayBufferView> = buf.into();
+      let Some(buffer) = buf.buffer(scope) else {
       return Err("buffer missing");
     };
-    let res = (
-      buffer.get_backing_store(),
-      buf.byte_offset(),
-      buf.byte_length(),
-    );
-    if !buffer.is_detachable() {
-      return Err("invalid type; expected: detachable");
-    }
-    buffer.detach(None);
-    res
-  } else {
-    return Err("expected typed ArrayBufferView");
-  };
+      let res = (
+        buffer.get_backing_store(),
+        buf.byte_offset(),
+        buf.byte_length(),
+      );
+      if !buffer.is_detachable() {
+        return Err("invalid type; expected: detachable");
+      }
+      buffer.detach(None);
+      res
+    } else {
+      return Err("expected typed ArrayBufferView");
+    };
   let slice =
     unsafe { serde_v8::V8Slice::from_parts(store, offset..(offset + length)) };
   Ok(slice)
@@ -649,6 +652,7 @@ mod tests {
       op_state_mut_attr,
       op_state_multi_attr,
       op_buffer_slice,
+      op_buffer_slice_32,
       op_buffer_slice_unsafe_callback,
       op_buffer_copy,
       op_buffer_bytesmut,
@@ -1464,54 +1468,79 @@ mod tests {
     output[0] = input[0];
   }
 
+  #[op2(core, fast)]
+  pub fn op_buffer_slice_32(
+    #[buffer] input: &[u32],
+    #[bigint] inlen: usize,
+    #[buffer] output: &mut [u32],
+    #[bigint] outlen: usize,
+  ) {
+    assert_eq!(inlen, input.len());
+    assert_eq!(outlen, output.len());
+    output[0] = input[0];
+  }
+
   #[tokio::test]
   pub async fn test_op_buffer_slice() -> Result<(), Box<dyn std::error::Error>>
   {
-    // Uint8Array -> Uint8Array
-    run_test2(
-      10000,
-      "op_buffer_slice",
-      r"
-      let out = new Uint8Array(10);
-      op_buffer_slice(new Uint8Array([1,2,3]), 3, out, 10);
-      assert(out[0] == 1);",
-    )?;
-    // Uint8Array(ArrayBuffer) -> Uint8Array(ArrayBuffer)
-    run_test2(
-      10000,
-      "op_buffer_slice",
-      r"
-      let inbuf = new ArrayBuffer(10);
-      let in_u8 = new Uint8Array(inbuf);
-      in_u8[0] = 1;
-      let out = new ArrayBuffer(10);
-      op_buffer_slice(in_u8, 10, new Uint8Array(out), 10);
-      assert(new Uint8Array(out)[0] == 1);",
-    )?;
-    // Uint8Array(ArrayBuffer, 5, 5) -> Uint8Array(ArrayBuffer)
-    run_test2(
-      10000,
-      "op_buffer_slice",
-      r"
-      let inbuf = new ArrayBuffer(10);
-      let in_u8 = new Uint8Array(inbuf);
-      in_u8[5] = 1;
-      let out = new ArrayBuffer(10);
-      op_buffer_slice(new Uint8Array(inbuf, 5, 5), 5, new Uint8Array(out), 10);
-      assert(new Uint8Array(out)[0] == 1);",
-    )?;
-    // Resizable
-    run_test2(
-      10000,
-      "op_buffer_slice",
-      r"
-      let inbuf = new ArrayBuffer(10, { maxByteLength: 100 });
-      let in_u8 = new Uint8Array(inbuf);
-      in_u8[5] = 1;
-      let out = new ArrayBuffer(10, { maxByteLength: 100 });
-      op_buffer_slice(new Uint8Array(inbuf, 5, 5), 5, new Uint8Array(out), 10);
-      assert(new Uint8Array(out)[0] == 1);",
-    )?;
+    for (op, arr, size) in [
+      ("op_buffer_slice", "Uint8Array", 1),
+      ("op_buffer_slice_32", "Uint32Array", 4),
+    ] {
+      // UintXArray -> UintXArray
+      run_test2(
+        10000,
+        op,
+        &format!(
+          r"
+        let out = new {arr}(10);
+        {op}(new {arr}([1,2,3]), 3, out, 10);
+        assert(out[0] == 1);"
+        ),
+      )?;
+      // UintXArray(ArrayBuffer) -> UintXArray(ArrayBuffer)
+      run_test2(
+        10000,
+        op,
+        &format!(
+          r"
+        let inbuf = new ArrayBuffer(10 * {size});
+        let in_u8 = new {arr}(inbuf);
+        in_u8[0] = 1;
+        let out = new ArrayBuffer(10 * {size});
+        {op}(in_u8, 10, new {arr}(out), 10);
+        assert(new {arr}(out)[0] == 1);"
+        ),
+      )?;
+      // UintXArray(ArrayBuffer, 5, 5) -> UintXArray(ArrayBuffer)
+      run_test2(
+        10000,
+        op,
+        &format!(
+          r"
+        let inbuf = new ArrayBuffer(10 * {size});
+        let in_u8 = new {arr}(inbuf);
+        in_u8[5] = 1;
+        let out = new ArrayBuffer(10 * {size});
+        {op}(new {arr}(inbuf, 5 * {size}, 5), 5, new {arr}(out), 10);
+        assert(new {arr}(out)[0] == 1);"
+        ),
+      )?;
+      // Resizable
+      run_test2(
+        10000,
+        op,
+        &format!(
+          r"
+        let inbuf = new ArrayBuffer(10 * {size}, {{ maxByteLength: 100 * {size} }});
+        let in_u8 = new {arr}(inbuf);
+        in_u8[5] = 1;
+        let out = new ArrayBuffer(10 * {size}, {{ maxByteLength: 100 * {size} }});
+        {op}(new {arr}(inbuf, 5 * {size}, 5), 5, new {arr}(out), 10);
+        assert(new {arr}(out)[0] == 1);"
+        ),
+      )?;
+    }
     Ok(())
   }
 
diff --git a/core/runtime/ops_rust_to_v8.rs b/core/runtime/ops_rust_to_v8.rs
index 7254a18..6fe91d5 100644
--- a/core/runtime/ops_rust_to_v8.rs
+++ b/core/runtime/ops_rust_to_v8.rs
@@ -306,10 +306,11 @@ to_v8_retval_fallible!(Cow<'a, [u8]>: |value, scope, rv| {
 // Buffers
 //
 
-to_v8_fallible!(serde_v8::V8Slice: |value, scope| {
-  let (buffer, range) = value.into_parts();
-  let buffer = v8::ArrayBuffer::with_backing_store(scope, &buffer);
-  v8::Uint8Array::new(scope, buffer, range.start, range.len()).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+to_v8_fallible!(serde_v8::V8Slice<u8>: |value, scope| {
+  value.into_v8_local(scope).ok_or_else(|| serde_v8::Error::Message("failed to allocate array".into()))
+});
+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_fallible!(serde_v8::JsBuffer: |value, scope| {
   value.into_parts().to_v8_fallible(scope)
diff --git a/ops/op2/README.md b/ops/op2/README.md
index 23d2957..26bbacd 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -606,6 +606,62 @@ Safe, but forces a copy.
 <tr>
 <td>
 
+```text
+#[buffer] &mut [u32]
+```
+
+</td><td>
+✅
+</td><td>
+UInt32Array (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[buffer] &[u32]
+```
+
+</td><td>
+✅
+</td><td>
+UInt32Array (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[buffer(copy)] Vec<u32>
+```
+
+</td><td>
+✅
+</td><td>
+UInt32Array (resizable=true,false)
+</td><td>
+Safe, but forces a copy.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[buffer(copy)] Box<[u32]>
+```
+
+</td><td>
+✅
+</td><td>
+UInt32Array (resizable=true,false)
+</td><td>
+Safe, but forces a copy.
+</td></tr>
+<tr>
+<td>
+
 ```text
 #[buffer] JsBuffer
 ```
@@ -1028,6 +1084,22 @@ f64
 <tr>
 <td>
 
+```text
+#[buffer] V8Slice<u8>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
 ```text
 #[buffer] Vec<u8>
 ```
@@ -1076,6 +1148,22 @@ f64
 <tr>
 <td>
 
+```text
+#[buffer] V8Slice<u32>
+```
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td><td>
+
+</td></tr>
+<tr>
+<td>
+
 ```text
 *const std::ffi::c_void
 ```
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 9697bb5..3383fd0 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -305,13 +305,13 @@ fn map_v8_fastcall_arg_to_arg(
 ) -> Result<TokenStream, V8MappingError> {
   let arg_temp = format_ident!("{}_temp", arg_ident);
   let res = match arg {
-    Arg::Buffer(Buffer::Slice(_, NumericArg::u8)) => {
+    Arg::Buffer(Buffer::Slice(_, NumericArg::u8 | NumericArg::u32)) => {
       quote!(let #arg_ident = unsafe { #arg_ident.as_mut().unwrap() }.get_storage_if_aligned().unwrap();)
     }
-    Arg::Buffer(Buffer::Vec(NumericArg::u8)) => {
+    Arg::Buffer(Buffer::Vec(NumericArg::u8 | NumericArg::u32)) => {
       quote!(let #arg_ident = unsafe { #arg_ident.as_mut().unwrap() }.get_storage_if_aligned().unwrap().to_vec();)
     }
-    Arg::Buffer(Buffer::BoxSlice(NumericArg::u8)) => {
+    Arg::Buffer(Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)) => {
       quote!(let #arg_ident = unsafe { #arg_ident.as_mut().unwrap() }.get_storage_if_aligned().unwrap().to_vec().into_boxed_slice();)
     }
     Arg::Buffer(Buffer::Bytes(BufferMode::Copy)) => {
@@ -423,6 +423,11 @@ fn map_arg_to_v8_fastcall_type(
       | Buffer::BoxSlice(NumericArg::u8)
       | Buffer::Bytes(BufferMode::Copy),
     ) => V8FastCallType::Uint8Array,
+    Arg::Buffer(
+      Buffer::Slice(_, NumericArg::u32)
+      | Buffer::Vec(NumericArg::u32)
+      | Buffer::BoxSlice(NumericArg::u32),
+    ) => V8FastCallType::Uint32Array,
     Arg::Buffer(_) => return Ok(None),
     // Virtual OpState arguments
     Arg::RcRefCell(Special::OpState)
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index b7662c6..6c5dade 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -458,10 +458,7 @@ pub fn from_arg_buffer(
 
   generator_state.needs_scope = true;
 
-  // TODO(mmastrac): Other buffer types
-  let array = NumericArg::u8
-    .v8_array_type()
-    .expect("Could not retrieve the v8 type");
+  let array = buffer.element();
 
   let to_v8_slice = if matches!(buffer, Buffer::JsBuffer(BufferMode::Detach)) {
     quote!(to_v8_slice_detachable)
@@ -470,7 +467,7 @@ pub fn from_arg_buffer(
   };
 
   let make_v8slice = gs_quote!(generator_state(deno_core, scope) => {
-    #temp = match unsafe { #deno_core::_ops::#to_v8_slice::<#deno_core::v8::#array>(&mut #scope, #arg_ident) } {
+    #temp = match unsafe { #deno_core::_ops::#to_v8_slice::<#array>(&mut #scope, #arg_ident) } {
       Ok(#arg_ident) => #arg_ident,
       Err(#err) => {
         #throw_exception
@@ -479,16 +476,16 @@ pub fn from_arg_buffer(
   });
 
   let make_arg = match buffer {
-    Buffer::Slice(RefType::Ref, NumericArg::u8) => {
+    Buffer::Slice(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #temp.as_ref();)
     }
-    Buffer::Slice(RefType::Mut, NumericArg::u8) => {
+    Buffer::Slice(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #temp.as_mut();)
     }
-    Buffer::Vec(NumericArg::u8) => {
+    Buffer::Vec(NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #temp.to_vec();)
     }
-    Buffer::BoxSlice(NumericArg::u8) => {
+    Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #temp.to_boxed_slice();)
     }
     Buffer::Bytes(BufferMode::Copy) => {
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index dfc2458..a1eb42d 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -168,13 +168,13 @@ pub enum Buffer {
   /// Owned, copy. Stored in `bytes::BytesMut`
   BytesMut(BufferMode),
   /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::V8Slice`
-  V8Slice(BufferMode),
+  V8Slice(BufferMode, NumericArg),
   /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::JsBuffer`
   JsBuffer(BufferMode),
 }
 
 impl Buffer {
-  const fn valid_modes(
+  pub const fn valid_modes(
     &self,
     position: Position,
   ) -> &'static [AttributeModifier] {
@@ -194,6 +194,17 @@ impl Buffer {
       },
     }
   }
+
+  pub const fn element(&self) -> NumericArg {
+    match self {
+      Self::Slice(_, arg) => *arg,
+      Self::BoxSlice(arg) => *arg,
+      Self::Bytes(_) | Self::BytesMut(_) | Self::JsBuffer(_) => NumericArg::u8,
+      Self::Ptr(_, arg) => *arg,
+      Self::Vec(arg) => *arg,
+      Self::V8Slice(_, arg) => *arg,
+    }
+  }
 }
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -345,14 +356,18 @@ impl Arg {
         Arg::V8Global(_) => ArgSlowRetval::V8Local,
         Arg::Buffer(
           Buffer::JsBuffer(BufferMode::Default)
-          | Buffer::Vec(NumericArg::u8)
-          | Buffer::BoxSlice(NumericArg::u8)
+          | 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::V8LocalFalliable,
         Arg::OptionBuffer(
           Buffer::JsBuffer(BufferMode::Default)
-          | Buffer::Vec(NumericArg::u8)
-          | Buffer::BoxSlice(NumericArg::u8)
+          | Buffer::Vec(NumericArg::u8 | NumericArg::u32)
+          | Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)
           | Buffer::BytesMut(BufferMode::Default),
         ) => ArgSlowRetval::V8LocalFalliable,
         _ => ArgSlowRetval::None,
@@ -978,12 +993,14 @@ fn parse_numeric_type(tp: &Path) -> Result<NumericArg, ArgError> {
     }
   }
 
-  let res = std::panic::catch_unwind(|| {
+  let Ok(Some(res)) = std::panic::catch_unwind(|| {
     rules!(tp.into_token_stream() => {
-      ( $( std :: ffi :: )? c_void ) => NumericArg::__VOID__,
+      ( $( std :: ffi :: )? c_void ) => Some(NumericArg::__VOID__),
+      ( $_ty:ty ) => None,
     })
-  })
-  .map_err(|_| ArgError::InvalidNumericType(stringify_token(tp)))?;
+  }) else {
+    return Err(ArgError::InvalidNumericType(stringify_token(tp)));
+  };
 
   Ok(res)
 }
@@ -1029,8 +1046,8 @@ fn parse_type_path(
       ( $( std :: boxed ::)? Box < [ $ty:path ] > ) => {
         Ok(CBare(TBuffer(Buffer::BoxSlice(parse_numeric_type(&ty)?))))
       }
-      ( $( serde_v8 :: )? V8Slice ) => {
-        Ok(CBare(TBuffer(Buffer::V8Slice(buffer_mode()?))))
+      ( $( serde_v8 :: )? V8Slice < $ty:path > ) => {
+        Ok(CBare(TBuffer(Buffer::V8Slice(buffer_mode()?, parse_numeric_type(&ty)?))))
       }
       ( $( serde_v8 :: )? JsBuffer ) => {
         Ok(CBare(TBuffer(Buffer::JsBuffer(buffer_mode()?))))
@@ -1508,8 +1525,8 @@ mod tests {
     (State(Ref, Something), OptionState(Ref, Something)) -> Infallible(Void)
   );
   test!(
-    #[buffer] fn op_buffers(#[buffer(copy)] a: Vec<u8>, #[buffer(copy)] b: Box<[u8]>, #[buffer(copy)] c: bytes::Bytes, #[buffer] d: V8Slice, #[buffer] e: JsBuffer, #[buffer(detach)] f: JsBuffer) -> Vec<u8>;
-    (Buffer(Vec(u8)), Buffer(BoxSlice(u8)), Buffer(Bytes(Copy)), Buffer(V8Slice(Default)), Buffer(JsBuffer(Default)), Buffer(JsBuffer(Detach))) -> Infallible(Buffer(Vec(u8)))
+    #[buffer] fn op_buffers(#[buffer(copy)] a: Vec<u8>, #[buffer(copy)] b: Box<[u8]>, #[buffer(copy)] c: bytes::Bytes, #[buffer] d: V8Slice<u8>, #[buffer] e: JsBuffer, #[buffer(detach)] f: JsBuffer) -> Vec<u8>;
+    (Buffer(Vec(u8)), Buffer(BoxSlice(u8)), Buffer(Bytes(Copy)), Buffer(V8Slice(Default, u8)), Buffer(JsBuffer(Default)), Buffer(JsBuffer(Detach))) -> Infallible(Buffer(Vec(u8)))
   );
   test!(
     #[buffer] fn op_return_bytesmut() -> bytes::BytesMut;
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index ecc5c7d..fd66ccb 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -31,6 +31,10 @@
 | 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.                                                                                                                                                   |
diff --git a/ops/op2/valid_retvals.md b/ops/op2/valid_retvals.md
index 0e1adbb..58d7f91 100644
--- a/ops/op2/valid_retvals.md
+++ b/ops/op2/valid_retvals.md
@@ -18,9 +18,13 @@
 | X         | #[string] &str                   |          | String      |                                                                                                                                                                                                  |
 | X         | #[string] Cow<str>               |          | String      |                                                                                                                                                                                                  |
 | X         | #[string(onebyte)] Cow<[u8]>     |          | String      |                                                                                                                                                                                                  |
-| X         | #[buffer] Vec<u8>                |          | ArrayBuffer |                                                                                                                                                                                                  |
-| X         | #[buffer] Box<[u8]>              |          | ArrayBuffer |                                                                                                                                                                                                  |
-| X         | #[buffer] bytes::BytesMut        |          | ArrayBuffer |                                                                                                                                                                                                  |
+| X         | #[buffer] V8Slice<u8>            |          | Uint8Array  |                                                                                                                                                                                                  |
+| X         | #[buffer] Vec<u8>                |          | Uint8Array  |                                                                                                                                                                                                  |
+| X         | #[buffer] Box<[u8]>              |          | Uint8Array  |                                                                                                                                                                                                  |
+| X         | #[buffer] bytes::BytesMut        |          | Uint8Array  |                                                                                                                                                                                                  |
+| X         | #[buffer] V8Slice<u32>           |          | Uint32Array |                                                                                                                                                                                                  |
+|           | #[buffer] Vec<u32>               |          | Uint32Array |                                                                                                                                                                                                  |
+|           | #[buffer] Box<[u32]>             |          | Uint32Array |                                                                                                                                                                                                  |
 | X         | *const std::ffi::c_void          | X        | External    |                                                                                                                                                                                                  |
 | X         | *mut std::ffi::c_void            | X        | External    |                                                                                                                                                                                                  |
 | X         | v8::Local<v8::Value>             |          | any         |                                                                                                                                                                                                  |
diff --git a/serde_v8/lib.rs b/serde_v8/lib.rs
index 2778268..3bce2e5 100644
--- a/serde_v8/lib.rs
+++ b/serde_v8/lib.rs
@@ -24,6 +24,7 @@ pub use magic::detached_buffer::DetachedBuffer;
 pub use magic::string_or_buffer::StringOrBuffer;
 pub use magic::u16string::U16String;
 pub use magic::v8slice::V8Slice;
+pub use magic::v8slice::V8Sliceable;
 pub use magic::ExternalPointer;
 pub use magic::Global;
 pub use magic::Value;
diff --git a/serde_v8/magic/buffer.rs b/serde_v8/magic/buffer.rs
index 76598c6..aadef6d 100644
--- a/serde_v8/magic/buffer.rs
+++ b/serde_v8/magic/buffer.rs
@@ -9,17 +9,17 @@ use super::transl8::ToV8;
 use super::v8slice::V8Slice;
 use crate::magic::transl8::impl_magic;
 
-pub struct JsBuffer(V8Slice);
+pub struct JsBuffer(V8Slice<u8>);
 
 impl_magic!(JsBuffer);
 
 impl JsBuffer {
-  pub fn from_parts(slice: V8Slice) -> Self {
+  pub fn from_parts(slice: V8Slice<u8>) -> Self {
     Self(slice)
   }
 
   /// Returns the buffer to its parts.
-  pub fn into_parts(self) -> V8Slice {
+  pub fn into_parts(self) -> V8Slice<u8> {
     self.0
   }
 }
@@ -76,8 +76,8 @@ impl From<JsBuffer> for bytes::Bytes {
   }
 }
 
-impl From<V8Slice> for JsBuffer {
-  fn from(value: V8Slice) -> Self {
+impl From<V8Slice<u8>> for JsBuffer {
+  fn from(value: V8Slice<u8>) -> Self {
     JsBuffer(value)
   }
 }
diff --git a/serde_v8/magic/detached_buffer.rs b/serde_v8/magic/detached_buffer.rs
index 6f057aa..fed49fe 100644
--- a/serde_v8/magic/detached_buffer.rs
+++ b/serde_v8/magic/detached_buffer.rs
@@ -11,7 +11,7 @@ use super::v8slice::V8Slice;
 use crate::magic::transl8::impl_magic;
 
 // A buffer that detaches when deserialized from JS
-pub struct DetachedBuffer(V8Slice);
+pub struct DetachedBuffer(V8Slice<u8>);
 impl_magic!(DetachedBuffer);
 
 impl AsRef<[u8]> for DetachedBuffer {
@@ -64,6 +64,7 @@ impl FromV8 for DetachedBuffer {
     }
     let store = b.get_backing_store();
     b.detach(None); // Detach
-    Ok(Self(V8Slice { store, range }))
+                    // SAFETY: We got these values from to_ranged_buffer
+    Ok(Self(unsafe { V8Slice::from_parts(store, range) }))
   }
 }
diff --git a/serde_v8/magic/v8slice.rs b/serde_v8/magic/v8slice.rs
index ba2850a..9b417b4 100644
--- a/serde_v8/magic/v8slice.rs
+++ b/serde_v8/magic/v8slice.rs
@@ -1,6 +1,7 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
 
 use std::fmt::Debug;
+use std::marker::PhantomData;
 use std::ops::Deref;
 use std::ops::DerefMut;
 use std::ops::Range;
@@ -9,6 +10,42 @@ use std::rc::Rc;
 use super::rawbytes;
 use super::transl8::FromV8;
 
+/// A type that may be represented as a [`V8Slice`].
+pub trait V8Sliceable: Copy + Clone {
+  /// The concrete V8 data view type.
+  type V8;
+  fn new_buf<'s>(
+    scope: &mut v8::HandleScope<'s>,
+    buf: v8::Local<v8::ArrayBuffer>,
+    byte_offset: usize,
+    length: usize,
+  ) -> Option<v8::Local<'s, Self::V8>>;
+}
+
+impl V8Sliceable for u8 {
+  type V8 = v8::Uint8Array;
+  fn new_buf<'s>(
+    scope: &mut v8::HandleScope<'s>,
+    buf: v8::Local<v8::ArrayBuffer>,
+    byte_offset: usize,
+    length: usize,
+  ) -> Option<v8::Local<'s, Self::V8>> {
+    v8::Uint8Array::new(scope, buf, byte_offset, length)
+  }
+}
+
+impl V8Sliceable for u32 {
+  type V8 = v8::Uint32Array;
+  fn new_buf<'s>(
+    scope: &mut v8::HandleScope<'s>,
+    buf: v8::Local<v8::ArrayBuffer>,
+    byte_offset: usize,
+    length: usize,
+  ) -> Option<v8::Local<'s, Self::V8>> {
+    v8::Uint32Array::new(scope, buf, byte_offset, length)
+  }
+}
+
 /// A V8Slice encapsulates a slice that's been borrowed from a JavaScript
 /// ArrayBuffer object. JavaScript objects can normally be garbage collected,
 /// but the existence of a V8Slice inhibits this until it is dropped. It
@@ -21,25 +58,36 @@ use super::transl8::FromV8;
 /// To actually clone the contents of the buffer do
 /// `let copy = Vec::from(&*zero_copy_buf);`
 #[derive(Clone)]
-pub struct V8Slice {
+pub struct V8Slice<T>
+where
+  T: V8Sliceable,
+{
   pub(crate) store: v8::SharedRef<v8::BackingStore>,
   pub(crate) range: Range<usize>,
+  _phantom: PhantomData<T>,
 }
 
-impl Debug for V8Slice {
+impl<T> Debug for V8Slice<T>
+where
+  T: V8Sliceable,
+{
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     f.write_fmt(format_args!(
-      "V8Slice({:?} of {} bytes)",
+      "V8Slice({:?} of {} {})",
       self.range,
-      self.store.len()
+      self.store.len(),
+      std::any::type_name::<T>()
     ))
   }
 }
 
 // SAFETY: unsafe trait must have unsafe implementation
-unsafe impl Send for V8Slice {}
+unsafe impl<T> Send for V8Slice<T> where T: V8Sliceable {}
 
-impl V8Slice {
+impl<T> V8Slice<T>
+where
+  T: V8Sliceable,
+{
   /// Create one of these for testing. We create and forget an isolate here. If we decide to perform more v8-requiring tests,
   /// this code will probably need to be hoisted to another location.
   #[cfg(test)]
@@ -73,15 +121,20 @@ impl V8Slice {
     store: v8::SharedRef<v8::BackingStore>,
     range: Range<usize>,
   ) -> Self {
-    Self { store, range }
+    Self {
+      store,
+      range: range.start / std::mem::size_of::<T>()
+        ..range.end / std::mem::size_of::<T>(),
+      _phantom: PhantomData,
+    }
   }
 
-  fn as_slice(&self) -> &[u8] {
+  fn as_slice(&self) -> &[T] {
     let store = &self.store;
     let Some(ptr) = store.data() else {
       return &[];
     };
-    let ptr = ptr.cast::<u8>().as_ptr();
+    let ptr = ptr.cast::<T>().as_ptr();
     // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
     // it points to a fixed continuous slice of bytes on the heap.
     // We assume it's initialized and thus safe to read (though may not contain
@@ -95,12 +148,12 @@ impl V8Slice {
     }
   }
 
-  fn as_slice_mut(&mut self) -> &mut [u8] {
+  fn as_slice_mut(&mut self) -> &mut [T] {
     let store = &self.store;
     let Some(ptr) = store.data() else {
       return &mut [];
     };
-    let ptr = ptr.cast::<u8>().as_ptr();
+    let ptr = ptr.cast::<T>().as_ptr();
     // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
     // it points to a fixed continuous slice of bytes on the heap.
     // We assume it's initialized and thus safe to read (though may not contain
@@ -122,19 +175,38 @@ impl V8Slice {
     self.range.is_empty()
   }
 
-  /// Create a [`Vec<u8>`] copy of this slice data.
-  pub fn to_vec(&self) -> Vec<u8> {
+  /// Create a [`Vec<T>`] copy of this slice data.
+  pub fn to_vec(&self) -> Vec<T> {
     self.as_slice().to_vec()
   }
 
-  /// Create a [`Box<[u8]>`] copy of this slice data.
-  pub fn to_boxed_slice(&self) -> Box<[u8]> {
+  /// Create a [`Box<[T]>`] copy of this slice data.
+  pub fn to_boxed_slice(&self) -> Box<[T]> {
     self.to_vec().into_boxed_slice()
   }
 
+  /// Takes this slice and converts it into a strongly-typed v8 array.
+  pub fn into_v8_local<'a>(
+    self,
+    scope: &mut v8::HandleScope<'a>,
+  ) -> Option<v8::Local<'a, T::V8>> {
+    let (store, range) = self.into_parts();
+    let buffer = v8::ArrayBuffer::with_backing_store(scope, &store);
+    T::new_buf(
+      scope,
+      buffer,
+      range.start,
+      range.len() / std::mem::size_of::<T>(),
+    )
+  }
+
   /// Returns the slice to the parts it came from.
   pub fn into_parts(self) -> (v8::SharedRef<v8::BackingStore>, Range<usize>) {
-    (self.store, self.range)
+    (
+      self.store,
+      self.range.start * std::mem::size_of::<T>()
+        ..self.range.end * std::mem::size_of::<T>(),
+    )
   }
 
   /// Splits the buffer into two at the given index.
@@ -197,7 +269,10 @@ pub(crate) fn to_ranged_buffer<'s>(
   Ok((b, 0..b.byte_length()))
 }
 
-impl FromV8 for V8Slice {
+impl<T> FromV8 for V8Slice<T>
+where
+  T: V8Sliceable,
+{
   fn from_v8(
     scope: &mut v8::HandleScope,
     value: v8::Local<v8::Value>,
@@ -210,7 +285,8 @@ impl FromV8 for V8Slice {
         } else if store.is_shared() {
           Err(crate::Error::ExpectedBuffer(value.type_repr()))
         } else {
-          Ok(V8Slice { store, range })
+          // SAFETY: we got these parts from to_ranged_buffer
+          Ok(unsafe { V8Slice::from_parts(store, range) })
         }
       }
       Err(_) => Err(crate::Error::ExpectedBuffer(value.type_repr())),
@@ -218,46 +294,60 @@ impl FromV8 for V8Slice {
   }
 }
 
-impl Deref for V8Slice {
-  type Target = [u8];
-  fn deref(&self) -> &[u8] {
+impl<T> Deref for V8Slice<T>
+where
+  T: V8Sliceable,
+{
+  type Target = [T];
+  fn deref(&self) -> &[T] {
     self.as_slice()
   }
 }
 
-impl DerefMut for V8Slice {
-  fn deref_mut(&mut self) -> &mut [u8] {
+impl<T> DerefMut for V8Slice<T>
+where
+  T: V8Sliceable,
+{
+  fn deref_mut(&mut self) -> &mut [T] {
     self.as_slice_mut()
   }
 }
 
-impl AsRef<[u8]> for V8Slice {
-  fn as_ref(&self) -> &[u8] {
+impl<T> AsRef<[T]> for V8Slice<T>
+where
+  T: V8Sliceable,
+{
+  fn as_ref(&self) -> &[T] {
     self.as_slice()
   }
 }
 
-impl AsMut<[u8]> for V8Slice {
-  fn as_mut(&mut self) -> &mut [u8] {
+impl<T> AsMut<[T]> for V8Slice<T>
+where
+  T: V8Sliceable,
+{
+  fn as_mut(&mut self) -> &mut [T] {
     self.as_slice_mut()
   }
 }
 
 // Implement V8Slice -> bytes::Bytes
-impl V8Slice {
-  fn rc_into_byte_parts(self: Rc<Self>) -> (*const u8, usize, *mut V8Slice) {
+impl V8Slice<u8> {
+  fn rc_into_byte_parts(
+    self: Rc<Self>,
+  ) -> (*const u8, usize, *mut V8Slice<u8>) {
     let (ptr, len) = {
       let slice = self.as_ref();
       (slice.as_ptr(), slice.len())
     };
     let rc_raw = Rc::into_raw(self);
-    let data = rc_raw as *mut V8Slice;
+    let data = rc_raw as *mut V8Slice<u8>;
     (ptr, len, data)
   }
 }
 
-impl From<V8Slice> for bytes::Bytes {
-  fn from(v8slice: V8Slice) -> Self {
+impl From<V8Slice<u8>> for bytes::Bytes {
+  fn from(v8slice: V8Slice<u8>) -> Self {
     let (ptr, len, data) = Rc::new(v8slice).rc_into_byte_parts();
     rawbytes::RawBytes::new_raw(ptr, len, data.cast(), &V8SLICE_VTABLE)
   }
@@ -277,7 +367,7 @@ unsafe fn v8slice_clone(
   ptr: *const u8,
   len: usize,
 ) -> bytes::Bytes {
-  let rc = Rc::from_raw(*data as *const V8Slice);
+  let rc = Rc::from_raw(*data as *const V8Slice<u8>);
   let (_, _, data) = rc.clone().rc_into_byte_parts();
   std::mem::forget(rc);
   // NOTE: `bytes::Bytes` does bounds checking so we trust its ptr, len inputs
@@ -302,16 +392,29 @@ unsafe fn v8slice_drop(
   _: *const u8,
   _: usize,
 ) {
-  drop(Rc::from_raw(*data as *const V8Slice))
+  drop(Rc::from_raw(*data as *const V8Slice<u8>))
 }
 
 #[cfg(test)]
 mod tests {
-  use super::V8Slice;
+  use super::*;
+
+  fn make_slice<T: V8Sliceable>(len: usize) -> V8Slice<T> {
+    let slice = V8Slice::<T>::very_unsafe_new_only_for_test(
+      len * std::mem::size_of::<T>(),
+    );
+    assert_eq!(slice.len(), len);
+    slice
+  }
 
   #[test]
   pub fn test_split_off() {
-    let mut slice = V8Slice::very_unsafe_new_only_for_test(1024);
+    test_split_off_generic::<u8>();
+    test_split_off_generic::<u32>();
+  }
+
+  pub fn test_split_off_generic<T: V8Sliceable>() {
+    let mut slice = make_slice::<T>(1024);
     let mut other = slice.split_off(16);
     assert_eq!(0..16, slice.range);
     assert_eq!(16..1024, other.range);
@@ -322,7 +425,12 @@ mod tests {
 
   #[test]
   pub fn test_split_to() {
-    let mut slice = V8Slice::very_unsafe_new_only_for_test(1024);
+    test_split_to_generic::<u8>();
+    test_split_to_generic::<u32>();
+  }
+
+  pub fn test_split_to_generic<T: V8Sliceable>() {
+    let mut slice = make_slice::<T>(1024);
     let other = slice.split_to(16);
     assert_eq!(16..1024, slice.range);
     assert_eq!(0..16, other.range);
@@ -333,14 +441,24 @@ mod tests {
 
   #[test]
   pub fn test_truncate() {
-    let mut slice = V8Slice::very_unsafe_new_only_for_test(1024);
+    test_truncate_generic::<u8>();
+    test_truncate_generic::<u32>();
+  }
+
+  pub fn test_truncate_generic<T: V8Sliceable>() {
+    let mut slice = make_slice::<T>(1024);
     slice.truncate(16);
     assert_eq!(0..16, slice.range);
   }
 
   #[test]
-  pub fn test_truncate_after_split() {
-    let mut slice = V8Slice::very_unsafe_new_only_for_test(1024);
+  fn test_truncate_after_split() {
+    test_truncate_after_split_generic::<u8>();
+    test_truncate_after_split_generic::<u32>();
+  }
+
+  pub fn test_truncate_after_split_generic<T: V8Sliceable>() {
+    let mut slice = make_slice::<T>(1024);
     _ = slice.split_to(16);
     assert_eq!(16..1024, slice.range);
     slice.truncate(16);


Test patch:
diff --git a/ops/op2/test_cases/async/async_jsbuffer.out b/ops/op2/test_cases/async/async_jsbuffer.out
index 85b5473..b71dffd 100644
--- a/ops/op2/test_cases/async/async_jsbuffer.out
+++ b/ops/op2/test_cases/async/async_jsbuffer.out
@@ -38,9 +38,7 @@ impl op_async_v8_buffer {
             let arg0 = args.get(1usize as i32);
             let mut arg0_temp;
             arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<
-                    deno_core::v8::Uint8Array,
-                >(&mut scope, arg0)
+                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
             } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
diff --git a/ops/op2/test_cases/sync/buffers.out b/ops/op2/test_cases/sync/buffers.out
index 0e5d053..4bd833f 100644
--- a/ops/op2/test_cases/sync/buffers.out
+++ b/ops/op2/test_cases/sync/buffers.out
@@ -62,9 +62,7 @@ impl op_buffers {
             let arg0 = args.get(0usize as i32);
             let mut arg0_temp;
             arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<
-                    deno_core::v8::Uint8Array,
-                >(&mut scope, arg0)
+                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
             } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
@@ -84,9 +82,7 @@ impl op_buffers {
             let arg1 = args.get(1usize as i32);
             let mut arg1_temp;
             arg1_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<
-                    deno_core::v8::Uint8Array,
-                >(&mut scope, arg1)
+                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg1)
             } {
                 Ok(arg1) => arg1,
                 Err(arg1_err) => {
@@ -110,6 +106,114 @@ impl op_buffers {
     fn call(_a: &[u8], _b: &mut [u8]) {}
 }
 
+#[allow(non_camel_case_types)]
+struct op_buffers_32 {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_buffers_32 {
+    const NAME: &'static str = stringify!(op_buffers_32);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_buffers_32),
+        false,
+        false,
+        false,
+        2usize as u8,
+        Self::v8_fn_ptr as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[
+                    Type::V8Value,
+                    Type::TypedArray(CType::Uint32),
+                    Type::TypedArray(CType::Uint32),
+                ],
+                CType::Void,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl op_buffers_32 {
+    pub const fn name() -> &'static str {
+        stringify!(op_buffers_32)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
+        arg1: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
+    ) -> () {
+        let result = {
+            let arg0 = unsafe { arg0.as_mut().unwrap() }
+                .get_storage_if_aligned()
+                .unwrap();
+            let arg1 = unsafe { arg1.as_mut().unwrap() }
+                .get_storage_if_aligned()
+                .unwrap();
+            Self::call(arg0, arg1)
+        };
+        result as _
+    }
+    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::<u32>(&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 = arg0_temp.as_ref();
+            let arg1 = args.get(1usize as i32);
+            let mut arg1_temp;
+            arg1_temp = match unsafe {
+                deno_core::_ops::to_v8_slice::<u32>(&mut scope, arg1)
+            } {
+                Ok(arg1) => arg1,
+                Err(arg1_err) => {
+                    let msg = deno_core::v8::String::new_from_one_byte(
+                            &mut scope,
+                            arg1_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 arg1 = arg1_temp.as_mut();
+            Self::call(arg0, arg1)
+        };
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
+    }
+    #[inline(always)]
+    fn call(_a: &[u32], _b: &mut [u32]) {}
+}
+
 #[allow(non_camel_case_types)]
 struct op_buffers_option {
     _unconstructable: ::std::marker::PhantomData<()>,
@@ -149,9 +253,7 @@ impl op_buffers_option {
                 None
             } else {
                 arg0_temp = match unsafe {
-                    deno_core::_ops::to_v8_slice::<
-                        deno_core::v8::Uint8Array,
-                    >(&mut scope, arg0)
+                    deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
                 } {
                     Ok(arg0) => arg0,
                     Err(arg0_err) => {
@@ -178,9 +280,7 @@ impl op_buffers_option {
                 None
             } else {
                 arg1_temp = match unsafe {
-                    deno_core::_ops::to_v8_slice::<
-                        deno_core::v8::Uint8Array,
-                    >(&mut scope, arg1)
+                    deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg1)
                 } {
                     Ok(arg1) => arg1,
                     Err(arg1_err) => {
diff --git a/ops/op2/test_cases/sync/buffers.rs b/ops/op2/test_cases/sync/buffers.rs
index b46cbbc..a062758 100644
--- a/ops/op2/test_cases/sync/buffers.rs
+++ b/ops/op2/test_cases/sync/buffers.rs
@@ -8,6 +8,10 @@ use deno_core::JsBuffer;
 fn op_buffers(#[buffer] _a: &[u8], #[buffer] _b: &mut [u8]) {
 }
 
+#[op2(fast)]
+fn op_buffers_32(#[buffer] _a: &[u32], #[buffer] _b: &mut [u32]) {
+}
+
 #[op2]
 fn op_buffers_option(#[buffer] _a: Option<&[u8]>, #[buffer] _b: Option<JsBuffer>) {
 }
diff --git a/ops/op2/test_cases/sync/buffers_copy.out b/ops/op2/test_cases/sync/buffers_copy.out
index e1a27fa..fabb766 100644
--- a/ops/op2/test_cases/sync/buffers_copy.out
+++ b/ops/op2/test_cases/sync/buffers_copy.out
@@ -72,9 +72,7 @@ impl op_buffers {
             let arg0 = args.get(0usize as i32);
             let mut arg0_temp;
             arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<
-                    deno_core::v8::Uint8Array,
-                >(&mut scope, arg0)
+                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
             } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
@@ -94,9 +92,7 @@ impl op_buffers {
             let arg1 = args.get(1usize as i32);
             let mut arg1_temp;
             arg1_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<
-                    deno_core::v8::Uint8Array,
-                >(&mut scope, arg1)
+                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg1)
             } {
                 Ok(arg1) => arg1,
                 Err(arg1_err) => {
@@ -115,9 +111,7 @@ impl op_buffers {
             let arg2 = args.get(2usize as i32);
             let mut arg2_temp;
             arg2_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<
-                    deno_core::v8::Uint8Array,
-                >(&mut scope, arg2)
+                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg2)
             } {
                 Ok(arg2) => arg2,
                 Err(arg2_err) => {
@@ -140,3 +134,114 @@ impl op_buffers {
     #[inline(always)]
     fn call(_a: Vec<u8>, _b: Box<[u8]>, _c: bytes::Bytes) {}
 }
+
+#[allow(non_camel_case_types)]
+struct op_buffers_32 {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_buffers_32 {
+    const NAME: &'static str = stringify!(op_buffers_32);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_buffers_32),
+        false,
+        false,
+        false,
+        2usize as u8,
+        Self::v8_fn_ptr as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[
+                    Type::V8Value,
+                    Type::TypedArray(CType::Uint32),
+                    Type::TypedArray(CType::Uint32),
+                ],
+                CType::Void,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl op_buffers_32 {
+    pub const fn name() -> &'static str {
+        stringify!(op_buffers_32)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
+        arg1: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
+    ) -> () {
+        let result = {
+            let arg0 = unsafe { arg0.as_mut().unwrap() }
+                .get_storage_if_aligned()
+                .unwrap()
+                .to_vec();
+            let arg1 = unsafe { arg1.as_mut().unwrap() }
+                .get_storage_if_aligned()
+                .unwrap()
+                .to_vec()
+                .into_boxed_slice();
+            Self::call(arg0, arg1)
+        };
+        result as _
+    }
+    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::<u32>(&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 = arg0_temp.to_vec();
+            let arg1 = args.get(1usize as i32);
+            let mut arg1_temp;
+            arg1_temp = match unsafe {
+                deno_core::_ops::to_v8_slice::<u32>(&mut scope, arg1)
+            } {
+                Ok(arg1) => arg1,
+                Err(arg1_err) => {
+                    let msg = deno_core::v8::String::new_from_one_byte(
+                            &mut scope,
+                            arg1_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 arg1 = arg1_temp.to_boxed_slice();
+            Self::call(arg0, arg1)
+        };
+        deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv)
+    }
+    #[inline(always)]
+    fn call(_a: Vec<u32>, _b: Box<[u32]>) {}
+}
diff --git a/ops/op2/test_cases/sync/buffers_copy.rs b/ops/op2/test_cases/sync/buffers_copy.rs
index 4a4cd63..b0bf7fe 100644
--- a/ops/op2/test_cases/sync/buffers_copy.rs
+++ b/ops/op2/test_cases/sync/buffers_copy.rs
@@ -5,3 +5,7 @@ deno_ops_compile_test_runner::prelude!();
 #[op2(fast)]
 fn op_buffers(#[buffer(copy)] _a: Vec<u8>, #[buffer(copy)] _b: Box<[u8]>, #[buffer(copy)] _c: bytes::Bytes) {
 }
+
+#[op2(fast)]
+fn op_buffers_32(#[buffer(copy)] _a: Vec<u32>, #[buffer(copy)] _b: Box<[u32]>) {
+}