Repository: denoland/deno_core
Pull request #252 (2023-10-03 17:08:30)

feat(core,ops): add `*mut v8::Isolate` as a dangerous but fast arg
`op2` ops can request a `*mut v8::Isolate` which allows them to do all sorts of dangerous things, but requires an `unsafe` block to make it into something that can be used. This is a "I know what I'm doing" op2 argument so we leave it as a pointer.

Generally you'll want to use this with `nofast` to avoid the fastcall "no-js" scope.

With this patch HTTP 131k -> 133k, async ops deferred 743k ->  761k.

```
test baseline                               ... bench:         506 ns/iter (+/- 20)
test bench_op_v8_global                     ... bench:      23,064 ns/iter (+/- 2,293)
test bench_op_v8_global_scope               ... bench:      30,598 ns/iter (+/- 2,850)
test bench_op_v8_isolate_nofast             ... bench:       5,903 ns/iter (+/- 270)
test bench_op_v8_local                      ... bench:       3,232 ns/iter (+/- 580)
test bench_op_v8_local_nofast               ... bench:       6,184 ns/iter (+/- 431)
test bench_op_v8_local_scope                ... bench:      12,652 ns/iter (+/- 292)
test bench_op_v8_scope                      ... bench:      11,727 ns/iter (+/- 547)
```


Patch:
diff --git a/core/benches/ops/sync.rs b/core/benches/ops/sync.rs
index 8231e68..cf6347d 100644
--- a/core/benches/ops/sync.rs
+++ b/core/benches/ops/sync.rs
@@ -21,6 +21,8 @@ deno_core::extension!(
     op_local_nofast,
     op_global,
     op_global_scope,
+    op_scope,
+    op_isolate_nofast,
     op_make_external,
     op_bigint,
     op_bigint_return,
@@ -94,6 +96,12 @@ pub fn op_global_scope(
 ) {
 }
 
+#[op2]
+pub fn op_scope(_scope: &mut v8::HandleScope) {}
+
+#[op2(nofast)]
+pub fn op_isolate_nofast(_isolate: *mut v8::Isolate) {}
+
 #[op2(fast)]
 pub fn op_make_external() -> *const c_void {
   std::ptr::null()
@@ -405,6 +413,22 @@ fn bench_op_bigint_return(b: &mut Bencher) {
   );
 }
 
+/// A function that takes only a scope
+fn bench_op_v8_scope(b: &mut Bencher) {
+  bench_op(b, BENCH_COUNT, "op_scope", 1, "op_scope();");
+}
+
+/// A function that takes only an isolate
+fn bench_op_v8_isolate_nofast(b: &mut Bencher) {
+  bench_op(
+    b,
+    BENCH_COUNT,
+    "op_isolate_nofast",
+    1,
+    "op_isolate_nofast();",
+  );
+}
+
 fn bench_op_external(b: &mut Bencher) {
   bench_op(b, BENCH_COUNT, "op_external", 1, "op_external(EXTERNAL)");
 }
@@ -471,6 +495,8 @@ benchmark_group!(
   bench_op_v8_global_scope,
   bench_op_bigint,
   bench_op_bigint_return,
+  bench_op_v8_scope,
+  bench_op_v8_isolate_nofast,
   bench_op_external,
   bench_op_external_nofast,
   bench_op_buffer,
diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs
index d405908..b7913a7 100644
--- a/core/ops_builtin_v8.rs
+++ b/core/ops_builtin_v8.rs
@@ -47,22 +47,40 @@ pub fn op_set_promise_reject_callback<'a>(
   old.map(|v| v8::Local::new(scope, &*v))
 }
 
-#[op2(core)]
-pub fn op_run_microtasks(scope: &mut v8::HandleScope) {
-  scope.perform_microtask_checkpoint();
+// We run in a `nofast` op here so we don't get put into a `DisallowJavascriptExecutionScope` and we're
+// allowed to touch JS heap.
+#[op2(core, nofast)]
+pub fn op_queue_microtask(
+  isolate: *mut v8::Isolate,
+  cb: v8::Local<v8::Function>,
+) {
+  // SAFETY: we know v8 provides us a valid, non-null isolate pointer
+  unsafe {
+    isolate.as_mut().unwrap_unchecked().enqueue_microtask(cb);
+  }
 }
 
-#[op2(core)]
-pub fn op_has_tick_scheduled(scope: &mut v8::HandleScope) -> bool {
-  let state_rc = JsRuntime::state_from(scope);
-  let state = state_rc.borrow();
+// We run in a `nofast` op here so we don't get put into a `DisallowJavascriptExecutionScope` and we're
+// allowed to touch JS heap.
+#[op2(core, nofast)]
+pub fn op_run_microtasks(isolate: *mut v8::Isolate) {
+  // SAFETY: we know v8 provides us with a valid, non-null isolate
+  unsafe {
+    isolate
+      .as_mut()
+      .unwrap_unchecked()
+      .perform_microtask_checkpoint()
+  };
+}
+
+#[op2(core, fast)]
+pub fn op_has_tick_scheduled(state: &JsRuntimeState) -> bool {
   state.has_tick_scheduled
 }
 
-#[op2(core)]
-pub fn op_set_has_tick_scheduled(scope: &mut v8::HandleScope, v: bool) {
-  let state_rc = JsRuntime::state_from(scope);
-  state_rc.borrow_mut().has_tick_scheduled = v;
+#[op2(core, fast)]
+pub fn op_set_has_tick_scheduled(state: &mut JsRuntimeState, v: bool) {
+  state.has_tick_scheduled = v;
 }
 
 #[derive(Serialize)]
@@ -126,14 +144,6 @@ pub fn op_eval_context<'a>(
   }
 }
 
-#[op2(core)]
-pub fn op_queue_microtask(
-  scope: &mut v8::HandleScope,
-  cb: v8::Local<v8::Function>,
-) {
-  scope.enqueue_microtask(cb);
-}
-
 #[op2(core)]
 pub fn op_create_host_object<'a>(
   scope: &mut v8::HandleScope<'a>,
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 22693ee..9e67297 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -569,7 +569,6 @@ pub fn serde_v8_to_rust<'a, T: Deserialize<'a>>(
 
 /// Retrieve a [`serde_v8::V8Slice`] from a typed array in an [`v8::ArrayBufferView`].
 pub fn to_v8_slice<'a, T>(
-  scope: &mut v8::HandleScope,
   input: v8::Local<'a, v8::Value>,
 ) -> Result<serde_v8::V8Slice<T>, &'static str>
 where
@@ -580,14 +579,10 @@ where
   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 {
+      let Some(buffer) = buf.get_backing_store() else {
       return Err("buffer missing");
     };
-      (
-        buffer.get_backing_store(),
-        buf.byte_offset(),
-        buf.byte_length(),
-      )
+      (buffer, buf.byte_offset(), buf.byte_length())
     } else {
       return Err("expected typed ArrayBufferView");
     };
@@ -719,20 +714,16 @@ pub fn to_v8_slice_buffer_detachable(
 
 /// 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 offset = buf.byte_offset();
     let len = buf.byte_length();
-    let Some(buf) = buf.buffer(scope) else {
+    let Some(buf) = buf.get_backing_store() else {
       return Err("buffer missing");
     };
     return Ok(unsafe {
-      serde_v8::V8Slice::<u8>::from_parts(
-        buf.get_backing_store(),
-        offset..offset + len,
-      )
+      serde_v8::V8Slice::<u8>::from_parts(buf, offset..offset + len)
     });
   }
   if let Ok(buf) = to_v8_slice_buffer(input) {
@@ -827,6 +818,8 @@ mod tests {
       op_external_process,
       op_external_make_ptr,
       op_external_process_ptr,
+      op_isolate_queue_microtask,
+      op_isolate_run_microtasks,
 
       op_async_void,
       op_async_number,
@@ -1795,7 +1788,7 @@ mod tests {
     Ok(())
   }
 
-  #[op2(core)]
+  #[op2(core, fast)]
   pub fn op_buffer_jsbuffer(
     #[buffer] input: JsBuffer,
     #[number] inlen: usize,
@@ -2103,6 +2096,31 @@ mod tests {
     Ok(())
   }
 
+  #[op2(core, nofast)]
+  fn op_isolate_run_microtasks(isolate: *mut v8::Isolate) {
+    // SAFETY: testing
+    unsafe { isolate.as_mut().unwrap().perform_microtask_checkpoint() };
+  }
+
+  #[op2(core, nofast)]
+  fn op_isolate_queue_microtask(
+    isolate: *mut v8::Isolate,
+    cb: v8::Local<v8::Function>,
+  ) {
+    // SAFETY: testing
+    unsafe { isolate.as_mut().unwrap().enqueue_microtask(cb) };
+  }
+
+  #[tokio::test]
+  pub async fn test_isolate() -> Result<(), Box<dyn std::error::Error>> {
+    run_test2(
+      10000,
+      "op_isolate_queue_microtask,op_isolate_run_microtasks",
+      "op_isolate_queue_microtask(() => {}); op_isolate_run_microtasks();",
+    )?;
+    Ok(())
+  }
+
   #[op2(async, core)]
   async fn op_async_void() {}
 
diff --git a/ops/op2/README.md b/ops/op2/README.md
index 0d0dd91..c29ed1d 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -849,9 +849,9 @@ Safe, but forces a copy.
 ```
 
 </td><td>
-
+✅
 </td><td>
-ArrayBuffer, ArrayBufferView (resizable=false)
+ArrayBufferView (resizable=false)
 </td><td>
 ⚠️ JS may modify the contents of slices obtained from buffer.
 </td></tr>
@@ -865,7 +865,7 @@ ArrayBuffer, ArrayBufferView (resizable=false)
 </td><td>
 
 </td><td>
-ArrayBuffer, ArrayBufferView (resizable=true,false)
+ArrayBufferView (resizable=true,false)
 </td><td>
 Safe.
 </td></tr>
@@ -1009,6 +1009,20 @@ Rc<RefCell<JsRuntimeState>>
 </td><td>
 Only usable in `deno_core`.
 </td></tr>
+<tr>
+<td>
+
+```text
+*mut v8::Isolate
+```
+
+</td><td>
+✅
+</td><td>
+
+</td><td>
+⚠️ Extremely dangerous, may crash if you don't use `nofast` depending on what you do.
+</td></tr>
 </table>
 
 <!-- END ARGS -->
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 84ad64d..c4319bb 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -400,6 +400,22 @@ fn map_v8_fastcall_arg_to_arg(
         #buf
       )
     }
+    Arg::Buffer(
+      buffer @ (BufferType::V8Slice(..) | BufferType::JsBuffer),
+      _,
+      BufferSource::TypedArray,
+    ) => {
+      *needs_fast_api_callback_options = true;
+      let buf = v8slice_to_buffer(deno_core, arg_ident, &arg_temp, *buffer)?;
+      quote!(
+        let Ok(mut #arg_temp) = #deno_core::_ops::to_v8_slice(#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::ArrayBuffer) => {
       *needs_fast_api_callback_options = true;
       let buf = byte_slice_to_buffer(arg_ident, &arg_temp, *buffer)?;
@@ -429,6 +445,10 @@ fn map_v8_fastcall_arg_to_arg(
     Arg::Buffer(buffer, _, BufferSource::TypedArray) => {
       fast_api_typed_array_to_buffer(deno_core, arg_ident, arg_ident, *buffer)?
     }
+    Arg::Special(Special::Isolate) => {
+      *needs_opctx = true;
+      quote!(let #arg_ident = #opctx.isolate;)
+    }
     Arg::Ref(RefType::Ref, Special::OpState) => {
       *needs_opctx = true;
       quote!(let #arg_ident = &::std::cell::RefCell::borrow(&#opctx.state);)
@@ -549,7 +569,7 @@ fn map_arg_to_v8_fastcall_type(
       BufferType::JsBuffer | BufferType::V8Slice(..),
       _,
       BufferSource::TypedArray,
-    ) => return Ok(None),
+    ) => V8FastCallType::V8Value,
     Arg::Buffer(
       BufferType::Slice(.., NumericArg::u8)
       | BufferType::Ptr(.., NumericArg::u8),
@@ -574,6 +594,7 @@ fn map_arg_to_v8_fastcall_type(
     | Arg::RcRefCell(Special::JsRuntimeState)
     | Arg::Ref(_, Special::JsRuntimeState)
     | Arg::State(..)
+    | Arg::Special(Special::Isolate)
     | Arg::OptionState(..) => V8FastCallType::Virtual,
     // Other types + ref types are not handled
     Arg::OptionNumeric(..)
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 06cad07..217b541 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -235,10 +235,12 @@ pub fn from_arg(
     args,
     scope,
     opstate,
+    opctx,
     js_runtime_state,
     needs_scope,
     needs_isolate,
     needs_opstate,
+    needs_opctx,
     needs_js_runtime_state,
     ..
   } = &mut generator_state;
@@ -381,6 +383,10 @@ pub fn from_arg(
     Arg::External(External::Ptr(_)) => {
       from_arg_option(generator_state, &arg_ident, "external")?
     }
+    Arg::Special(Special::Isolate) => {
+      *needs_opctx = true;
+      quote!(let #arg_ident = #opctx.isolate;)
+    }
     Arg::Ref(_, Special::HandleScope) => {
       *needs_scope = true;
       quote!(let #arg_ident = &mut #scope;)
@@ -561,22 +567,22 @@ pub fn from_arg_buffer(
   let throw_exception = throw_type_error_static_string(generator_state, &err)?;
 
   let array = buffer_type.element();
-  generator_state.needs_scope = true;
 
   let to_v8_slice = if matches!(buffer_mode, BufferMode::Detach) {
-    quote!(to_v8_slice_detachable)
+    generator_state.needs_scope = true;
+    gs_quote!(generator_state(deno_core, scope) => { #deno_core::_ops::to_v8_slice_detachable::<#array>(&mut #scope, #arg_ident) })
   } else {
-    quote!(to_v8_slice)
+    gs_quote!(generator_state(deno_core) => { #deno_core::_ops::to_v8_slice::<#array>(#arg_ident) })
   };
 
-  let make_v8slice = gs_quote!(generator_state(deno_core, scope) => {
-    #temp = match unsafe { #deno_core::_ops::#to_v8_slice::<#array>(&mut #scope, #arg_ident) } {
+  let make_v8slice = quote!(
+    #temp = match unsafe { #to_v8_slice } {
       Ok(#arg_ident) => #arg_ident,
       Err(#err) => {
         #throw_exception
       }
     };
-  });
+  );
 
   let make_arg = v8slice_to_buffer(
     &generator_state.deno_core,
@@ -645,11 +651,8 @@ pub fn from_arg_any_buffer(
     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) } {
+  let make_v8slice = gs_quote!(generator_state(deno_core) => {
+    #temp = match unsafe { #deno_core::_ops::#to_v8_slice(#arg_ident) } {
       Ok(#arg_ident) => #arg_ident,
       Err(#err) => {
         #throw_exception
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 4f5758f..33a2be1 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -143,6 +143,7 @@ pub enum Special {
   OpState,
   JsRuntimeState,
   FastApiCallbackOptions,
+  Isolate,
 }
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -318,7 +319,8 @@ impl Arg {
         Special::FastApiCallbackOptions
         | Special::OpState
         | Special::JsRuntimeState
-        | Special::HandleScope,
+        | Special::HandleScope
+        | Special::Isolate,
       ) => true,
       Self::Ref(
         _,
@@ -568,7 +570,7 @@ impl ParsedTypeContainer {
           return Err(ArgError::InvalidAttributeType(
             attr.name(),
             stringify_token(tp),
-          ))
+          ));
         }
       },
       Some(attr) => {
@@ -1128,12 +1130,19 @@ fn parse_numeric_type(tp: &Path) -> Result<NumericArg, ArgError> {
   Ok(res)
 }
 
+#[derive(Copy, Clone, Eq, PartialEq)]
+enum TypePathContext {
+  None,
+  Ref,
+  Ptr,
+}
+
 /// Parse a raw type into a container + type, allowing us to simplify the typechecks elsewhere in
 /// this code.
 fn parse_type_path(
   position: Position,
   attrs: Attributes,
-  is_ref: bool,
+  ctx: TypePathContext,
   tp: &TypePath,
 ) -> Result<ParsedTypeContainer, ArgError> {
   use ParsedType::*;
@@ -1178,6 +1187,7 @@ fn parse_type_path(
       }
       ( OpState ) => Ok(CBare(TSpecial(Special::OpState))),
       ( JsRuntimeState ) => Ok(CBare(TSpecial(Special::JsRuntimeState))),
+      ( v8 :: Isolate ) => Ok(CBare(TSpecial(Special::Isolate))),
       ( v8 :: HandleScope $( < $_scope:lifetime >)? ) => Ok(CBare(TSpecial(Special::HandleScope))),
       ( v8 :: FastApiCallbackOptions ) => Ok(CBare(TSpecial(Special::FastApiCallbackOptions))),
       ( v8 :: Local < $( $_scope:lifetime , )? v8 :: $v8:ident >) => Ok(CV8Local(TV8(parse_v8_type(&v8)?))),
@@ -1214,23 +1224,26 @@ fn parse_type_path(
   // the easiest way to work with the 'rules!' macro above.
   match res {
     // OpState and JsRuntimeState appears in both ways
-    CBare(TSpecial(Special::OpState)) => {}
-    CBare(TSpecial(Special::JsRuntimeState)) => {}
+    CBare(TSpecial(Special::OpState | Special::JsRuntimeState)) => {}
     CBare(
       TString(Strings::RefStr) | TSpecial(Special::HandleScope) | TV8(_),
     ) => {
-      if !is_ref {
+      if ctx != TypePathContext::Ref {
         return Err(ArgError::MissingReference(stringify_token(tp)));
       }
     }
     _ => {
-      if is_ref {
+      if ctx == TypePathContext::Ref {
         return Err(ArgError::InvalidReference(stringify_token(tp)));
       }
     }
   }
 
-  res.validate_attributes(position, attrs, &tp)?;
+  // TODO(mmastrac): this is a bit awkward, but we need to modify the type container here
+  // if this is going to work any other way
+  if ctx != TypePathContext::Ptr {
+    res.validate_attributes(position, attrs, &tp)?;
+  }
 
   Ok(res)
 }
@@ -1296,14 +1309,26 @@ pub(crate) fn parse_type(
         }
         Type::Path(of) => {
           // If this type will parse without #[serde] (or with #[string]), it is illegal to use this type with #[serde]
-          if parse_type_path(position, Attributes::default(), false, of).is_ok()
+          if parse_type_path(
+            position,
+            Attributes::default(),
+            TypePathContext::None,
+            of,
+          )
+          .is_ok()
           {
             return Err(ArgError::InvalidSerdeAttributeType(stringify_token(
               ty,
             )));
           }
           // If this type will parse without #[serde] (or with #[string]), it is illegal to use this type with #[serde]
-          if parse_type_path(position, Attributes::string(), false, of).is_ok()
+          if parse_type_path(
+            position,
+            Attributes::string(),
+            TypePathContext::None,
+            of,
+          )
+          .is_ok()
           {
             return Err(ArgError::InvalidSerdeAttributeType(stringify_token(
               ty,
@@ -1339,25 +1364,27 @@ pub(crate) fn parse_type(
         // We handle this as part of the normal parsing process
       }
       AttributeModifier::Number => match ty {
-        Type::Path(of) => match parse_type_path(position, attrs, false, of)? {
-          COption(TNumeric(
-            n @ (NumericArg::u64
-            | NumericArg::usize
-            | NumericArg::i64
-            | NumericArg::isize),
-          )) => return Ok(Arg::OptionNumeric(n, NumericFlag::Number)),
-          CBare(TNumeric(
-            n @ (NumericArg::u64
-            | NumericArg::usize
-            | NumericArg::i64
-            | NumericArg::isize),
-          )) => return Ok(Arg::Numeric(n, NumericFlag::Number)),
-          _ => {
-            return Err(ArgError::InvalidNumberAttributeType(stringify_token(
-              ty,
-            )))
+        Type::Path(of) => {
+          match parse_type_path(position, attrs, TypePathContext::None, of)? {
+            COption(TNumeric(
+              n @ (NumericArg::u64
+              | NumericArg::usize
+              | NumericArg::i64
+              | NumericArg::isize),
+            )) => return Ok(Arg::OptionNumeric(n, NumericFlag::Number)),
+            CBare(TNumeric(
+              n @ (NumericArg::u64
+              | NumericArg::usize
+              | NumericArg::i64
+              | NumericArg::isize),
+            )) => return Ok(Arg::Numeric(n, NumericFlag::Number)),
+            _ => {
+              return Err(ArgError::InvalidNumberAttributeType(
+                stringify_token(ty),
+              ))
+            }
           }
-        },
+        }
         _ => {
           return Err(ArgError::InvalidNumberAttributeType(stringify_token(ty)))
         }
@@ -1416,18 +1443,20 @@ pub(crate) fn parse_type(
             Err(ArgError::InvalidType(stringify_token(ty), "for slice"))
           }
         }
-        Type::Path(of) => match parse_type_path(position, attrs, true, of)? {
-          CBare(TString(Strings::RefStr)) => Ok(Arg::String(Strings::RefStr)),
-          COption(TString(Strings::RefStr)) => {
-            Ok(Arg::OptionString(Strings::RefStr))
+        Type::Path(of) => {
+          match parse_type_path(position, attrs, TypePathContext::Ref, of)? {
+            CBare(TString(Strings::RefStr)) => Ok(Arg::String(Strings::RefStr)),
+            COption(TString(Strings::RefStr)) => {
+              Ok(Arg::OptionString(Strings::RefStr))
+            }
+            CBare(TV8(v8)) => Ok(Arg::V8Ref(mut_type, v8)),
+            CBare(TSpecial(special)) => Ok(Arg::Ref(mut_type, special)),
+            _ => Err(ArgError::InvalidType(
+              stringify_token(ty),
+              "for reference path",
+            )),
           }
-          CBare(TV8(v8)) => Ok(Arg::V8Ref(mut_type, v8)),
-          CBare(TSpecial(special)) => Ok(Arg::Ref(mut_type, special)),
-          _ => Err(ArgError::InvalidType(
-            stringify_token(ty),
-            "for reference path",
-          )),
-        },
+        }
         _ => Err(ArgError::InvalidType(stringify_token(ty), "for reference")),
       }
     }
@@ -1438,23 +1467,38 @@ pub(crate) fn parse_type(
         RefType::Ref
       };
       match &*of.elem {
-        Type::Path(of) => match parse_numeric_type(&of.path)? {
-          NumericArg::__VOID__ => Ok(Arg::External(External::Ptr(mut_type))),
-          numeric => {
-            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")
-            })
+        Type::Path(of) => {
+          match parse_type_path(position, attrs, TypePathContext::Ptr, of)? {
+            CBare(TNumeric(NumericArg::__VOID__)) => {
+              Ok(Arg::External(External::Ptr(mut_type)))
+            }
+            CBare(TNumeric(numeric)) => {
+              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 numeric pointer",
+                )
+              })
+            }
+            CBare(TSpecial(Special::Isolate)) => {
+              Ok(Arg::Special(Special::Isolate))
+            }
+            _ => Err(ArgError::InvalidType(
+              stringify_token(of),
+              "for pointer to type path",
+            )),
           }
-        },
+        }
         _ => Err(ArgError::InvalidType(stringify_token(ty), "for pointer")),
       }
     }
-    Type::Path(of) => {
-      Arg::from_parsed(parse_type_path(position, attrs, false, of)?, attrs)
-        .map_err(|_| ArgError::InvalidType(stringify_token(ty), "for path"))
-    }
+    Type::Path(of) => Arg::from_parsed(
+      parse_type_path(position, attrs, TypePathContext::None, of)?,
+      attrs,
+    )
+    .map_err(|_| ArgError::InvalidType(stringify_token(ty), "for path")),
     _ => Err(ArgError::InvalidType(
       stringify_token(ty),
       "for top-level type",
@@ -1722,6 +1766,10 @@ mod tests {
     fn op_js_runtime_state_rc(state: Rc<RefCell<JsRuntimeState>>);
     (RcRefCell(JsRuntimeState)) -> Infallible(Void)
   );
+  test!(
+    fn op_isolate(isolate: *mut v8::Isolate);
+    (Special(Isolate)) -> Infallible(Void)
+  );
   // Args
 
   expect_fail!(
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index 21796b6..c901ca4 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -48,13 +48,13 @@
 | X         | #[buffer] &[u32]                  | X        | UInt32Array (resizable=true,false)                  | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly.                                                                                                                                        |
 | X         | #[buffer(copy)] Vec<u32>          | X        | UInt32Array (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
 | X         | #[buffer(copy)] Box<[u32]>        | X        | UInt32Array (resizable=true,false)                  | Safe, but forces a copy.                                                                                                                                                                                       |
-|           | #[buffer] V8Slice                 | X        | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
-|           | #[buffer(detach)] V8Slice         | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
-|           | #[buffer] V8ResizableSlice        | X        | ArrayBuffer, ArrayBufferView (resizable=true)       | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
-| X         | #[buffer] JsBuffer                |          | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
-| X         | #[buffer(detach)] JsBuffer        |          | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
-|           | #[buffer(unsafe)] bytes::Bytes    | X        | ArrayBuffer, ArrayBufferView (resizable=false)      | ⚠️ JS may modify the contents of the buffer.                                                                                                                                                                    |
-|           | #[buffer(detach)] bytes::Bytes    | X        | ArrayBuffer, ArrayBufferView (resizable=true,false) | Safe.                                                                                                                                                                                                          |
+|           | #[buffer] V8Slice                 | X        | ArrayBufferView (resizable=false)                   | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
+|           | #[buffer(detach)] V8Slice         | X        | ArrayBufferView (resizable=true,false)              | Safe.                                                                                                                                                                                                          |
+|           | #[buffer] V8ResizableSlice        | X        | ArrayBufferView (resizable=true)                    | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
+| X         | #[buffer] JsBuffer                | X        | ArrayBufferView (resizable=false)                   | ⚠️ JS may modify the contents of slices obtained from buffer.                                                                                                                                                   |
+| X         | #[buffer(detach)] JsBuffer        |          | ArrayBufferView (resizable=true,false)              | Safe.                                                                                                                                                                                                          |
+|           | #[buffer(unsafe)] bytes::Bytes    | X        | ArrayBufferView (resizable=false)                   | ⚠️ JS may modify the contents of the buffer.                                                                                                                                                                    |
+|           | #[buffer(detach)] bytes::Bytes    | X        | ArrayBufferView (resizable=true,false)              | Safe.                                                                                                                                                                                                          |
 | X         | *const std::ffi::c_void           | X        | External                                            |                                                                                                                                                                                                                |
 | X         | *mut std::ffi::c_void             | X        | External                                            |                                                                                                                                                                                                                |
 | X         | &OpState                          | X        |                                                     |                                                                                                                                                                                                                |
@@ -65,3 +65,4 @@
 | X         | &JsRuntimeState                   | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
 | X         | &mut JsRuntimeState               | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
 | X         | Rc<RefCell<JsRuntimeState>>       | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
+| X         | *mut v8::Isolate                  | X        |                                                     | ⚠️ Extremely dangerous, may crash if you don't use `nofast` depending on what you do.                                                                                                                           |


Test patch:
diff --git a/ops/op2/test_cases/async/async_jsbuffer.out b/ops/op2/test_cases/async/async_jsbuffer.out
index 26ce082..ec5e32c 100644
--- a/ops/op2/test_cases/async/async_jsbuffer.out
+++ b/ops/op2/test_cases/async/async_jsbuffer.out
@@ -43,9 +43,7 @@ impl op_async_v8_buffer {
         let result = {
             let arg0 = args.get(1usize as i32);
             let mut arg0_temp;
-            arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
-            } {
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg0) } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
diff --git a/ops/op2/test_cases/sync/buffers.out b/ops/op2/test_cases/sync/buffers.out
index cae5419..97552f1 100644
--- a/ops/op2/test_cases/sync/buffers.out
+++ b/ops/op2/test_cases/sync/buffers.out
@@ -128,7 +128,6 @@ impl op_buffers {
         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
         });
@@ -138,9 +137,7 @@ impl op_buffers {
         let result = {
             let arg0 = args.get(0usize as i32);
             let mut arg0_temp;
-            arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
-            } {
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg0) } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
@@ -158,11 +155,10 @@ impl op_buffers {
             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::<u8>(&mut scope, arg1)
-            } {
+            arg1_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg1) } {
                 Ok(arg1) => arg1,
                 Err(arg1_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg1_err.as_bytes(),
@@ -177,11 +173,10 @@ impl op_buffers {
             let arg1 = arg1_temp.as_mut();
             let arg2 = args.get(2usize as i32);
             let mut arg2_temp;
-            arg2_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg2)
-            } {
+            arg2_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg2) } {
                 Ok(arg2) => arg2,
                 Err(arg2_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg2_err.as_bytes(),
@@ -200,11 +195,10 @@ impl op_buffers {
             };
             let arg3 = args.get(3usize as i32);
             let mut arg3_temp;
-            arg3_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg3)
-            } {
+            arg3_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg3) } {
                 Ok(arg3) => arg3,
                 Err(arg3_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg3_err.as_bytes(),
@@ -377,7 +371,6 @@ impl op_buffers_32 {
         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
         });
@@ -387,9 +380,7 @@ impl op_buffers_32 {
         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)
-            } {
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u32>(arg0) } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
@@ -407,11 +398,10 @@ impl op_buffers_32 {
             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)
-            } {
+            arg1_temp = match unsafe { deno_core::_ops::to_v8_slice::<u32>(arg1) } {
                 Ok(arg1) => arg1,
                 Err(arg1_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg1_err.as_bytes(),
@@ -426,11 +416,10 @@ impl op_buffers_32 {
             let arg1 = arg1_temp.as_mut();
             let arg2 = args.get(2usize as i32);
             let mut arg2_temp;
-            arg2_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u32>(&mut scope, arg2)
-            } {
+            arg2_temp = match unsafe { deno_core::_ops::to_v8_slice::<u32>(arg2) } {
                 Ok(arg2) => arg2,
                 Err(arg2_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg2_err.as_bytes(),
@@ -449,11 +438,10 @@ impl op_buffers_32 {
             };
             let arg3 = args.get(3usize as i32);
             let mut arg3_temp;
-            arg3_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u32>(&mut scope, arg3)
-            } {
+            arg3_temp = match unsafe { deno_core::_ops::to_v8_slice::<u32>(arg3) } {
                 Ok(arg3) => arg3,
                 Err(arg3_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg3_err.as_bytes(),
@@ -521,7 +509,6 @@ impl op_buffers_option {
         <Self as deno_core::_ops::Op>::DECL
     }
     extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
-        let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -534,9 +521,7 @@ impl op_buffers_option {
             let arg0 = if arg0.is_null_or_undefined() {
                 None
             } else {
-                arg0_temp = match unsafe {
-                    deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
-                } {
+                arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg0) } {
                     Ok(arg0) => arg0,
                     Err(arg0_err) => {
                         let mut scope = unsafe {
@@ -561,11 +546,12 @@ impl op_buffers_option {
             let arg1 = if arg1.is_null_or_undefined() {
                 None
             } else {
-                arg1_temp = match unsafe {
-                    deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg1)
-                } {
+                arg1_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg1) } {
                     Ok(arg1) => arg1,
                     Err(arg1_err) => {
+                        let mut scope = unsafe {
+                            deno_core::v8::CallbackScope::new(&*info)
+                        };
                         let msg = deno_core::v8::String::new_from_one_byte(
                                 &mut scope,
                                 arg1_err.as_bytes(),
diff --git a/ops/op2/test_cases/sync/buffers_copy.out b/ops/op2/test_cases/sync/buffers_copy.out
index d6bec48..3de1b02 100644
--- a/ops/op2/test_cases/sync/buffers_copy.out
+++ b/ops/op2/test_cases/sync/buffers_copy.out
@@ -109,7 +109,6 @@ impl op_buffers {
         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
         });
@@ -119,9 +118,7 @@ impl op_buffers {
         let result = {
             let arg0 = args.get(0usize as i32);
             let mut arg0_temp;
-            arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
-            } {
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg0) } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
@@ -139,11 +136,10 @@ impl op_buffers {
             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::<u8>(&mut scope, arg1)
-            } {
+            arg1_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg1) } {
                 Ok(arg1) => arg1,
                 Err(arg1_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg1_err.as_bytes(),
@@ -158,11 +154,10 @@ impl op_buffers {
             let arg1 = arg1_temp.to_boxed_slice();
             let arg2 = args.get(2usize as i32);
             let mut arg2_temp;
-            arg2_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg2)
-            } {
+            arg2_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg2) } {
                 Ok(arg2) => arg2,
                 Err(arg2_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg2_err.as_bytes(),
@@ -301,7 +296,6 @@ impl op_buffers_32 {
         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
         });
@@ -311,9 +305,7 @@ impl op_buffers_32 {
         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)
-            } {
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u32>(arg0) } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
@@ -331,11 +323,10 @@ impl op_buffers_32 {
             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)
-            } {
+            arg1_temp = match unsafe { deno_core::_ops::to_v8_slice::<u32>(arg1) } {
                 Ok(arg1) => arg1,
                 Err(arg1_err) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
                     let msg = deno_core::v8::String::new_from_one_byte(
                             &mut scope,
                             arg1_err.as_bytes(),
diff --git a/ops/op2/test_cases/sync/buffers_out.out b/ops/op2/test_cases/sync/buffers_out.out
index 9c32a2e..6147fce 100644
--- a/ops/op2/test_cases/sync/buffers_out.out
+++ b/ops/op2/test_cases/sync/buffers_out.out
@@ -33,9 +33,7 @@ impl op_buffers {
         let result = {
             let arg0 = args.get(0usize as i32);
             let mut arg0_temp;
-            arg0_temp = match unsafe {
-                deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
-            } {
+            arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg0) } {
                 Ok(arg0) => arg0,
                 Err(arg0_err) => {
                     let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
@@ -129,9 +127,7 @@ impl op_buffers_option {
             let arg0 = if arg0.is_null_or_undefined() {
                 None
             } else {
-                arg0_temp = match unsafe {
-                    deno_core::_ops::to_v8_slice::<u8>(&mut scope, arg0)
-                } {
+                arg0_temp = match unsafe { deno_core::_ops::to_v8_slice::<u8>(arg0) } {
                     Ok(arg0) => arg0,
                     Err(arg0_err) => {
                         let mut scope = unsafe {