Repository: denoland/deno_core
Pull request #214 (2023-09-19 13:28:57)

feat(op2): support JsRuntimeState as op parameter
Add support for accessing `JsRuntimeState` in ops. This is supported both in
fast and slow paths of ops.


Patch:
diff --git a/.gitignore b/.gitignore
index a8738ea..9df3e84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,9 +19,10 @@ gclient_config.py_entries
 .DS_Store
 .DS_Store?
 
-# Flamegraphs
+# Flamegraphs & profiles
 /flamebench*.svg
 /flamegraph*.svg
+profile.json
 
 # WPT generated cert files
 /tools/wpt/certs/index.txt*
diff --git a/ops/op2/README.md b/ops/op2/README.md
index a48dce1..c46cfb7 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -785,6 +785,48 @@ Extracts an object from `OpState`.
 </td><td>
 Extracts an object from `OpState`.
 </td></tr>
+<tr>
+<td>
+
+```text
+&JsRuntimeState
+```
+
+</td><td>
+✅
+</td><td>
+
+</td><td>
+Only usable in `deno_core`.
+</td></tr>
+<tr>
+<td>
+
+```text
+&mut JsRuntimeState
+```
+
+</td><td>
+✅
+</td><td>
+
+</td><td>
+Only usable in `deno_core`.
+</td></tr>
+<tr>
+<td>
+
+```text
+Rc<RefCell<JsRuntimeState>>
+```
+
+</td><td>
+✅
+</td><td>
+
+</td><td>
+Only usable in `deno_core`.
+</td></tr>
 </table>
 
 <!-- END ARGS -->
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index da74e14..efeb435 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -160,17 +160,6 @@ pub fn generate_dispatch_fast(
     Some(rv) => rv,
   };
 
-  let GeneratorState {
-    fast_function,
-    deno_core,
-    result,
-    opctx,
-    fast_api_callback_options,
-    needs_fast_api_callback_options,
-    needs_fast_opctx,
-    ..
-  } = generator_state;
-
   // Collect the names and types for the fastcall and the underlying op call
   let mut fastcall_names = vec![];
   let mut fastcall_types = vec![];
@@ -181,22 +170,27 @@ pub fn generate_dispatch_fast(
     let name = format_ident!("arg{i}");
     if let Input::Concrete(fv) = input {
       fastcall_names.push(name.clone());
-      fastcall_types.push(fv.quote_rust_type(deno_core));
+      fastcall_types.push(fv.quote_rust_type(&generator_state.deno_core));
       input_types.push(fv.quote_type());
     }
 
     call_names.push(name.clone());
-    call_args.push(map_v8_fastcall_arg_to_arg(
-      deno_core,
-      opctx,
-      fast_api_callback_options,
-      needs_fast_opctx,
-      needs_fast_api_callback_options,
-      &name,
-      arg,
-    )?)
+    call_args.push(map_v8_fastcall_arg_to_arg(generator_state, &name, arg)?)
   }
 
+  let GeneratorState {
+    deno_core,
+    result,
+    fast_function,
+    opctx,
+    js_runtime_state,
+    fast_api_callback_options,
+    needs_fast_opctx,
+    needs_fast_api_callback_options,
+    needs_fast_js_runtime_state,
+    ..
+  } = generator_state;
+
   let handle_error = match signature.ret_val {
     RetVal::Infallible(_) => quote!(),
     RetVal::Result(_) => {
@@ -233,6 +227,15 @@ pub fn generate_dispatch_fast(
     _ => todo!(),
   };
 
+  let with_js_runtime_state = if *needs_fast_js_runtime_state {
+    *needs_fast_opctx = true;
+    quote! {
+      let #js_runtime_state = std::rc::Weak::upgrade(&#opctx.runtime_state).unwrap();
+    }
+  } else {
+    quote!()
+  };
+
   let with_opctx = if *needs_fast_opctx {
     *needs_fast_api_callback_options = true;
     quote!(
@@ -282,6 +285,7 @@ pub fn generate_dispatch_fast(
     ) -> #output_type {
       #with_fast_api_callback_options
       #with_opctx
+      #with_js_runtime_state
       let #result = {
         #(#call_args)*
         Self::call(#(#call_names),*)
@@ -295,15 +299,23 @@ pub fn generate_dispatch_fast(
   Ok(Some((fast_definition, fast_fn)))
 }
 
+#[allow(clippy::too_many_arguments)]
 fn map_v8_fastcall_arg_to_arg(
-  deno_core: &TokenStream,
-  opctx: &Ident,
-  fast_api_callback_options: &Ident,
-  needs_opctx: &mut bool,
-  needs_fast_api_callback_options: &mut bool,
+  generator_state: &mut GeneratorState,
   arg_ident: &Ident,
   arg: &Arg,
 ) -> Result<TokenStream, V8MappingError> {
+  let GeneratorState {
+    deno_core,
+    opctx,
+    js_runtime_state,
+    fast_api_callback_options,
+    needs_fast_opctx: needs_opctx,
+    needs_fast_api_callback_options,
+    needs_fast_js_runtime_state: needs_js_runtime_state,
+    ..
+  } = generator_state;
+
   let arg_temp = format_ident!("{}_temp", arg_ident);
   let res = match arg {
     Arg::Buffer(Buffer::Slice(_, NumericArg::u8 | NumericArg::u32)) => {
@@ -330,6 +342,18 @@ fn map_v8_fastcall_arg_to_arg(
       *needs_opctx = true;
       quote!(let #arg_ident = #opctx.state.clone();)
     }
+    Arg::Ref(RefType::Ref, Special::JsRuntimeState) => {
+      *needs_js_runtime_state = true;
+      quote!(let #arg_ident = &#js_runtime_state.borrow();)
+    }
+    Arg::Ref(RefType::Mut, Special::JsRuntimeState) => {
+      *needs_js_runtime_state = true;
+      quote!(let #arg_ident = &mut #js_runtime_state.borrow_mut();)
+    }
+    Arg::RcRefCell(Special::JsRuntimeState) => {
+      *needs_js_runtime_state = true;
+      quote!(let #arg_ident = #js_runtime_state.clone();)
+    }
     Arg::State(RefType::Ref, state) => {
       *needs_opctx = true;
       let state =
@@ -433,6 +457,8 @@ fn map_arg_to_v8_fastcall_type(
     // Virtual OpState arguments
     Arg::RcRefCell(Special::OpState)
     | Arg::Ref(_, Special::OpState)
+    | Arg::RcRefCell(Special::JsRuntimeState)
+    | Arg::Ref(_, Special::JsRuntimeState)
     | Arg::State(..)
     | Arg::OptionState(..) => V8FastCallType::Virtual,
     // Other types + ref types are not handled
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 6da5c86..9c4e39e 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -94,6 +94,12 @@ pub(crate) fn generate_dispatch_slow(
     quote!()
   };
 
+  let with_js_runtime_state = if generator_state.needs_js_runtime_state {
+    with_js_runtime_state(generator_state)
+  } else {
+    quote!()
+  };
+
   let with_opctx = if generator_state.needs_opctx {
     with_opctx(generator_state)
   } else {
@@ -121,6 +127,7 @@ pub(crate) fn generate_dispatch_slow(
         #with_opctx
         #with_isolate
         #with_opstate
+        #with_js_runtime_state
 
         #output
       }
@@ -176,6 +183,15 @@ pub(crate) fn with_opstate(
   )
 }
 
+pub(crate) fn with_js_runtime_state(
+  generator_state: &mut GeneratorState,
+) -> TokenStream {
+  generator_state.needs_opctx = true;
+  gs_quote!(generator_state(opctx, js_runtime_state) =>
+    (let #js_runtime_state = std::rc::Weak::upgrade(&#opctx.runtime_state).unwrap();)
+  )
+}
+
 pub fn extract_arg(
   generator_state: &mut GeneratorState,
   index: usize,
@@ -199,9 +215,11 @@ pub fn from_arg(
     args,
     scope,
     opstate,
+    js_runtime_state,
     needs_scope,
     needs_isolate,
     needs_opstate,
+    needs_js_runtime_state,
     ..
   } = &mut generator_state;
   let arg_ident = args
@@ -345,6 +363,18 @@ pub fn from_arg(
       *needs_opstate = true;
       quote!(let #arg_ident = #opstate.clone();)
     }
+    Arg::Ref(RefType::Ref, Special::JsRuntimeState) => {
+      *needs_js_runtime_state = true;
+      quote!(let #arg_ident = &#js_runtime_state.borrow();)
+    }
+    Arg::Ref(RefType::Mut, Special::JsRuntimeState) => {
+      *needs_js_runtime_state = true;
+      quote!(let #arg_ident = &mut #js_runtime_state.borrow_mut();)
+    }
+    Arg::RcRefCell(Special::JsRuntimeState) => {
+      *needs_js_runtime_state = true;
+      quote!(let #arg_ident = #js_runtime_state.clone();)
+    }
     Arg::State(RefType::Ref, state) => {
       *needs_opstate = true;
       let state =
diff --git a/ops/op2/generator_state.rs b/ops/op2/generator_state.rs
index 796c633..d904b89 100644
--- a/ops/op2/generator_state.rs
+++ b/ops/op2/generator_state.rs
@@ -23,6 +23,8 @@ pub struct GeneratorState {
   pub opctx: Ident,
   /// The `OpState` used for storing op state.
   pub opstate: Ident,
+  /// The `JsRuntimeState` used for storing the Rc<RefCell<JsRuntimeState>>.
+  pub js_runtime_state: Ident,
   /// The `FastApiCallbackOptions` used in fast calls for fallback returns.
   pub fast_api_callback_options: Ident,
   /// The `v8::ReturnValue` used in the slow function
@@ -40,8 +42,10 @@ pub struct GeneratorState {
   pub needs_isolate: bool,
   pub needs_opstate: bool,
   pub needs_opctx: bool,
+  pub needs_js_runtime_state: bool,
   pub needs_fast_opctx: bool,
   pub needs_fast_api_callback_options: bool,
+  pub needs_fast_js_runtime_state: bool,
 }
 
 /// Quotes a set of generator_state fields, along with variables captured from
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 1d94d50..e39f172 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -130,6 +130,7 @@ fn generate_op2(
   let info = Ident::new("info", Span::call_site());
   let opctx = Ident::new("opctx", Span::call_site());
   let opstate = Ident::new("opstate", Span::call_site());
+  let js_runtime_state = Ident::new("js_runtime_state", Span::call_site());
   let promise_id = Ident::new("promise_id", Span::call_site());
   let slow_function = Ident::new("v8_fn_ptr", Span::call_site());
   let fast_function = Ident::new("v8_fn_ptr_fast", Span::call_site());
@@ -152,6 +153,7 @@ fn generate_op2(
     info,
     opctx,
     opstate,
+    js_runtime_state,
     fast_api_callback_options,
     deno_core,
     result,
@@ -165,8 +167,10 @@ fn generate_op2(
     needs_isolate: false,
     needs_opctx: false,
     needs_opstate: false,
+    needs_js_runtime_state: false,
     needs_fast_opctx: false,
     needs_fast_api_callback_options: false,
+    needs_fast_js_runtime_state: false,
   };
 
   let name = func.sig.ident;
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index dff123c..0e39456 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -141,6 +141,7 @@ impl ToTokens for V8Arg {
 pub enum Special {
   HandleScope,
   OpState,
+  JsRuntimeState,
   FastApiCallbackOptions,
 }
 
@@ -262,17 +263,20 @@ impl Arg {
       Self::Special(
         Special::FastApiCallbackOptions
         | Special::OpState
+        | Special::JsRuntimeState
         | Special::HandleScope,
       ) => true,
       Self::Ref(
         _,
         Special::FastApiCallbackOptions
         | Special::OpState
+        | Special::JsRuntimeState
         | Special::HandleScope,
       ) => true,
       Self::RcRefCell(
         Special::FastApiCallbackOptions
         | Special::OpState
+        | Special::JsRuntimeState
         | Special::HandleScope,
       ) => true,
       Self::State(..) | Self::OptionState(..) => true,
@@ -658,6 +662,8 @@ pub enum SignatureError {
   InvalidWherePredicate(String),
   #[error("State may be either a single OpState parameter, one mutable #[state], or multiple immultiple #[state]s")]
   InvalidOpStateCombination,
+  #[error("JsRuntimeState may only be used in one parameter")]
+  InvalidMultipleJsRuntimeState,
 }
 
 #[derive(Error, Debug)]
@@ -783,6 +789,8 @@ pub fn parse_signature(
   let mut has_mut_state = false;
   let mut has_ref_state = false;
 
+  let mut jsruntimestate_count = 0;
+
   for arg in &args {
     match arg {
       Arg::RcRefCell(Special::OpState) | Arg::Ref(_, Special::OpState) => {
@@ -797,6 +805,12 @@ pub fn parse_signature(
         }
         has_mut_state = true;
       }
+      Arg::Ref(_, Special::JsRuntimeState) => {
+        jsruntimestate_count += 1;
+      }
+      Arg::RcRefCell(Special::JsRuntimeState) => {
+        jsruntimestate_count += 1;
+      }
       _ => {}
     }
   }
@@ -806,6 +820,11 @@ pub fn parse_signature(
     return Err(SignatureError::InvalidOpStateCombination);
   }
 
+  // Ensure that there is at most one JsRuntimeState
+  if jsruntimestate_count > 1 {
+    return Err(SignatureError::InvalidMultipleJsRuntimeState);
+  }
+
   Ok(ParsedSignature {
     args,
     names,
@@ -1082,6 +1101,7 @@ fn parse_type_path(
         Ok(CBare(TBuffer(Buffer::BytesMut(buffer_mode()?))))
       }
       ( OpState ) => Ok(CBare(TSpecial(Special::OpState))),
+      ( JsRuntimeState ) => Ok(CBare(TSpecial(Special::JsRuntimeState))),
       ( 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)?))),
@@ -1109,8 +1129,9 @@ fn parse_type_path(
   // Ensure that we have the correct reference state. This is a bit awkward but it's
   // the easiest way to work with the 'rules!' macro above.
   match res {
-    // OpState appears in both ways
+    // OpState and JsRuntimeState appears in both ways
     CBare(TSpecial(Special::OpState)) => {}
+    CBare(TSpecial(Special::JsRuntimeState)) => {}
     CBare(
       TString(Strings::RefStr) | TSpecial(Special::HandleScope) | TV8(_),
     ) => {
@@ -1604,6 +1625,18 @@ mod tests {
     fn op_async_result_impl_void() -> Result<impl Future<Output = ()>, Error>;
     () -> ResultFuture(Void)
   );
+  test!(
+    fn op_js_runtime_state_ref(state: &JsRuntimeState);
+    (Ref(Ref, JsRuntimeState)) -> Infallible(Void)
+  );
+  test!(
+    fn op_js_runtime_state_mut(state: &mut JsRuntimeState);
+    (Ref(Mut, JsRuntimeState)) -> Infallible(Void)
+  );
+  test!(
+    fn op_js_runtime_state_rc(state: Rc<RefCell<JsRuntimeState>>);
+    (RcRefCell(JsRuntimeState)) -> Infallible(Void)
+  );
   // Args
 
   expect_fail!(
@@ -1652,6 +1685,11 @@ mod tests {
     ArgError("l", InvalidAttributeType("global", "v8::Local<v8::String>")),
     fn f(#[global] l: v8::Local<v8::String>) {}
   );
+  expect_fail!(
+    op_duplicate_js_runtime_state,
+    InvalidMultipleJsRuntimeState,
+    fn f(s1: &JsRuntimeState, s2: &mut JsRuntimeState) {}
+  );
 
   // Generics
 
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index fd66ccb..ae5a5c7 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -49,3 +49,6 @@
 | X         | Rc<RefCell<OpState>>             | X        |                                                     |                                                                                                                                                                                                                |
 | X         | #[state] &StateObject            | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
 | X         | #[state] &mut StateObject        | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
+| X         | &JsRuntimeState                  | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
+| X         | &mut JsRuntimeState              | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
+| X         | Rc<RefCell<JsRuntimeState>>      | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |


Test patch:
diff --git a/ops/op2/test_cases/sync/op_state_ref.out b/ops/op2/test_cases/sync/op_state_ref.out
index f697839..da3326b 100644
--- a/ops/op2/test_cases/sync/op_state_ref.out
+++ b/ops/op2/test_cases/sync/op_state_ref.out
@@ -1,11 +1,11 @@
 #[allow(non_camel_case_types)]
-struct op_state_rc {
+struct op_state_ref {
     _unconstructable: ::std::marker::PhantomData<()>,
 }
-impl deno_core::_ops::Op for op_state_rc {
-    const NAME: &'static str = stringify!(op_state_rc);
+impl deno_core::_ops::Op for op_state_ref {
+    const NAME: &'static str = stringify!(op_state_ref);
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
-        stringify!(op_state_rc),
+        stringify!(op_state_ref),
         false,
         false,
         false,
@@ -22,9 +22,9 @@ impl deno_core::_ops::Op for op_state_rc {
         }),
     );
 }
-impl op_state_rc {
+impl op_state_ref {
     pub const fn name() -> &'static str {
-        stringify!(op_state_rc)
+        stringify!(op_state_ref)
     }
     #[deprecated(note = "Use the const op::DECL instead")]
     pub const fn decl() -> deno_core::_ops::OpDecl {
diff --git a/ops/op2/test_cases/sync/op_state_ref.rs b/ops/op2/test_cases/sync/op_state_ref.rs
index ad5753f..f765f5f 100644
--- a/ops/op2/test_cases/sync/op_state_ref.rs
+++ b/ops/op2/test_cases/sync/op_state_ref.rs
@@ -5,4 +5,4 @@ deno_ops_compile_test_runner::prelude!();
 use deno_core::OpState;
 
 #[op2(fast)]
-fn op_state_rc(_state: &OpState) {}
+fn op_state_ref(_state: &OpState) {}