Repository: denoland/deno_core
Pull request #245 (2023-09-29 01:36:34)

Adds `clear_modules` to JsRealm and JsRuntime to allow the unloading of modules.
Added `clear_modules` to JsRealm and JsRuntime to allow the unloading of modules.

This change does not affect loaded extensions.

The use case is a very quick reset for an active runtime that does not require reconstructing the runtime, or reloading extensions/ops

The code below was used to check for memory not being cleared from v8 resources:
- Performs 1000 iterations of
    - Load 1000 modules, each storing 1024bytes of data.
    - Clear the modules
```rust
#[cfg(test)]
mod test_module_unload {
  fn get_numbered_module(
    i: usize,
  ) -> (crate::url::Url, Option<crate::FastString>) {
    let name = crate::resolve_path(
      &format!("module{i}.js"),
      &std::env::current_dir()
        .expect("Unable to get current working directory"),
    )
    .expect("Unable to get module url");

    let code = Some(crate::FastString::from(
      "
      let value = `
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla mollis facilisis ex a pretium. Pellentesque pharetra mollis ipsum, laoreet dapibus turpis. Ut volutpat hendrerit justo ac hendrerit. Vivamus id ligula nisl. Proin pretium tincidunt hendrerit. Vivamus ultrices magna neque, id elementum nisl luctus id. Sed finibus, arcu vitae porta ornare, felis nibh convallis neque, eu auctor risus diam ut neque. Sed euismod nisl ut nibh tincidunt interdum. Mauris aliquet mauris odio, vitae eleifend ante imperdiet at. Phasellus dictum sodales mi sit amet fringilla. Aliquam erat volutpat. Ut eget neque eu lorem aliquet molestie. In a purus sit amet velit dapibus semper a ac quam. Ut hendrerit aliquam tempus. Maecenas justo ipsum, venenatis id ipsum nec, euismod rutrum ante. Sed et urna porta dolor dictum pulvinar ut eu enim.
      Vestibulum justo nisl, lacinia consequat quam ut, tristique dapibus arcu. Mauris rhoncus, lectus nec mattis pharetra, lectus neque placerat dolor, vel commodo nunc ligula sit amet justo. Nunc mi.
      `;
      ".to_string(),
    ));

    (name, code)
  }

  #[test]
  fn test_unload() {
    let mut js_runtime = crate::JsRuntime::new(Default::default());

    for iteration in 0..1000 {
      let runtime = &mut js_runtime;
      let future = async move {
        for i in 0..1000 {
          let (name, code) = get_numbered_module(i);
          let mod_id = runtime.load_side_module(&name, code).await?;
          let result = runtime.mod_evaluate(mod_id);
          runtime.run_event_loop(false).await?;
          result.await??;
        }

        Ok::<(), anyhow::Error>(())
      };

      tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(future)
        .expect("Could not run tokio");

      js_runtime.clear_modules();

      if iteration % 100 == 0 {
        println!("Finished {} / 1000 iterations...", iteration);
      }
    }
  }
}
```


bartlomieju: Thanks for the PR, could you give some context what is the use case?
rscarson: > Thanks for the PR, could you give some context what is the use case?

A runtime instance that will need to run a series of unrelated modules over and over, with very tight performance constraints.

Creating a new runtime takes ~20ms, snapshots take about 5ms (and have several constraints that make implementation very difficult in my case).

Unloading the modules (and restoring the state of globalThis from a backup on the JS side) takes only ~1ms and allows me to re-use the runtime and it's extensions
bartlomieju: > > Thanks for the PR, could you give some context what is the use case?
> 
> A runtime instance that will need to run a series of unrelated modules over and over, with very tight performance constraints, and without side-effects between runs.
> 
> Creating a new runtime takes ~20ms, snapshots take about 5ms (and have several constraints that make implementation very difficult in my case).
> 
> Unloading the modules (and restoring the state of globalThis from a backup on the JS side) takes only ~1ms and allows me to re-use the runtime and it's extensions

Thanks for explanation; I don't have objections to landing the PR, just keep in mind that clearing the loaded modules will likely not clear the whole environment completely - while it might be fine for testing I wouldn't recommended running it in production.
rscarson: > > > Thanks for the PR, could you give some context what is the use case?
> > 
> > 
> > A runtime instance that will need to run a series of unrelated modules over and over, with very tight performance constraints, and without side-effects between runs.
> > Creating a new runtime takes ~20ms, snapshots take about 5ms (and have several constraints that make implementation very difficult in my case).
> > Unloading the modules (and restoring the state of globalThis from a backup on the JS side) takes only ~1ms and allows me to re-use the runtime and it's extensions
> 
> Thanks for explanation; I don't have objections to landing the PR, just keep in mind that clearing the loaded modules will likely not clear the whole environment completely - while it might be fine for testing I wouldn't recommended running it in production.

I'll update the function docs accordingly. Would you guys be opposed to another PR in the future that clears the memory a bit better?
bartlomieju: > I'll update the function docs accordingly. Would you guys be open to another PR in the future that clears the memory a bit better?

Sure, do you have something specific in mind?

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 8a13736..8e44d71 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -281,7 +281,7 @@ dependencies = [
 
 [[package]]
 name = "deno_core"
-version = "0.218.0"
+version = "0.219.0"
 dependencies = [
  "anyhow",
  "bencher",
@@ -321,7 +321,7 @@ dependencies = [
 
 [[package]]
 name = "deno_ops"
-version = "0.94.0"
+version = "0.95.0"
 dependencies = [
  "deno-proc-macro-rules",
  "lazy-regex",
@@ -1232,7 +1232,7 @@ dependencies = [
 
 [[package]]
 name = "serde_v8"
-version = "0.127.0"
+version = "0.128.0"
 dependencies = [
  "bencher",
  "bytes",
@@ -2025,9 +2025,9 @@ checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
 
 [[package]]
 name = "v8"
-version = "0.78.0"
+version = "0.79.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6c96f70e8fc6c009af99d4e8ac4f5e84655a0fc3ec6e58147933b9c99f8b43c"
+checksum = "b87d5248d1a7e321a264d21dc7839675fc0bb456e489102272a55b44047869f0"
 dependencies = [
  "bitflags 1.3.2",
  "fslock",
diff --git a/Cargo.toml b/Cargo.toml
index 65722bb..8f415f4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,11 +17,11 @@ repository = "https://github.com/denoland/deno_core"
 
 [workspace.dependencies]
 # Local dependencies
-deno_core = { version = "0.218.0", path = "./core" }
-deno_ops = { version = "0.94.0", path = "./ops" }
-serde_v8 = { version = "0.127.0", path = "./serde_v8" }
+deno_core = { version = "0.219.0", path = "./core" }
+deno_ops = { version = "0.95.0", path = "./ops" }
+serde_v8 = { version = "0.128.0", path = "./serde_v8" }
 
-v8 = { version = "0.78.0", default-features = false }
+v8 = { version = "0.79.1", default-features = false }
 deno_ast = { version = "0.29.1", features = ["transpiling"] }
 deno_unsync = "0.3.0"
 
diff --git a/core/Cargo.toml b/core/Cargo.toml
index da6d7ba..0a48f2e 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -2,7 +2,7 @@
 
 [package]
 name = "deno_core"
-version = "0.218.0"
+version = "0.219.0"
 authors.workspace = true
 edition.workspace = true
 license.workspace = true
diff --git a/core/feature_checker.rs b/core/feature_checker.rs
new file mode 100644
index 0000000..acaa3f6
--- /dev/null
+++ b/core/feature_checker.rs
@@ -0,0 +1,41 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::collections::BTreeSet;
+
+#[derive(Default, Debug)]
+pub struct FeatureChecker {
+  features: BTreeSet<&'static str>,
+  // TODO(bartlomieju): remove once we migrate away from `--unstable` flag
+  // in the CLI.
+  legacy_unstable: bool,
+}
+
+impl FeatureChecker {
+  pub fn enable_feature(&mut self, feature: &'static str) {
+    self.features.insert(feature);
+  }
+
+  /// Check if a feature is enabled.
+  ///
+  /// If a feature in not present in the checker, return false.
+  pub fn check(&self, feature: &str) -> bool {
+    self.features.contains(feature)
+  }
+
+  // TODO(bartlomieju): remove this.
+  pub fn enable_legacy_unstable(&mut self) {
+    self.legacy_unstable = true;
+  }
+
+  // TODO(bartlomieju): remove this.
+  /// Check if `--unstable` flag has been passed. If not then exit the process
+  /// with exit code 70.
+  pub fn check_legacy_unstable_or_exit(&self, api_name: &str) {
+    if !self.legacy_unstable {
+      eprintln!(
+        "Unstable API '{api_name}'. The --unstable flag must be provided."
+      );
+      std::process::exit(70);
+    }
+  }
+}
diff --git a/core/lib.rs b/core/lib.rs
index 8668f0d..4367f92 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -6,6 +6,7 @@ pub mod error;
 mod error_codes;
 mod extensions;
 mod fast_string;
+mod feature_checker;
 mod flags;
 mod gotham_state;
 mod inspector;
@@ -66,6 +67,7 @@ pub use crate::extensions::Op;
 pub use crate::extensions::OpDecl;
 pub use crate::extensions::OpMiddlewareFn;
 pub use crate::fast_string::FastString;
+pub use crate::feature_checker::FeatureChecker;
 pub use crate::flags::v8_set_flags;
 pub use crate::inspector::InspectorMsg;
 pub use crate::inspector::InspectorMsgKind;
diff --git a/core/ops.rs b/core/ops.rs
index 3aa0750..df30507 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -6,6 +6,7 @@ use crate::gotham_state::GothamState;
 use crate::resources::ResourceTable;
 use crate::runtime::ContextState;
 use crate::runtime::JsRuntimeState;
+use crate::FeatureChecker;
 use crate::OpDecl;
 use crate::OpsTracker;
 use anyhow::Error;
@@ -125,7 +126,7 @@ pub fn to_op_result<R: Serialize + 'static>(
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum OpMetricsEvent {
-  /// Entered an op.
+  /// Entered an op dispatch.
   Dispatched,
   /// Left an op synchronously.
   Completed,
@@ -253,6 +254,7 @@ pub struct OpState {
   pub last_fast_op_error: Option<AnyError>,
   pub(crate) gotham_state: GothamState,
   pub waker: Arc<AtomicWaker>,
+  pub feature_checker: FeatureChecker,
 }
 
 impl OpState {
@@ -263,6 +265,7 @@ impl OpState {
       last_fast_op_error: None,
       tracker: OpsTracker::new(ops_count),
       waker: Arc::new(AtomicWaker::new()),
+      feature_checker: FeatureChecker::default(),
     }
   }
 
diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs
index 68b7e6f..d405908 100644
--- a/core/ops_builtin_v8.rs
+++ b/core/ops_builtin_v8.rs
@@ -493,7 +493,7 @@ pub fn op_deserialize<'a>(
           let array_buffer =
             v8::ArrayBuffer::with_backing_store(scope, &backing_store);
           value_deserializer.transfer_array_buffer(id, array_buffer);
-          transferred_array_buffers.set(scope, id_val, array_buffer.into());
+          transferred_array_buffers.set(scope, i, array_buffer.into());
         } else {
           return Err(type_error(
             "transferred array buffer not present in shared_array_buffer_store",
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
index 3f2463c..d501873 100644
--- a/core/runtime/jsrealm.rs
+++ b/core/runtime/jsrealm.rs
@@ -402,6 +402,13 @@ impl JsRealm {
     }
   }
 
+  /// Clears all loaded modules
+  /// May not clear all associated memory and should not
+  /// be used in production
+  pub fn clear_modules(&mut self) {
+    self.0.module_map().borrow_mut().clear();
+  }
+
   /// Returns the namespace object of a module.
   ///
   /// This is only available after module evaluation has completed.
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
index e89c97c..c3ccaf4 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -1898,6 +1898,13 @@ impl JsRuntime {
     self.main_realm().mod_evaluate(self.v8_isolate(), id)
   }
 
+  /// Clears all loaded modules
+  /// May not free all associated memory, and should not be used
+  /// in production environments
+  pub fn clear_modules(&mut self) {
+    self.main_realm().clear_modules()
+  }
+
   /// Asynchronously load specified module and all of its dependencies.
   ///
   /// The module will be marked as "main", and because of that
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index cafa859..70d5736 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -16,6 +16,7 @@ use std::ffi::c_void;
 use std::future::ready;
 use std::mem::MaybeUninit;
 use std::option::Option;
+use std::ptr::NonNull;
 use std::task::Context;
 use std::task::Poll;
 use v8::WriteOptions;
@@ -654,6 +655,36 @@ pub unsafe fn to_slice_buffer(
   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_any(
+  input: v8::Local<v8::Value>,
+) -> Result<&mut [u8], &'static str> {
+  let (data, len) = {
+    if let Ok(buf) = v8::Local::<v8::ArrayBufferView>::try_from(input) {
+      (NonNull::new(buf.data()), buf.byte_length())
+    } else if let Ok(buf) = v8::Local::<v8::ArrayBuffer>::try_from(input) {
+      (buf.data(), buf.byte_length())
+    } else {
+      return Err("expected ArrayBuffer or ArrayBufferView");
+    }
+  };
+  let slice = if len > 0 {
+    if let Some(ptr) = 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>,
@@ -686,6 +717,28 @@ pub fn to_v8_slice_buffer_detachable(
   Ok(slice)
 }
 
+/// Retrieve a [`serde_v8::V8Slice`] from a [`v8::ArrayBuffer`].
+pub fn to_v8_slice_any(
+  scope: &mut v8::HandleScope,
+  input: v8::Local<v8::Value>,
+) -> Result<serde_v8::V8Slice<u8>, &'static str> {
+  if let Ok(buf) = v8::Local::<v8::ArrayBufferView>::try_from(input) {
+    let Some(buf) = buf.buffer(scope) else {
+      return Err("buffer missing");
+    };
+    return Ok(unsafe {
+      serde_v8::V8Slice::<u8>::from_parts(
+        buf.get_backing_store(),
+        0..buf.byte_length(),
+      )
+    });
+  }
+  if let Ok(buf) = to_v8_slice_buffer(input) {
+    return Ok(buf);
+  }
+  Err("expected ArrayBuffer or ArrayBufferView")
+}
+
 #[cfg(test)]
 mod tests {
   use crate::error::generic_error;
@@ -758,12 +811,15 @@ mod tests {
       op_state_mut_attr,
       op_state_multi_attr,
       op_buffer_slice,
+      op_buffer_jsbuffer,
       op_buffer_ptr,
       op_buffer_slice_32,
       op_buffer_ptr_32,
       op_buffer_slice_unsafe_callback,
       op_buffer_copy,
       op_buffer_bytesmut,
+      op_buffer_any,
+      op_buffer_any_length,
       op_arraybuffer_slice,
       op_external_make,
       op_external_process,
@@ -1737,6 +1793,102 @@ mod tests {
     Ok(())
   }
 
+  #[op2(core)]
+  pub fn op_buffer_jsbuffer(
+    #[buffer] input: JsBuffer,
+    #[number] inlen: usize,
+    #[buffer] mut output: JsBuffer,
+    #[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_buffer_jsbuffer(
+  ) -> Result<(), Box<dyn std::error::Error>> {
+    run_test2(
+      10000,
+      "op_buffer_jsbuffer",
+      r"
+        let inbuf = new ArrayBuffer(10);
+        let in_u8 = new Uint8Array(inbuf);
+        in_u8[5] = 1;
+        let out = new ArrayBuffer(10);
+        op_buffer_jsbuffer(new Uint8Array(inbuf, 5, 5), 5, new Uint8Array(out), 10);
+        assert(new Uint8Array(out)[0] == 1);",
+    )?;
+    Ok(())
+  }
+
+  #[op2(core, fast)]
+  pub fn op_buffer_any(#[anybuffer] buffer: &[u8]) -> u32 {
+    let mut sum: u32 = 0;
+    for i in buffer {
+      sum += *i as u32;
+    }
+    sum
+  }
+
+  #[tokio::test]
+  pub async fn test_op_buffer_any() -> Result<(), Box<dyn std::error::Error>> {
+    run_test2(
+      10000,
+      "op_buffer_any",
+      "assert(op_buffer_any(new Uint8Array([1,2,3,4])) == 10);",
+    )?;
+    run_test2(
+      10000,
+      "op_buffer_any",
+      "assert(op_buffer_any(new Uint8Array([1,2,3,4]).buffer) == 10);",
+    )?;
+    run_test2(
+      10000,
+      "op_buffer_any",
+      "assert(op_buffer_any(new Uint32Array([1,2,3,4,0x01010101])) == 14);",
+    )?;
+    run_test2(
+      10000,
+      "op_buffer_any",
+      "assert(op_buffer_any(new DataView(new Uint8Array([1,2,3,4]).buffer)) == 10);",
+    )?;
+    Ok(())
+  }
+
+  #[op2(core, fast)]
+  pub fn op_buffer_any_length(#[anybuffer] buffer: &[u8]) -> u32 {
+    buffer.len() as _
+  }
+
+  #[tokio::test]
+  pub async fn test_op_buffer_any_length(
+  ) -> Result<(), Box<dyn std::error::Error>> {
+    run_test2(
+      10000,
+      "op_buffer_any_length",
+      "assert(op_buffer_any_length(new Uint8Array(10)) == 10);",
+    )?;
+    run_test2(
+      10000,
+      "op_buffer_any_length",
+      "assert(op_buffer_any_length(new ArrayBuffer(10)) == 10);",
+    )?;
+    run_test2(
+      10000,
+      "op_buffer_any_length",
+      "assert(op_buffer_any_length(new Uint32Array(10)) == 40);",
+    )?;
+    run_test2(
+      10000,
+      "op_buffer_any_length",
+      "assert(op_buffer_any_length(new DataView(new ArrayBuffer(10))) == 10);",
+    )?;
+    Ok(())
+  }
+
   #[op2(core, fast)]
   pub fn op_arraybuffer_slice(
     #[arraybuffer] input: &[u8],
diff --git a/ops/Cargo.toml b/ops/Cargo.toml
index 18772f0..c60d2fd 100644
--- a/ops/Cargo.toml
+++ b/ops/Cargo.toml
@@ -2,7 +2,7 @@
 
 [package]
 name = "deno_ops"
-version = "0.94.0"
+version = "0.95.0"
 authors.workspace = true
 edition.workspace = true
 license.workspace = true
diff --git a/ops/op2/README.md b/ops/op2/README.md
index 84ef9e6..f0fb6fd 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -536,6 +536,62 @@ any
 <tr>
 <td>
 
+```text
+#[anybuffer] &mut [u8]
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer, ArrayBufferView (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[anybuffer] &[u8]
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer, ArrayBufferView (resizable=true,false)
+</td><td>
+⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.
+</td></tr>
+<tr>
+<td>
+
+```text
+#[anybuffer] *mut u8
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer, ArrayBufferView (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
+#[anybuffer] *const u8
+```
+
+</td><td>
+✅
+</td><td>
+ArrayBuffer, ArrayBufferView (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] &mut [u8]
 ```
diff --git a/ops/op2/dispatch_async.rs b/ops/op2/dispatch_async.rs
index 7ad5067..0b334af 100644
--- a/ops/op2/dispatch_async.rs
+++ b/ops/op2/dispatch_async.rs
@@ -1,3 +1,5 @@
+use super::V8MappingError;
+use super::V8SignatureMappingError;
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
 use super::config::MacroConfig;
 use super::dispatch_slow::call;
@@ -16,15 +18,41 @@ use super::generator_state::gs_quote;
 use super::generator_state::GeneratorState;
 use super::signature::ParsedSignature;
 use super::signature::RetVal;
-use super::V8MappingError;
 use proc_macro2::TokenStream;
 use quote::quote;
 
+fn map_async_return_type(
+  generator_state: &mut GeneratorState,
+  ret_val: &RetVal,
+) -> Result<(TokenStream, TokenStream, TokenStream), V8MappingError> {
+  let return_value = match ret_val {
+    RetVal::Future(r) | RetVal::ResultFuture(r) => {
+      return_value_v8_value(generator_state, r)?
+    }
+    RetVal::FutureResult(r) | RetVal::ResultFutureResult(r) => {
+      return_value_v8_value(generator_state, r)?
+    }
+    RetVal::Infallible(_) | RetVal::Result(_) => return Err("an async return"),
+  };
+  let (mapper, return_value_immediate) = match ret_val {
+    RetVal::Future(r) | RetVal::ResultFuture(r) => (
+      quote!(map_async_op_infallible),
+      return_value_infallible(generator_state, r)?,
+    ),
+    RetVal::FutureResult(r) | RetVal::ResultFutureResult(r) => (
+      quote!(map_async_op_fallible),
+      return_value_result(generator_state, r)?,
+    ),
+    RetVal::Infallible(_) | RetVal::Result(_) => return Err("an async return"),
+  };
+  Ok((return_value, mapper, return_value_immediate))
+}
+
 pub(crate) fn generate_dispatch_async(
   config: &MacroConfig,
   generator_state: &mut GeneratorState,
   signature: &ParsedSignature,
-) -> Result<TokenStream, V8MappingError> {
+) -> Result<TokenStream, V8SignatureMappingError> {
   let mut output = TokenStream::new();
 
   // Collect virtual arguments in a deferred list that we compute at the very end. This allows us to borrow
@@ -36,11 +64,13 @@ pub(crate) fn generate_dispatch_async(
   let mut input_index = 1;
 
   for (index, arg) in signature.args.iter().enumerate() {
+    let arg_mapped = from_arg(generator_state, index, arg)
+      .map_err(|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()))?;
     if arg.is_virtual() {
-      deferred.extend(from_arg(generator_state, index, arg)?);
+      deferred.extend(arg_mapped);
     } else {
-      args.extend(extract_arg(generator_state, index, input_index)?);
-      args.extend(from_arg(generator_state, index, arg)?);
+      args.extend(extract_arg(generator_state, index, input_index));
+      args.extend(arg_mapped);
       input_index += 1;
     }
   }
@@ -52,33 +82,15 @@ pub(crate) fn generate_dispatch_async(
   generator_state.needs_scope = true;
   generator_state.needs_retval = true;
 
-  let return_value = match &signature.ret_val {
-    RetVal::Future(r) | RetVal::ResultFuture(r) => {
-      return_value_v8_value(generator_state, r)?
-    }
-    RetVal::FutureResult(r) | RetVal::ResultFutureResult(r) => {
-      return_value_v8_value(generator_state, r)?
-    }
-    RetVal::Infallible(r) | RetVal::Result(r) => {
-      return Err(V8MappingError::NoMapping("an async return", r.clone()))
-    }
-  };
-  let (mapper, return_value_immediate) = match &signature.ret_val {
-    RetVal::Future(r) | RetVal::ResultFuture(r) => (
-      quote!(map_async_op_infallible),
-      return_value_infallible(generator_state, r)?,
-    ),
-    RetVal::FutureResult(r) | RetVal::ResultFutureResult(r) => (
-      quote!(map_async_op_fallible),
-      return_value_result(generator_state, r)?,
-    ),
-    RetVal::Infallible(r) | RetVal::Result(r) => {
-      return Err(V8MappingError::NoMapping("an async return", r.clone()))
-    }
-  };
+  let (return_value, mapper, return_value_immediate) =
+    map_async_return_type(generator_state, &signature.ret_val).map_err(
+      |s| {
+        V8SignatureMappingError::NoRetValMapping(s, signature.ret_val.clone())
+      },
+    )?;
 
   args.extend(deferred);
-  args.extend(call(generator_state)?);
+  args.extend(call(generator_state));
   output.extend(gs_quote!(generator_state(deno_core, result, opctx) => {
     if #opctx.metrics_enabled() {
       #deno_core::_ops::dispatch_metrics_async(&#opctx, #deno_core::_ops::OpMetricsEvent::Dispatched);
@@ -92,7 +104,7 @@ pub(crate) fn generate_dispatch_async(
     signature.ret_val,
     RetVal::ResultFuture(_) | RetVal::ResultFutureResult(_)
   ) {
-    let exception = throw_exception(generator_state)?;
+    let exception = throw_exception(generator_state);
     output.extend(gs_quote!(generator_state(deno_core, opctx, result) => {
       let #result = match #result {
         Ok(#result) => #result,
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 6371741..1bb8089 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -7,8 +7,9 @@ use super::dispatch_shared::v8slice_to_buffer;
 use super::generator_state::gs_quote;
 use super::generator_state::GeneratorState;
 use super::signature::Arg;
-use super::signature::Buffer;
 use super::signature::BufferMode;
+use super::signature::BufferSource;
+use super::signature::BufferType;
 use super::signature::NumericArg;
 use super::signature::NumericFlag;
 use super::signature::ParsedSignature;
@@ -17,6 +18,7 @@ use super::signature::RetVal;
 use super::signature::Special;
 use super::signature::Strings;
 use super::V8MappingError;
+use super::V8SignatureMappingError;
 use proc_macro2::Ident;
 use proc_macro2::TokenStream;
 use quote::format_ident;
@@ -38,6 +40,8 @@ pub(crate) enum V8FastCallType {
   F64,
   Pointer,
   V8Value,
+  /// Any typed array.
+  AnyArray,
   Uint8Array,
   Uint32Array,
   Float64Array,
@@ -82,8 +86,8 @@ 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::AnyArray | V8FastCallType::ArrayBuffer => {
+        quote!(#deno_core::v8::Local<#deno_core::v8::Value>)
       }
       V8FastCallType::Virtual => unreachable!("invalid virtual argument"),
     }
@@ -103,6 +107,7 @@ impl V8FastCallType {
       V8FastCallType::Pointer => quote!(CType::Pointer),
       V8FastCallType::V8Value => quote!(CType::V8Value),
       V8FastCallType::CallbackOptions => quote!(CType::CallbackOptions),
+      V8FastCallType::AnyArray => unreachable!(),
       V8FastCallType::Uint8Array => unreachable!(),
       V8FastCallType::Uint32Array => unreachable!(),
       V8FastCallType::Float64Array => unreachable!(),
@@ -126,6 +131,7 @@ impl V8FastCallType {
       V8FastCallType::Pointer => quote!(Type::Pointer),
       V8FastCallType::V8Value => quote!(Type::V8Value),
       V8FastCallType::CallbackOptions => quote!(Type::CallbackOptions),
+      V8FastCallType::AnyArray => quote!(Type::V8Value),
       V8FastCallType::Uint8Array => quote!(Type::TypedArray(CType::Uint8)),
       V8FastCallType::Uint32Array => quote!(Type::TypedArray(CType::Uint32)),
       V8FastCallType::Float64Array => quote!(Type::TypedArray(CType::Float64)),
@@ -139,7 +145,10 @@ impl V8FastCallType {
 pub fn generate_dispatch_fast(
   generator_state: &mut GeneratorState,
   signature: &ParsedSignature,
-) -> Result<Option<(TokenStream, TokenStream, TokenStream)>, V8MappingError> {
+) -> Result<
+  Option<(TokenStream, TokenStream, TokenStream)>,
+  V8SignatureMappingError,
+> {
   enum Input<'a> {
     Concrete(V8FastCallType),
     Virtual(&'a Arg),
@@ -152,7 +161,7 @@ pub fn generate_dispatch_fast(
 
   let mut inputs = vec![];
   for arg in &signature.args {
-    let Some(fv) = map_arg_to_v8_fastcall_type(arg)? else {
+    let Some(fv) = map_arg_to_v8_fastcall_type(arg).map_err(|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()))? else {
       return Ok(None);
     };
     if fv == V8FastCallType::Virtual {
@@ -168,7 +177,9 @@ pub fn generate_dispatch_fast(
     _ => todo!(),
   };
 
-  let output = match map_retval_to_v8_fastcall_type(ret_val)? {
+  let output = match map_retval_to_v8_fastcall_type(ret_val).map_err(|s| {
+    V8SignatureMappingError::NoRetValMapping(s, signature.ret_val.clone())
+  })? {
     None => return Ok(None),
     Some(rv) => rv,
   };
@@ -188,7 +199,10 @@ pub fn generate_dispatch_fast(
     }
 
     call_names.push(name.clone());
-    call_args.push(map_v8_fastcall_arg_to_arg(generator_state, &name, arg)?)
+    call_args.push(
+      map_v8_fastcall_arg_to_arg(generator_state, &name, arg)
+        .map_err(|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()))?,
+    )
   }
 
   let GeneratorState {
@@ -374,7 +388,11 @@ fn map_v8_fastcall_arg_to_arg(
   let arg_temp = format_ident!("{}_temp", arg_ident);
 
   let res = match arg {
-    Arg::ArrayBuffer(buffer @ (Buffer::V8Slice(..) | Buffer::JsBuffer(..))) => {
+    Arg::Buffer(
+      buffer @ (BufferType::V8Slice(..) | BufferType::JsBuffer),
+      _,
+      BufferSource::ArrayBuffer,
+    ) => {
       *needs_fast_api_callback_options = true;
       let buf = v8slice_to_buffer(deno_core, arg_ident, &arg_temp, *buffer)?;
       quote!(
@@ -386,7 +404,7 @@ fn map_v8_fastcall_arg_to_arg(
         #buf
       )
     }
-    Arg::ArrayBuffer(buffer) => {
+    Arg::Buffer(buffer, _, BufferSource::ArrayBuffer) => {
       *needs_fast_api_callback_options = true;
       let buf = byte_slice_to_buffer(arg_ident, &arg_temp, *buffer)?;
       quote!(
@@ -399,7 +417,20 @@ fn map_v8_fastcall_arg_to_arg(
         #buf
       )
     }
-    Arg::Buffer(buffer) => {
+    Arg::Buffer(buffer, _, BufferSource::Any) => {
+      *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_any(#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, _, BufferSource::TypedArray) => {
       fast_api_typed_array_to_buffer(deno_core, arg_ident, arg_ident, *buffer)?
     }
     Arg::Ref(RefType::Ref, Special::OpState) => {
@@ -514,28 +545,33 @@ fn map_arg_to_v8_fastcall_type(
   arg: &Arg,
 ) -> Result<Option<V8FastCallType>, V8MappingError> {
   let rv = match arg {
+    // We don't allow detaching buffers in fast mode
+    Arg::Buffer(_, BufferMode::Detach, _) => return Ok(None),
+    // We don't allow JsBuffer or V8Slice fastcalls for TypedArray
+    // TODO(mmastrac): we can enable these soon
     Arg::Buffer(
-      Buffer::Slice(_, NumericArg::u8)
-      | Buffer::Ptr(_, NumericArg::u8)
-      | Buffer::Vec(NumericArg::u8)
-      | Buffer::BoxSlice(NumericArg::u8)
-      | Buffer::Bytes(BufferMode::Copy),
-    ) => V8FastCallType::Uint8Array,
+      BufferType::JsBuffer | BufferType::V8Slice(..),
+      _,
+      BufferSource::TypedArray,
+    ) => return Ok(None),
     Arg::Buffer(
-      Buffer::Slice(_, NumericArg::u32)
-      | Buffer::Ptr(_, NumericArg::u32)
-      | Buffer::Vec(NumericArg::u32)
-      | Buffer::BoxSlice(NumericArg::u32),
+      BufferType::Slice(.., NumericArg::u8)
+      | BufferType::Ptr(.., NumericArg::u8),
+      _,
+      BufferSource::Any,
+    ) => V8FastCallType::AnyArray,
+    // TODO(mmastrac): implement fast for any Any-typed buffer
+    Arg::Buffer(_, _, BufferSource::Any) => return Ok(None),
+    Arg::Buffer(_, _, BufferSource::ArrayBuffer) => V8FastCallType::ArrayBuffer,
+    Arg::Buffer(
+      BufferType::Slice(.., NumericArg::u32)
+      | BufferType::Ptr(.., NumericArg::u32)
+      | BufferType::Vec(.., NumericArg::u32)
+      | BufferType::BoxSlice(.., NumericArg::u32),
+      _,
+      BufferSource::TypedArray,
     ) => 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),
+    Arg::Buffer(_, _, BufferSource::TypedArray) => V8FastCallType::Uint8Array,
     // Virtual OpState arguments
     Arg::RcRefCell(Special::OpState)
     | Arg::Ref(_, Special::OpState)
@@ -547,8 +583,7 @@ fn map_arg_to_v8_fastcall_type(
     Arg::OptionNumeric(..)
     | Arg::Option(_)
     | Arg::OptionString(_)
-    | Arg::OptionArrayBuffer(_)
-    | Arg::OptionBuffer(_)
+    | Arg::OptionBuffer(..)
     | Arg::SerdeV8(_)
     | Arg::Ref(..) => return Ok(None),
     // We don't support v8 global arguments
@@ -589,7 +624,7 @@ fn map_arg_to_v8_fastcall_type(
     // Cow byte strings can be fast and don't require copying
     Arg::String(Strings::CowByte) => V8FastCallType::SeqOneByteString,
     Arg::External(..) => V8FastCallType::Pointer,
-    _ => return Err(V8MappingError::NoMapping("a fast argument", arg.clone())),
+    _ => return Err("a fast argument"),
   };
   Ok(Some(rv))
 }
@@ -635,14 +670,9 @@ fn map_retval_to_v8_fastcall_type(
     | Arg::V8Local(_)
     | Arg::OptionV8Local(_)
     | Arg::OptionV8Ref(..) => return Ok(None),
-    Arg::ArrayBuffer(..) | Arg::Buffer(..) => return Ok(None),
+    Arg::Buffer(..) | Arg::OptionBuffer(..) => return Ok(None),
     Arg::External(..) => V8FastCallType::Pointer,
-    _ => {
-      return Err(V8MappingError::NoMapping(
-        "a fast return value",
-        arg.clone(),
-      ))
-    }
+    _ => return Err("a fast return value"),
   };
   Ok(Some(rv))
 }
diff --git a/ops/op2/dispatch_shared.rs b/ops/op2/dispatch_shared.rs
index ffc9c30..06e704b 100644
--- a/ops/op2/dispatch_shared.rs
+++ b/ops/op2/dispatch_shared.rs
@@ -1,7 +1,6 @@
 // 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::BufferType;
 use super::signature::NumericArg;
 use super::signature::RefType;
 use super::signature::V8Arg;
@@ -77,78 +76,69 @@ pub fn v8_to_arg(
   })
 }
 
-/// Given a V8Slice in `v8slice`, turns it into the appropriate slice type in `arg_ident`.
+/// Given a `V8Slice` in `v8slice`, turns it into the appropriate buffer type in `arg_ident`.
 pub fn v8slice_to_buffer(
   deno_core: &TokenStream,
   arg_ident: &Ident,
   v8slice: &Ident,
-  buffer: Buffer,
+  buffer: BufferType,
 ) -> Result<TokenStream, V8MappingError> {
   let make_arg = match buffer {
-    Buffer::V8Slice(_, _) => {
+    BufferType::V8Slice(..) => {
       quote!(let #arg_ident = #arg_ident;)
     }
-    Buffer::Slice(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
+    BufferType::Slice(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #v8slice.as_ref();)
     }
-    Buffer::Slice(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
+    BufferType::Slice(RefType::Mut, NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #v8slice.as_mut();)
     }
-    Buffer::Ptr(RefType::Ref, NumericArg::u8 | NumericArg::u32) => {
+    BufferType::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) => {
+    BufferType::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) => {
+    BufferType::Vec(NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #v8slice.to_vec();)
     }
-    Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
+    BufferType::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #v8slice.to_boxed_slice();)
     }
-    Buffer::Bytes(BufferMode::Copy) => {
+    BufferType::Bytes => {
       quote!(let #arg_ident = #v8slice.to_vec().into();)
     }
-    Buffer::JsBuffer(BufferMode::Default | BufferMode::Detach) => {
+    BufferType::JsBuffer => {
       quote!(let #arg_ident = #deno_core::serde_v8::JsBuffer::from_parts(#v8slice);)
     }
-    _ => {
-      return Err(V8MappingError::NoMapping(
-        "a v8slice argument",
-        Arg::Buffer(buffer),
-      ))
-    }
+    _ => return Err("a v8slice argument"),
   };
   Ok(make_arg)
 }
 
+// Given a `&[u8]` in `buf`, turns it into the appropriate buffer type in `arg_ident`.
 pub fn byte_slice_to_buffer(
   arg_ident: &Ident,
   buf: &Ident,
-  buffer: Buffer,
+  buffer: BufferType,
 ) -> Result<TokenStream, V8MappingError> {
   let res = match buffer {
-    Buffer::Slice(_, NumericArg::u8 | NumericArg::u32) => {
+    BufferType::Slice(_, NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #buf;)
     }
-    Buffer::Ptr(_, NumericArg::u8 | NumericArg::u32) => {
+    BufferType::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) => {
+    BufferType::Vec(NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #buf.to_vec();)
     }
-    Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
+    BufferType::BoxSlice(NumericArg::u8 | NumericArg::u32) => {
       quote!(let #arg_ident = #buf.to_vec().into_boxed_slice();)
     }
-    Buffer::Bytes(BufferMode::Copy) => {
+    BufferType::Bytes => {
       quote!(let #arg_ident = #buf.to_vec().into();)
     }
-    _ => {
-      return Err(V8MappingError::NoMapping(
-        "a fast typed array buffer argument",
-        Arg::Buffer(buffer),
-      ))
-    }
+    _ => return Err("a fast typed array buffer argument"),
   };
 
   Ok(res)
@@ -158,7 +148,7 @@ pub fn fast_api_typed_array_to_buffer(
   deno_core: &TokenStream,
   arg_ident: &Ident,
   input: &Ident,
-  buffer: Buffer,
+  buffer: BufferType,
 ) -> Result<TokenStream, V8MappingError> {
   let convert = byte_slice_to_buffer(arg_ident, input, buffer)?;
   Ok(quote! {
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index a556d80..06cad07 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -10,8 +10,9 @@ 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::BufferSource;
+use super::signature::BufferType;
 use super::signature::External;
 use super::signature::NumericArg;
 use super::signature::NumericFlag;
@@ -21,6 +22,7 @@ use super::signature::RetVal;
 use super::signature::Special;
 use super::signature::Strings;
 use super::V8MappingError;
+use super::V8SignatureMappingError;
 use proc_macro2::Ident;
 use proc_macro2::TokenStream;
 use quote::format_ident;
@@ -31,13 +33,13 @@ pub(crate) fn generate_dispatch_slow(
   config: &MacroConfig,
   generator_state: &mut GeneratorState,
   signature: &ParsedSignature,
-) -> Result<TokenStream, V8MappingError> {
+) -> Result<TokenStream, V8SignatureMappingError> {
   let mut output = TokenStream::new();
 
   // Fast ops require the slow op to check op_ctx for the last error
   if config.fast && matches!(signature.ret_val, RetVal::Result(_)) {
     generator_state.needs_opctx = true;
-    let throw_exception = throw_exception(generator_state)?;
+    let throw_exception = throw_exception(generator_state);
     // If the fast op returned an error, we must throw it rather than doing work.
     output.extend(quote!{
       // FASTCALL FALLBACK: This is where we pick up the errors for the slow-call error pickup
@@ -59,21 +61,25 @@ pub(crate) fn generate_dispatch_slow(
   let mut input_index = 0;
 
   for (index, arg) in signature.args.iter().enumerate() {
+    let arg_mapped = from_arg(generator_state, index, arg)
+      .map_err(|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()))?;
     if arg.is_virtual() {
-      deferred.extend(from_arg(generator_state, index, arg)?);
+      deferred.extend(arg_mapped);
     } else {
-      args.extend(extract_arg(generator_state, index, input_index)?);
-      args.extend(from_arg(generator_state, index, arg)?);
+      args.extend(extract_arg(generator_state, index, input_index));
+      args.extend(arg_mapped);
       input_index += 1;
     }
   }
 
   args.extend(deferred);
-  args.extend(call(generator_state)?);
+  args.extend(call(generator_state));
   output.extend(gs_quote!(generator_state(result) => (let #result = {
     #args
   };)));
-  output.extend(return_value(generator_state, &signature.ret_val)?);
+  output.extend(return_value(generator_state, &signature.ret_val).map_err(
+    |s| V8SignatureMappingError::NoRetValMapping(s, signature.ret_val.clone()),
+  )?);
 
   // We only generate the isolate if we need it but don't need a scope. We call it `scope`.
   let with_isolate =
@@ -210,13 +216,13 @@ pub fn extract_arg(
   generator_state: &mut GeneratorState,
   index: usize,
   input_index: usize,
-) -> Result<TokenStream, V8MappingError> {
+) -> TokenStream {
   let GeneratorState { fn_args, .. } = &generator_state;
   let arg_ident = generator_state.args.get(index);
 
-  Ok(quote!(
+  quote!(
     let #arg_ident = #fn_args.get(#input_index as i32);
-  ))
+  )
 }
 
 pub fn from_arg(
@@ -335,21 +341,33 @@ pub fn from_arg(
         };
       })
     }
-    Arg::Buffer(_) | Arg::ArrayBuffer(_) => {
+    Arg::Buffer(buffer_type, mode, source) => {
       // Explicit temporary lifetime extension so we can take a reference
       let temp = format_ident!("{}_temp", arg_ident);
-      let buffer =
-        from_arg_array_or_buffer(generator_state, &arg_ident, arg, &temp)?;
+      let buffer = from_arg_array_or_buffer(
+        generator_state,
+        &arg_ident,
+        *buffer_type,
+        *mode,
+        *source,
+        &temp,
+      )?;
       quote! {
         let mut #temp;
         #buffer
       }
     }
-    Arg::OptionBuffer(_) | Arg::OptionArrayBuffer(_) => {
+    Arg::OptionBuffer(buffer_type, mode, source) => {
       // Explicit temporary lifetime extension so we can take a reference
       let temp = format_ident!("{}_temp", arg_ident);
-      let some =
-        from_arg_array_or_buffer(generator_state, &arg_ident, arg, &temp)?;
+      let some = from_arg_array_or_buffer(
+        generator_state,
+        &arg_ident,
+        *buffer_type,
+        *mode,
+        *source,
+        &temp,
+      )?;
       quote! {
         let mut #temp;
         let #arg_ident = if #arg_ident.is_null_or_undefined() {
@@ -477,7 +495,7 @@ pub fn from_arg(
         };
       }
     }
-    _ => return Err(V8MappingError::NoMapping("a slow argument", arg.clone())),
+    _ => return Err("a slow argument"),
   };
   Ok(res)
 }
@@ -502,33 +520,50 @@ pub fn from_arg_option(
 pub fn from_arg_array_or_buffer(
   generator_state: &mut GeneratorState,
   arg_ident: &Ident,
-  buffer: &Arg,
+  buffer_type: BufferType,
+  buffer_mode: BufferMode,
+  buffer_source: BufferSource,
   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!(),
+  match buffer_source {
+    BufferSource::TypedArray => from_arg_buffer(
+      generator_state,
+      arg_ident,
+      buffer_type,
+      buffer_mode,
+      temp,
+    ),
+    BufferSource::ArrayBuffer => from_arg_arraybuffer(
+      generator_state,
+      arg_ident,
+      buffer_type,
+      buffer_mode,
+      temp,
+    ),
+    BufferSource::Any => from_arg_any_buffer(
+      generator_state,
+      arg_ident,
+      buffer_type,
+      buffer_mode,
+      temp,
+    ),
   }
 }
 
 pub fn from_arg_buffer(
   generator_state: &mut GeneratorState,
   arg_ident: &Ident,
-  buffer: &Buffer,
+  buffer_type: BufferType,
+  buffer_mode: BufferMode,
   temp: &Ident,
 ) -> Result<TokenStream, V8MappingError> {
   let err = format_ident!("{}_err", arg_ident);
   let throw_exception = throw_type_error_static_string(generator_state, &err)?;
 
-  let array = buffer.element();
+  let array = buffer_type.element();
   generator_state.needs_scope = true;
 
-  let to_v8_slice = if matches!(buffer, Buffer::JsBuffer(BufferMode::Detach)) {
+  let to_v8_slice = if matches!(buffer_mode, BufferMode::Detach) {
     quote!(to_v8_slice_detachable)
   } else {
     quote!(to_v8_slice)
@@ -543,8 +578,12 @@ pub fn from_arg_buffer(
     };
   });
 
-  let make_arg =
-    v8slice_to_buffer(&generator_state.deno_core, arg_ident, temp, *buffer)?;
+  let make_arg = v8slice_to_buffer(
+    &generator_state.deno_core,
+    arg_ident,
+    temp,
+    buffer_type,
+  )?;
 
   Ok(quote! {
     #make_v8slice
@@ -555,13 +594,14 @@ pub fn from_arg_buffer(
 pub fn from_arg_arraybuffer(
   generator_state: &mut GeneratorState,
   arg_ident: &Ident,
-  buffer: &Buffer,
+  buffer_type: BufferType,
+  buffer_mode: BufferMode,
   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)) {
+  let to_v8_slice = if matches!(buffer_mode, BufferMode::Detach) {
     quote!(to_v8_slice_buffer_detachable)
   } else {
     quote!(to_v8_slice_buffer)
@@ -576,8 +616,12 @@ pub fn from_arg_arraybuffer(
     };
   });
 
-  let make_arg =
-    v8slice_to_buffer(&generator_state.deno_core, arg_ident, temp, *buffer)?;
+  let make_arg = v8slice_to_buffer(
+    &generator_state.deno_core,
+    arg_ident,
+    temp,
+    buffer_type,
+  )?;
 
   Ok(quote! {
     #make_v8slice
@@ -585,14 +629,53 @@ pub fn from_arg_arraybuffer(
   })
 }
 
-pub fn call(
+pub fn from_arg_any_buffer(
   generator_state: &mut GeneratorState,
+  arg_ident: &Ident,
+  buffer_type: BufferType,
+  buffer_mode: BufferMode,
+  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_mode, BufferMode::Detach) {
+    quote!(to_v8_slice_any_detachable)
+  } else {
+    quote!(to_v8_slice_any)
+  };
+
+  // TODO(mmastrac): upstream change required to avoid scope
+  generator_state.needs_scope = true;
+
+  let make_v8slice = gs_quote!(generator_state(deno_core, scope) => {
+    #temp = match unsafe { #deno_core::_ops::#to_v8_slice(&mut #scope, #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_type,
+  )?;
+
+  Ok(quote! {
+    #make_v8slice
+    #make_arg
+  })
+}
+
+pub fn call(generator_state: &mut GeneratorState) -> TokenStream {
   let mut tokens = TokenStream::new();
   for arg in &generator_state.args {
     tokens.extend(quote!( #arg , ));
   }
-  Ok(quote!(Self::call( #tokens )))
+  quote!(Self::call( #tokens ))
 }
 
 pub fn return_value(
@@ -665,12 +748,7 @@ pub fn return_value_infallible(
         },
       }))
     }
-    ArgSlowRetval::None => {
-      return Err(V8MappingError::NoMapping(
-        "a slow return value",
-        ret_type.clone(),
-      ))
-    }
+    ArgSlowRetval::None => return Err("a slow return value"),
   };
 
   Ok(res)
@@ -707,12 +785,7 @@ pub fn return_value_v8_value(
     ArgSlowRetval::RetValFallible | ArgSlowRetval::V8LocalFalliable => {
       quote!(#deno_core::_ops::RustToV8Fallible::to_v8_fallible(#result, #scope))
     }
-    ArgSlowRetval::None => {
-      return Err(V8MappingError::NoMapping(
-        "a v8 return value",
-        ret_type.clone(),
-      ))
-    }
+    ArgSlowRetval::None => return Err("a v8 return value"),
   };
   Ok(res)
 }
@@ -722,7 +795,7 @@ pub fn return_value_result(
   ret_type: &Arg,
 ) -> Result<TokenStream, V8MappingError> {
   let infallible = return_value_infallible(generator_state, ret_type)?;
-  let exception = throw_exception(generator_state)?;
+  let exception = throw_exception(generator_state);
 
   let tokens = gs_quote!(generator_state(result) => (
     match #result {
@@ -740,7 +813,7 @@ pub fn return_value_result(
 /// Generates code to throw an exception, adding required additional dependencies as needed.
 pub(crate) fn throw_exception(
   generator_state: &mut GeneratorState,
-) -> Result<TokenStream, V8MappingError> {
+) -> TokenStream {
   let maybe_scope = if generator_state.needs_scope {
     quote!()
   } else {
@@ -759,7 +832,7 @@ pub(crate) fn throw_exception(
     with_fn_args(generator_state)
   };
 
-  Ok(gs_quote!(generator_state(deno_core, scope, opctx) => {
+  gs_quote!(generator_state(deno_core, scope, opctx) => {
     #maybe_scope
     #maybe_args
     #maybe_opctx
@@ -771,7 +844,7 @@ pub(crate) fn throw_exception(
     );
     #scope.throw_exception(exception);
     return;
-  }))
+  })
 }
 
 /// Generates code to throw an exception, adding required additional dependencies as needed.
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 0cc07c7..8104434 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -25,6 +25,7 @@ use self::generator_state::GeneratorState;
 use self::signature::is_attribute_special;
 use self::signature::parse_signature;
 use self::signature::Arg;
+use self::signature::RetVal;
 use self::signature::SignatureError;
 
 pub mod config;
@@ -44,8 +45,8 @@ pub enum Op2Error {
   InvalidAttribute(String),
   #[error("Failed to parse syntax tree")]
   ParseError(#[from] syn::Error),
-  #[error("Failed to map a parsed signature to a V8 call")]
-  V8MappingError(#[from] V8MappingError),
+  #[error("Failed to map signature to V8")]
+  V8SignatureMappingError(#[from] V8SignatureMappingError),
   #[error("Failed to parse signature")]
   SignatureError(#[from] SignatureError),
   #[error("This op cannot use both ({0}) and ({1})")]
@@ -63,11 +64,15 @@ pub enum Op2Error {
 }
 
 #[derive(Debug, Error)]
-pub enum V8MappingError {
-  #[error("Unable to map {1:?} to {0}")]
-  NoMapping(&'static str, Arg),
+pub enum V8SignatureMappingError {
+  #[error("Unable to map return value {1:?} to {0}")]
+  NoRetValMapping(V8MappingError, RetVal),
+  #[error("Unable to map argument {1:?} to {0}")]
+  NoArgMapping(V8MappingError, Arg),
 }
 
+pub type V8MappingError = &'static str;
+
 pub fn op2(
   attr: TokenStream,
   item: TokenStream,
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index bbf9e4f..4f5758f 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -155,7 +155,7 @@ pub enum Strings {
 
 /// Buffers are complicated and may be shared/owned, shared/unowned, a copy, or detached.
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum Buffer {
+pub enum BufferType {
   /// Shared/unowned, may be resizable. [`&[u8]`], [`&mut [u8]`], [`&[u32]`], etc...
   Slice(RefType, NumericArg),
   /// Shared/unowned, may be resizable. [`*const u8`], [`*mut u8`], [`*const u32`], etc...
@@ -165,43 +165,44 @@ pub enum Buffer {
   /// Owned, copy. [`Vec<u8>`], [`Vec<u32>`], etc...
   Vec(NumericArg),
   /// Maybe shared or a copy. Stored in `bytes::Bytes`
-  Bytes(BufferMode),
+  Bytes,
   /// Owned, copy. Stored in `bytes::BytesMut`
-  BytesMut(BufferMode),
+  BytesMut,
   /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::V8Slice`
-  V8Slice(BufferMode, NumericArg),
+  V8Slice(NumericArg),
   /// Shared, not resizable (or resizable and detatched), stored in `serde_v8::JsBuffer`
-  JsBuffer(BufferMode),
+  JsBuffer,
 }
 
-impl Buffer {
+impl BufferType {
   pub const fn valid_modes(
     &self,
     position: Position,
   ) -> &'static [AttributeModifier] {
-    use AttributeModifier::ArrayBuffer as A;
-    use AttributeModifier::Buffer as B;
-    use Buffer::*;
-    use BufferMode::*;
+    use BufferType::*;
+    // For each mode, apply it to TypedArray, ArrayBuffer, and Any.
+    macro_rules! expand {
+      ($($mode:ident),*) => {
+        &[$(
+          AttributeModifier::Buffer(BufferMode::$mode, BufferSource::TypedArray),
+          AttributeModifier::Buffer(BufferMode::$mode, BufferSource::ArrayBuffer),
+          AttributeModifier::Buffer(BufferMode::$mode, BufferSource::Any),
+        )*]
+      };
+    }
     match position {
       Position::Arg => match self {
-        Bytes(..) | BytesMut(..) | Vec(..) | BoxSlice(..) => {
-          &[A(Copy), B(Copy)]
+        Bytes | BytesMut | Vec(..) | BoxSlice(..) => {
+          expand!(Copy)
         }
-        JsBuffer(..) | V8Slice(..) => &[
-          A(Copy),
-          A(Detach),
-          A(Default),
-          B(Copy),
-          B(Detach),
-          B(Default),
-        ],
-        Slice(..) | Ptr(..) => &[A(Default), B(Default)],
+        JsBuffer | V8Slice(..) => expand!(Copy, Detach, Default),
+        Slice(..) | Ptr(..) => expand!(Default),
       },
       Position::RetVal => match self {
-        Bytes(..) | BytesMut(..) | JsBuffer(..) | V8Slice(..) | Vec(..)
-        | BoxSlice(..) => &[A(Default), B(Default)],
-        Slice(..) | Ptr(..) => &([]),
+        Bytes | BytesMut | JsBuffer | V8Slice(..) | Vec(..) | BoxSlice(..) => {
+          expand!(Default)
+        }
+        Slice(..) | Ptr(..) => expand!(Default),
       },
     }
   }
@@ -210,10 +211,10 @@ impl Buffer {
     match self {
       Self::Slice(_, arg) => *arg,
       Self::BoxSlice(arg) => *arg,
-      Self::Bytes(_) | Self::BytesMut(_) | Self::JsBuffer(_) => NumericArg::u8,
+      Self::Bytes | Self::BytesMut | Self::JsBuffer => NumericArg::u8,
       Self::Ptr(_, arg) => *arg,
       Self::Vec(arg) => *arg,
-      Self::V8Slice(_, arg) => *arg,
+      Self::V8Slice(arg) => *arg,
     }
   }
 }
@@ -244,16 +245,14 @@ pub enum Arg {
   Void,
   Special(Special),
   String(Strings),
-  ArrayBuffer(Buffer),
-  Buffer(Buffer),
+  Buffer(BufferType, BufferMode, BufferSource),
   External(External),
   Ref(RefType, Special),
   RcRefCell(Special),
   Option(Special),
   OptionString(Strings),
   OptionNumeric(NumericArg, NumericFlag),
-  OptionArrayBuffer(Buffer),
-  OptionBuffer(Buffer),
+  OptionBuffer(BufferType, BufferMode, BufferSource),
   OptionV8Local(V8Arg),
   OptionV8Global(V8Arg),
   V8Local(V8Arg),
@@ -267,33 +266,38 @@ pub enum Arg {
 }
 
 impl Arg {
-  const fn from_parsed(
+  fn from_parsed(
     parsed: ParsedTypeContainer,
     attr: Attributes,
-  ) -> Result<Self, ()> {
+  ) -> Result<Self, ArgError> {
     use ParsedType::*;
     use ParsedTypeContainer::*;
+
+    let buffer_mode = || match attr.primary {
+      Some(AttributeModifier::Buffer(mode, _)) => Ok(mode),
+      _ => Err(ArgError::MissingAttribute("buffer", format!("{parsed:?}"))),
+    };
+
+    let buffer_source = || match attr.primary {
+      Some(AttributeModifier::Buffer(_, source)) => Ok(source),
+      _ => Err(ArgError::MissingAttribute("buffer", format!("{parsed:?}"))),
+    };
+
     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, buffer_mode()?, buffer_source()?))
       }
-      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, buffer_mode()?, buffer_source()?))
       }
-      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)),
@@ -301,7 +305,7 @@ impl Arg {
       COption(TV8Mut(v8)) => Ok(Arg::OptionV8Ref(RefType::Mut, v8)),
       CV8Local(TV8(v8)) => Ok(Arg::V8Local(v8)),
       CV8Global(TV8(v8)) => Ok(Arg::V8Global(v8)),
-      _ => Err(()),
+      _ => unreachable!(),
     }
   }
 
@@ -382,8 +386,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::OptionBuffer(t, m, s) => Arg::Buffer(*t, *m, *s),
       Arg::OptionState(r, t) => Arg::State(*r, t.clone()),
       _ => return None,
     })
@@ -423,38 +426,20 @@ 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)
-          | Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)
-          | Buffer::V8Slice(
-            BufferMode::Default,
-            NumericArg::u8 | NumericArg::u32,
-          )
-          | 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)
-          | Buffer::BoxSlice(NumericArg::u8 | NumericArg::u32)
-          | Buffer::BytesMut(BufferMode::Default),
-        ) => ArgSlowRetval::V8LocalFalliable,
+        // ArrayBuffer is infallible
+        Arg::Buffer(.., BufferSource::ArrayBuffer) => ArgSlowRetval::V8Local,
+        // TypedArray is fallible
+        Arg::Buffer(.., BufferSource::TypedArray) => {
+          ArgSlowRetval::V8LocalFalliable
+        }
+        // ArrayBuffer is infallible
+        Arg::OptionBuffer(.., BufferSource::ArrayBuffer) => {
+          ArgSlowRetval::V8Local
+        }
+        // TypedArray is fallible
+        Arg::OptionBuffer(.., BufferSource::TypedArray) => {
+          ArgSlowRetval::V8LocalFalliable
+        }
         _ => ArgSlowRetval::None,
       }
     }
@@ -463,7 +448,10 @@ 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::Buffer(.., BufferSource::ArrayBuffer)
+      | Arg::OptionBuffer(.., BufferSource::ArrayBuffer) => {
+        ArgMarker::ArrayBuffer
+      }
       Arg::SerdeV8(_) => ArgMarker::Serde,
       Arg::Numeric(NumericArg::__SMI__, _) => ArgMarker::Smi,
       Arg::Numeric(_, NumericFlag::Number) => ArgMarker::Number,
@@ -505,10 +493,11 @@ pub enum ArgMarker {
   ArrayBuffer,
 }
 
+#[derive(Debug)]
 pub enum ParsedType {
   TSpecial(Special),
   TString(Strings),
-  TBuffer(Buffer),
+  TBuffer(BufferType),
   TV8(V8Arg),
   // TODO(mmastrac): We need to carry the mut status through somehow
   TV8Mut(V8Arg),
@@ -540,6 +529,7 @@ impl ParsedType {
   }
 }
 
+#[derive(Debug)]
 pub enum ParsedTypeContainer {
   CBare(ParsedType),
   COption(ParsedType),
@@ -674,6 +664,17 @@ pub enum BufferMode {
   Detach,
 }
 
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum BufferSource {
+  /// The buffer expects an exactly-typed TypedArray of the given underlying format.
+  TypedArray,
+  /// The buffer expects a raw ArrayBuffer, which is an unsliced underlying backing store.
+  ArrayBuffer,
+  /// The buffer expects a byte-like slice which may be an ArrayBuffer, a TypedArray, or
+  /// a DataView.
+  Any,
+}
+
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum AttributeModifier {
   /// #[serde], for serde_v8 types.
@@ -686,9 +687,7 @@ pub enum AttributeModifier {
   /// #[state], for automatic OpState extraction.
   State,
   /// #[buffer], for buffers.
-  Buffer(BufferMode),
-  /// #[arraybuffer], for buffers backed directly by v8::ArrayBuffer rather than views.
-  ArrayBuffer(BufferMode),
+  Buffer(BufferMode, BufferSource),
   /// #[global], for [`v8::Global`]s
   Global,
   /// #[bigint], for u64/usize/i64/isize indicating value is a BigInt
@@ -702,8 +701,7 @@ impl AttributeModifier {
     match self {
       AttributeModifier::Bigint => "bigint",
       AttributeModifier::Number => "number",
-      AttributeModifier::ArrayBuffer(_) => "buffer",
-      AttributeModifier::Buffer(_) => "buffer",
+      AttributeModifier::Buffer(..) => "buffer",
       AttributeModifier::Smi => "smi",
       AttributeModifier::Serde => "serde",
       AttributeModifier::String(_) => "string",
@@ -1087,14 +1085,18 @@ fn parse_attribute(
       (#[string]) => Some(AttributeModifier::String(StringMode::Default)),
       (#[string(onebyte)]) => Some(AttributeModifier::String(StringMode::OneByte)),
       (#[state]) => Some(AttributeModifier::State),
-      (#[buffer]) => Some(AttributeModifier::Buffer(BufferMode::Default)),
-      (#[buffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe)),
-      (#[buffer(copy)]) => Some(AttributeModifier::Buffer(BufferMode::Copy)),
-      (#[buffer(detach)]) => Some(AttributeModifier::Buffer(BufferMode::Detach)),
-      (#[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)),
+      (#[buffer]) => Some(AttributeModifier::Buffer(BufferMode::Default, BufferSource::TypedArray)),
+      (#[buffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe, BufferSource::TypedArray)),
+      (#[buffer(copy)]) => Some(AttributeModifier::Buffer(BufferMode::Copy, BufferSource::TypedArray)),
+      (#[buffer(detach)]) => Some(AttributeModifier::Buffer(BufferMode::Detach, BufferSource::TypedArray)),
+      (#[anybuffer]) => Some(AttributeModifier::Buffer(BufferMode::Default, BufferSource::Any)),
+      (#[anybuffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe, BufferSource::Any)),
+      (#[anybuffer(copy)]) => Some(AttributeModifier::Buffer(BufferMode::Copy, BufferSource::Any)),
+      (#[anybuffer(detach)]) => Some(AttributeModifier::Buffer(BufferMode::Detach, BufferSource::Any)),
+      (#[arraybuffer]) => Some(AttributeModifier::Buffer(BufferMode::Default, BufferSource::ArrayBuffer)),
+      (#[arraybuffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe, BufferSource::ArrayBuffer)),
+      (#[arraybuffer(copy)]) => Some(AttributeModifier::Buffer(BufferMode::Copy, BufferSource::ArrayBuffer)),
+      (#[arraybuffer(detach)]) => Some(AttributeModifier::Buffer(BufferMode::Detach, BufferSource::ArrayBuffer)),
       (#[global]) => Some(AttributeModifier::Global),
       (#[allow ($_rule:path)]) => None,
       (#[doc = $_attr:literal]) => None,
@@ -1137,13 +1139,6 @@ fn parse_type_path(
   use ParsedType::*;
   use ParsedTypeContainer::*;
 
-  let buffer_mode = || match attrs.primary {
-    Some(
-      AttributeModifier::Buffer(mode) | AttributeModifier::ArrayBuffer(mode),
-    ) => Ok(mode),
-    _ => Err(ArgError::MissingAttribute("buffer", stringify_token(tp))),
-  };
-
   let tokens = tp.clone().into_token_stream();
   let res = if let Ok(numeric) = parse_numeric_type(&tp.path) {
     CBare(TNumeric(numeric))
@@ -1164,22 +1159,22 @@ fn parse_type_path(
         Ok(CBare(TString(Strings::CowByte)))
       }
       ( $( std :: vec ::)? Vec < $ty:path > ) => {
-        Ok(CBare(TBuffer(Buffer::Vec(parse_numeric_type(&ty)?))))
+        Ok(CBare(TBuffer(BufferType::Vec(parse_numeric_type(&ty)?))))
       }
       ( $( std :: boxed ::)? Box < [ $ty:path ] > ) => {
-        Ok(CBare(TBuffer(Buffer::BoxSlice(parse_numeric_type(&ty)?))))
+        Ok(CBare(TBuffer(BufferType::BoxSlice(parse_numeric_type(&ty)?))))
       }
       ( $( serde_v8 :: )? V8Slice < $ty:path > ) => {
-        Ok(CBare(TBuffer(Buffer::V8Slice(buffer_mode()?, parse_numeric_type(&ty)?))))
+        Ok(CBare(TBuffer(BufferType::V8Slice(parse_numeric_type(&ty)?))))
       }
       ( $( serde_v8 :: )? JsBuffer ) => {
-        Ok(CBare(TBuffer(Buffer::JsBuffer(buffer_mode()?))))
+        Ok(CBare(TBuffer(BufferType::JsBuffer)))
       }
       ( $( bytes :: )? Bytes ) => {
-        Ok(CBare(TBuffer(Buffer::Bytes(buffer_mode()?))))
+        Ok(CBare(TBuffer(BufferType::Bytes)))
       }
       ( $( bytes :: )? BytesMut ) => {
-        Ok(CBare(TBuffer(Buffer::BytesMut(buffer_mode()?))))
+        Ok(CBare(TBuffer(BufferType::BytesMut)))
       }
       ( OpState ) => Ok(CBare(TSpecial(Special::OpState))),
       ( JsRuntimeState ) => Ok(CBare(TSpecial(Special::JsRuntimeState))),
@@ -1194,7 +1189,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) | Arg::ArrayBuffer(buffer) => Ok(COption(TBuffer(buffer))),
+          Arg::Buffer(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))),
@@ -1338,8 +1333,7 @@ pub(crate) fn parse_type(
         return parse_type_state(ty);
       }
       AttributeModifier::String(_)
-      | AttributeModifier::ArrayBuffer(_)
-      | AttributeModifier::Buffer(_)
+      | AttributeModifier::Buffer(..)
       | AttributeModifier::Bigint
       | AttributeModifier::Global => {
         // We handle this as part of the normal parsing process
@@ -1411,8 +1405,7 @@ pub(crate) fn parse_type(
                 Ok(Arg::External(External::Ptr(mut_type)))
               }
               numeric => {
-                let buffer = Buffer::Slice(mut_type, numeric);
-                let res = CBare(TBuffer(buffer));
+                let res = CBare(TBuffer(BufferType::Slice(mut_type, numeric)));
                 res.validate_attributes(position, attrs, &of)?;
                 Arg::from_parsed(res, attrs).map_err(|_| {
                   ArgError::InvalidType(stringify_token(ty), "for slice")
@@ -1448,8 +1441,7 @@ pub(crate) fn parse_type(
         Type::Path(of) => match parse_numeric_type(&of.path)? {
           NumericArg::__VOID__ => Ok(Arg::External(External::Ptr(mut_type))),
           numeric => {
-            let buffer = Buffer::Ptr(mut_type, numeric);
-            let res = CBare(TBuffer(buffer));
+            let res = CBare(TBuffer(BufferType::Ptr(mut_type, numeric)));
             res.validate_attributes(position, attrs, &of)?;
             Arg::from_parsed(res, attrs).map_err(|_| {
               ArgError::InvalidType(stringify_token(ty), "for pointer")
@@ -1592,15 +1584,15 @@ mod tests {
   );
   test!(
     fn op_slices(#[buffer] r#in: &[u8], #[buffer] out: &mut [u8]);
-    (Buffer(Slice(Ref, u8)), Buffer(Slice(Mut, u8))) -> Infallible(Void)
+    (Buffer(Slice(Ref, u8), Default, TypedArray), Buffer(Slice(Mut, u8), Default, TypedArray)) -> Infallible(Void)
   );
   test!(
     fn op_pointers(#[buffer] r#in: *const u8, #[buffer] out: *mut u8);
-    (Buffer(Ptr(Ref, u8)), Buffer(Ptr(Mut, u8))) -> Infallible(Void)
+    (Buffer(Ptr(Ref, u8), Default, TypedArray), Buffer(Ptr(Mut, u8), Default, TypedArray)) -> Infallible(Void)
   );
   test!(
     fn op_arraybuffer(#[arraybuffer] r#in: &[u8]);
-    (ArrayBuffer(Slice(Ref, u8))) -> Infallible(Void)
+    (Buffer(Slice(Ref, u8), Default, ArrayBuffer)) -> Infallible(Void)
   );
   test!(
     #[serde] fn op_serde(#[serde] input: package::SerdeInputType) -> Result<package::SerdeReturnType, Error>;
@@ -1621,7 +1613,7 @@ mod tests {
   );
   test!(
     fn op_resource(#[smi] rid: ResourceId, #[buffer] buffer: &[u8]);
-    (Numeric(__SMI__, None), Buffer(Slice(Ref, u8))) ->  Infallible(Void)
+    (Numeric(__SMI__, None), Buffer(Slice(Ref, u8), Default, TypedArray)) ->  Infallible(Void)
   );
   test!(
     #[smi] fn op_resource2(#[smi] rid: ResourceId) -> Result<ResourceId, Error>;
@@ -1692,12 +1684,15 @@ 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<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)))
+    #[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), Copy, TypedArray), Buffer(BoxSlice(u8), Copy, TypedArray),
+      Buffer(Bytes, Copy, TypedArray), Buffer(V8Slice(u8), Default, TypedArray),
+      Buffer(JsBuffer, Default, TypedArray), Buffer(JsBuffer, Detach, TypedArray)) -> Infallible(Buffer(Vec(u8), Default, TypedArray))
   );
   test!(
     #[buffer] fn op_return_bytesmut() -> bytes::BytesMut;
-    () -> Infallible(Buffer(BytesMut(Default)))
+    () -> Infallible(Buffer(BytesMut, Default, TypedArray))
   );
   test!(
     async fn op_async_void();
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index ac48e31..21796b6 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -26,6 +26,10 @@
 | 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         | #[anybuffer] &mut [u8]            | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[anybuffer] &[u8]                | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
+| X         | #[anybuffer] *mut u8              | X        | ArrayBuffer, ArrayBufferView (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         | #[anybuffer] *const u8            | X        | ArrayBuffer, ArrayBufferView (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] &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.                                                |
diff --git a/serde_v8/Cargo.toml b/serde_v8/Cargo.toml
index 00cc5e1..0aca7e8 100644
--- a/serde_v8/Cargo.toml
+++ b/serde_v8/Cargo.toml
@@ -2,7 +2,7 @@
 
 [package]
 name = "serde_v8"
-version = "0.127.0"
+version = "0.128.0"
 authors.workspace = true
 edition.workspace = true
 license.workspace = true


Test patch:
diff --git a/core/modules/tests.rs b/core/modules/tests.rs
index 17d2743..fbd8075 100644
--- a/core/modules/tests.rs
+++ b/core/modules/tests.rs
@@ -573,6 +573,32 @@ async fn dyn_import_ok() {
   .await;
 }
 
+#[tokio::test]
+async fn clear_modules_works() {
+  fn count_modules(runtime: &mut JsRuntime) -> usize {
+    runtime
+      .main_realm()
+      .0
+      .module_map()
+      .borrow_mut()
+      .handles
+      .len()
+  }
+
+  let mut runtime = JsRuntime::new(Default::default());
+  futures::executor::block_on(runtime.load_main_module(
+    &Url::parse("file:///a.js").unwrap(),
+    Some(ascii_str!("")),
+  ))
+  .unwrap();
+
+  let result = futures::executor::block_on(runtime.run_event_loop(false));
+  assert!(result.is_ok());
+
+  runtime.clear_modules();
+  assert_eq!(0, count_modules(&mut runtime));
+}
+
 #[tokio::test]
 async fn dyn_import_borrow_mut_error() {
   // https://github.com/denoland/deno/issues/6054
diff --git a/core/runtime/tests/mod.rs b/core/runtime/tests/mod.rs
index daf4a26..1eb1c84 100644
--- a/core/runtime/tests/mod.rs
+++ b/core/runtime/tests/mod.rs
@@ -1,5 +1,6 @@
 use crate as deno_core;
 use crate::error::AnyError;
+use crate::CrossIsolateStore;
 use crate::JsRuntime;
 use crate::OpState;
 use crate::RuntimeOptions;
@@ -82,6 +83,7 @@ fn setup(mode: Mode) -> (JsRuntime, Arc<AtomicUsize>) {
     get_error_class_fn: Some(&|error| {
       crate::error::get_custom_error_class(error).unwrap()
     }),
+    shared_array_buffer_store: Some(CrossIsolateStore::default()),
     ..Default::default()
   });
 
diff --git a/core/runtime/tests/serialize_deserialize_test.js b/core/runtime/tests/serialize_deserialize_test.js
index 70397cd..d418511 100644
--- a/core/runtime/tests/serialize_deserialize_test.js
+++ b/core/runtime/tests/serialize_deserialize_test.js
@@ -17,6 +17,40 @@ function assertArrayEquals(a1, a2) {
   }
 }
 
+function testIssue20727() {
+  // https://github.com/denoland/deno/issues/20727
+  const ab = new ArrayBuffer(10);
+  const transferList = [ab];
+  Deno.core.serialize(
+    { ab },
+    { transferredArrayBuffers: transferList },
+  );
+
+  // check that shared_array_buffer_store is set
+  assert(typeof transferList[0] === "number");
+  const data = {
+    array1: new Uint32Array([]),
+    array2: new Float32Array([]),
+  };
+
+  const transferListTwo = [
+    data.array1.buffer,
+    data.array2.buffer,
+  ];
+  const serializedMultipleTransferredBuffers = Deno.core.ops.op_serialize(
+    { id: 2, data },
+    { transferredArrayBuffers: transferListTwo },
+  );
+
+  assert(typeof transferListTwo[0] === "number");
+  assert(typeof transferListTwo[1] === "number");
+  // should not throw
+  Deno.core.ops.op_deserialize(
+    serializedMultipleTransferredBuffers,
+    { transferredArrayBuffers: transferListTwo },
+  );
+}
+
 function main() {
   const emptyString = "";
   const emptyStringSerialized = [255, 15, 34, 0];
@@ -69,6 +103,8 @@ function main() {
     new Uint8Array(circularObjectSerialized),
   );
   assert(deserializedCircularObject.test == deserializedCircularObject);
+
+  testIssue20727();
 }
 
 main();