Repository: denoland/deno_core
Pull request #335 (2023-11-20 19:35:32)

refactor(core): refactoring ModuleMap and breaking down locks
Moves most of the module-related code from JsRealm to ModuleMap. This also removes the top-level RefCell and adds lower-level ones.

This paves the way for future refactoring.

Performance is not a consideration for this refactoring, but it's approximately on par with performance before.


Patch:
diff --git a/core/extensions.rs b/core/extensions.rs
index d50b670..74be401 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -96,10 +96,7 @@ pub struct OpDecl {
   pub name: &'static str,
   pub enabled: bool,
   pub is_async: bool,
-  #[deprecated = "Will be removed with op1"]
-  pub is_unstable: bool,
-  #[deprecated = "Will be removed with op1"]
-  pub is_v8: bool,
+  pub is_reentrant: bool,
   pub arg_count: u8,
   /// The slow dispatch call. If metrics are disabled, the `v8::Function` is created with this callback.
   pub(crate) slow_fn: OpFnRef,
@@ -114,37 +111,11 @@ pub struct OpDecl {
 impl OpDecl {
   /// For use by internal op implementation only.
   #[doc(hidden)]
-  #[deprecated = "#[op] is deprecated. Please switch this op to #[op2]."]
-  pub const fn new_internal(
-    name: &'static str,
-    is_async: bool,
-    is_unstable: bool,
-    is_v8: bool,
-    arg_count: u8,
-    slow_fn: OpFnRef,
-    fast_fn: Option<FastFunction>,
-  ) -> Self {
-    assert!(!is_async);
-    #[allow(deprecated)]
-    Self {
-      name,
-      enabled: true,
-      is_async: false,
-      is_unstable,
-      is_v8,
-      arg_count,
-      slow_fn,
-      slow_fn_with_metrics: slow_fn,
-      fast_fn,
-      fast_fn_with_metrics: fast_fn,
-    }
-  }
-
-  /// For use by internal op implementation only.
-  #[doc(hidden)]
+  #[allow(clippy::too_many_arguments)]
   pub const fn new_internal_op2(
     name: &'static str,
     is_async: bool,
+    is_reentrant: bool,
     arg_count: u8,
     slow_fn: OpFnRef,
     slow_fn_with_metrics: OpFnRef,
@@ -156,8 +127,7 @@ impl OpDecl {
       name,
       enabled: true,
       is_async,
-      is_unstable: false,
-      is_v8: false,
+      is_reentrant,
       arg_count,
       slow_fn,
       slow_fn_with_metrics,
diff --git a/core/lib.rs b/core/lib.rs
index e856a9e..43bc2a1 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -151,6 +151,8 @@ pub mod _ops {
   pub use super::error_codes::get_error_code;
   pub use super::extensions::Op;
   pub use super::extensions::OpDecl;
+  #[cfg(debug_assertions)]
+  pub use super::ops::reentrancy_check;
   pub use super::ops::OpCtx;
   pub use super::ops::OpResult;
   pub use super::ops_metrics::dispatch_metrics_async;
diff --git a/core/modules/map.rs b/core/modules/map.rs
index c9dbd03..a0a9820 100644
--- a/core/modules/map.rs
+++ b/core/modules/map.rs
@@ -2,6 +2,7 @@
 use crate::error::exception_to_err_result;
 use crate::error::generic_error;
 use crate::error::throw_type_error;
+use crate::error::to_v8_type_error;
 use crate::fast_string::FastString;
 use crate::modules::get_asserted_module_type_from_assertions;
 use crate::modules::parse_import_assertions;
@@ -26,10 +27,15 @@ use anyhow::Error;
 use futures::future::FutureExt;
 use futures::stream::FuturesUnordered;
 use futures::stream::StreamFuture;
+use futures::StreamExt;
+use std::cell::Cell;
 use std::cell::RefCell;
 use std::collections::HashMap;
+use std::ops::DerefMut;
 use std::pin::Pin;
 use std::rc::Rc;
+use std::task::Context;
+use std::task::Poll;
 
 use super::AssertedModuleType;
 
@@ -55,46 +61,80 @@ pub(crate) enum SymbolicModule {
   Mod(ModuleId),
 }
 
+struct DynImportModEvaluate {
+  load_id: ModuleLoadId,
+  module_id: ModuleId,
+  promise: v8::Global<v8::Promise>,
+  module: v8::Global<v8::Module>,
+}
+
 /// A collection of JS modules.
 pub(crate) struct ModuleMap {
-  // Handling of specifiers and v8 objects
-  pub handles: Vec<v8::Global<v8::Module>>,
-  pub info: Vec<ModuleInfo>,
-  pub(crate) by_name_js: HashMap<ModuleName, SymbolicModule>,
-  pub(crate) by_name_json: HashMap<ModuleName, SymbolicModule>,
-  pub(crate) next_load_id: ModuleLoadId,
-
   // Handling of futures for loading module sources
-  pub loader: Rc<dyn ModuleLoader>,
-  pub(crate) dynamic_import_map:
-    HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>,
-  pub(crate) preparing_dynamic_imports:
-    FuturesUnordered<Pin<Box<PrepareLoadFuture>>>,
-  pub(crate) pending_dynamic_imports:
-    FuturesUnordered<StreamFuture<RecursiveModuleLoad>>,
-
-  // This store is used temporarily, to forward parsed JSON
-  // value from `new_json_module` to `json_module_evaluation_steps`
+  // TODO(mmastrac): we should not be swapping this loader out
+  pub(crate) loader: RefCell<Rc<dyn ModuleLoader>>,
+
+  dynamic_import_map:
+    RefCell<HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>>,
+  preparing_dynamic_imports:
+    RefCell<FuturesUnordered<Pin<Box<PrepareLoadFuture>>>>,
+  pending_dynamic_imports:
+    RefCell<FuturesUnordered<StreamFuture<RecursiveModuleLoad>>>,
+  pending_dyn_mod_evaluations: RefCell<Vec<DynImportModEvaluate>>,
+  data: RefCell<ModuleMapData>,
+
+  /// A counter used to delay our dynamic import deadlock detection by one spin
+  /// of the event loop.
+  pub(crate) dyn_module_evaluate_idle_counter: Cell<u32>,
+}
+
+#[derive(Default)]
+pub(crate) struct ModuleMapData {
+  /// Inverted index from module to index in `info`.
+  handles_inverted: HashMap<v8::Global<v8::Module>, usize>,
+  /// The handles we have loaded so far, corresponding with the [`ModuleInfo`] in `info`.
+  handles: Vec<v8::Global<v8::Module>>,
+  /// The modules we have loaded so far.
+  info: Vec<ModuleInfo>,
+  /// [`ModuleName`] to [`SymbolicModule`] for JS/WASM modules.
+  by_name_js: HashMap<ModuleName, SymbolicModule>,
+  /// [`ModuleName`] to [`SymbolicModule`] for JSON modules.
+  by_name_json: HashMap<ModuleName, SymbolicModule>,
+  /// The next ID used for a load.
+  next_load_id: ModuleLoadId,
+  /// If a main module has been loaded, points to it by index.
+  main_module_id: Option<ModuleId>,
+  /// This store is used temporarily, to forward parsed JSON
+  /// value from `new_json_module` to `json_module_evaluation_steps`
   json_value_store: HashMap<v8::Global<v8::Module>, v8::Global<v8::Value>>,
 }
 
 impl ModuleMap {
-  pub fn collect_modules(
+  pub(crate) fn next_load_id(&self) -> i32 {
+    // TODO(mmastrac): move recursive module loading into here so we can avoid making this pub
+    let mut data = self.data.borrow_mut();
+    let id = data.next_load_id;
+    data.next_load_id += 1;
+    id + 1
+  }
+
+  /// Walk the currently registered modules (JS, WASM and JSON) along with their index and names.
+  fn walk_modules(
     &self,
-  ) -> Vec<(AssertedModuleType, &ModuleName, &SymbolicModule)> {
-    let mut output = vec![];
+    mut f: impl FnMut(usize, AssertedModuleType, &ModuleName, &SymbolicModule),
+  ) {
+    let mut i = 0;
     for module_type in [
       AssertedModuleType::JavaScriptOrWasm,
       AssertedModuleType::Json,
     ] {
-      output.extend(
-        self
-          .by_name(module_type)
-          .iter()
-          .map(|x| (module_type, x.0, x.1)),
-      )
+      self.by_name(module_type, |map| {
+        for (name, module) in map.iter() {
+          f(i, module_type, name, module);
+          i += 1;
+        }
+      });
     }
-    output
   }
 
   #[cfg(debug_assertions)]
@@ -103,11 +143,12 @@ impl ModuleMap {
     scope: &mut v8::HandleScope,
   ) {
     let mut not_evaluated = vec![];
+    let data = self.data.borrow();
 
-    for (i, handle) in self.handles.iter().enumerate() {
+    for (handle, i) in data.handles_inverted.iter() {
       let module = v8::Local::new(scope, handle);
       if !matches!(module.get_status(), v8::ModuleStatus::Evaluated) {
-        not_evaluated.push(self.info[i].name.as_str().to_string());
+        not_evaluated.push(data.info[*i].name.as_str().to_string());
       }
     }
 
@@ -125,22 +166,25 @@ impl ModuleMap {
     scope: &mut v8::HandleScope,
   ) -> SnapshottedData {
     let array = v8::Array::new(scope, 3);
+    let data = self.data.borrow();
 
-    let next_load_id = v8::Integer::new(scope, self.next_load_id);
+    let next_load_id = v8::Integer::new(scope, data.next_load_id);
     array.set_index(scope, 0, next_load_id.into());
 
-    let info_arr = v8::Array::new(scope, self.info.len() as i32);
-    for (i, info) in self.info.iter().enumerate() {
-      let module_info_arr = v8::Array::new(scope, 5);
+    if let Some(main_module_id) = data.main_module_id {
+      let main_module_id = v8::Integer::new(scope, main_module_id as i32);
+      array.set_index(scope, 1, main_module_id.into());
+    }
+
+    let info_arr = v8::Array::new(scope, data.info.len() as i32);
+    for (i, info) in data.info.iter().enumerate() {
+      let module_info_arr = v8::Array::new(scope, 4);
 
       let id = v8::Integer::new(scope, info.id as i32);
       module_info_arr.set_index(scope, 0, id.into());
 
-      let main = v8::Boolean::new(scope, info.main);
-      module_info_arr.set_index(scope, 1, main.into());
-
       let name = info.name.v8(scope);
-      module_info_arr.set_index(scope, 2, name.into());
+      module_info_arr.set_index(scope, 1, name.into());
 
       let array_len = 2 * info.requests.len() as i32;
       let requests_arr = v8::Array::new(scope, array_len);
@@ -161,52 +205,51 @@ impl ModuleMap {
           asserted_module_type.into(),
         );
       }
-      module_info_arr.set_index(scope, 3, requests_arr.into());
+      module_info_arr.set_index(scope, 2, requests_arr.into());
 
       let module_type = v8::Integer::new(scope, info.module_type as i32);
-      module_info_arr.set_index(scope, 4, module_type.into());
+      module_info_arr.set_index(scope, 3, module_type.into());
 
       info_arr.set_index(scope, i as u32, module_info_arr.into());
     }
-    array.set_index(scope, 1, info_arr.into());
-
-    let by_name = self.collect_modules();
-    let by_name_array = v8::Array::new(scope, by_name.len() as i32);
-    {
-      for (i, (module_type, name, module)) in by_name.into_iter().enumerate() {
-        let arr = v8::Array::new(scope, 3);
-
-        let specifier = name.v8(scope);
-        arr.set_index(scope, 0, specifier.into());
-
-        let asserted_module_type = v8::Integer::new(scope, module_type as i32);
-        arr.set_index(scope, 1, asserted_module_type.into());
-
-        let symbolic_module: v8::Local<v8::Value> = match module {
-          SymbolicModule::Alias(alias) => {
-            let alias = v8::String::new_from_one_byte(
-              scope,
-              alias.as_bytes(),
-              v8::NewStringType::Normal,
-            )
-            .unwrap();
-            alias.into()
-          }
-          SymbolicModule::Mod(id) => {
-            let id = v8::Integer::new(scope, *id as i32);
-            id.into()
-          }
-        };
-        arr.set_index(scope, 2, symbolic_module);
+    array.set_index(scope, 2, info_arr.into());
+
+    let length = self.data.borrow().by_name_js.len()
+      + self.data.borrow().by_name_json.len();
+    let by_name_array = v8::Array::new(scope, length.try_into().unwrap());
+    self.walk_modules(|i, module_type, name, module| {
+      let arr = v8::Array::new(scope, 3);
+
+      let specifier = name.v8(scope);
+      arr.set_index(scope, 0, specifier.into());
+
+      let asserted_module_type = v8::Integer::new(scope, module_type as i32);
+      arr.set_index(scope, 1, asserted_module_type.into());
+
+      let symbolic_module: v8::Local<v8::Value> = match module {
+        SymbolicModule::Alias(alias) => {
+          let alias = v8::String::new_from_one_byte(
+            scope,
+            alias.as_bytes(),
+            v8::NewStringType::Normal,
+          )
+          .unwrap();
+          alias.into()
+        }
+        SymbolicModule::Mod(id) => {
+          let id = v8::Integer::new(scope, *id as i32);
+          id.into()
+        }
+      };
+      arr.set_index(scope, 2, symbolic_module);
 
-        by_name_array.set_index(scope, i as u32, arr.into());
-      }
-    }
-    array.set_index(scope, 2, by_name_array.into());
+      by_name_array.set_index(scope, i as u32, arr.into());
+    });
+    array.set_index(scope, 3, by_name_array.into());
 
     let array_global = v8::Global::new(scope, array);
 
-    let handles = self.handles.clone();
+    let handles = data.handles.clone();
     SnapshottedData {
       module_map_data: array_global,
       module_handles: handles,
@@ -214,7 +257,7 @@ impl ModuleMap {
   }
 
   pub fn update_with_snapshotted_data(
-    &mut self,
+    &self,
     scope: &mut v8::HandleScope,
     snapshotted_data: SnapshottedData,
   ) {
@@ -226,11 +269,21 @@ impl ModuleMap {
       assert!(next_load_id.is_int32());
       let integer = next_load_id.to_integer(scope).unwrap();
       let val = integer.int32_value(scope).unwrap();
-      self.next_load_id = val;
+      self.data.borrow_mut().next_load_id = val;
+    }
+
+    {
+      let main_module_id = local_data.get_index(scope, 1).unwrap();
+      if !main_module_id.is_undefined() {
+        let integer = main_module_id
+          .to_integer(scope)
+          .map(|v| v.int32_value(scope).unwrap() as usize);
+        self.data.borrow_mut().main_module_id = integer;
+      }
     }
 
     {
-      let info_val = local_data.get_index(scope, 1).unwrap();
+      let info_val = local_data.get_index(scope, 2).unwrap();
 
       let info_arr: v8::Local<v8::Array> = info_val.try_into().unwrap();
       let len = info_arr.length() as usize;
@@ -250,20 +303,14 @@ impl ModuleMap {
           .unwrap()
           .value() as ModuleId;
 
-        let main = module_info_arr
-          .get_index(scope, 1)
-          .unwrap()
-          .to_boolean(scope)
-          .is_true();
-
         let name = module_info_arr
-          .get_index(scope, 2)
+          .get_index(scope, 1)
           .unwrap()
           .to_rust_string_lossy(scope)
           .into();
 
         let requests_arr: v8::Local<v8::Array> = module_info_arr
-          .get_index(scope, 3)
+          .get_index(scope, 2)
           .unwrap()
           .try_into()
           .unwrap();
@@ -292,7 +339,7 @@ impl ModuleMap {
         }
 
         let module_type_no = module_info_arr
-          .get_index(scope, 4)
+          .get_index(scope, 3)
           .unwrap()
           .to_integer(scope)
           .unwrap()
@@ -303,6 +350,7 @@ impl ModuleMap {
           _ => unreachable!(),
         };
 
+        let main = self.data.borrow().main_module_id == Some(id);
         let module_info = ModuleInfo {
           id,
           main,
@@ -313,17 +361,15 @@ impl ModuleMap {
         info.push(module_info);
       }
 
-      self.info = info;
+      self.data.borrow_mut().info = info;
     }
 
-    self
-      .by_name_mut(AssertedModuleType::JavaScriptOrWasm)
-      .clear();
-    self.by_name_mut(AssertedModuleType::Json).clear();
+    self.data.borrow_mut().by_name_js.clear();
+    self.data.borrow_mut().by_name_json.clear();
 
     {
       let by_name_arr: v8::Local<v8::Array> =
-        local_data.get_index(scope, 2).unwrap().try_into().unwrap();
+        local_data.get_index(scope, 3).unwrap().try_into().unwrap();
       let len = by_name_arr.length() as usize;
 
       for i in 0..len {
@@ -363,27 +409,24 @@ impl ModuleMap {
           )
         };
 
-        self
-          .by_name_mut(asserted_module_type)
-          .insert(specifier.into(), val);
+        self.by_name_mut(asserted_module_type, move |map| {
+          map.insert(specifier.into(), val)
+        });
       }
     }
 
-    self.handles = snapshotted_data.module_handles;
+    self.data.borrow_mut().handles = snapshotted_data.module_handles;
   }
 
   pub(crate) fn new(loader: Rc<dyn ModuleLoader>) -> ModuleMap {
     Self {
-      handles: vec![],
-      info: vec![],
-      by_name_js: HashMap::new(),
-      by_name_json: HashMap::new(),
-      next_load_id: 1,
-      loader,
-      dynamic_import_map: HashMap::new(),
-      preparing_dynamic_imports: FuturesUnordered::new(),
-      pending_dynamic_imports: FuturesUnordered::new(),
-      json_value_store: HashMap::new(),
+      loader: loader.into(),
+      dyn_module_evaluate_idle_counter: Default::default(),
+      dynamic_import_map: Default::default(),
+      preparing_dynamic_imports: Default::default(),
+      pending_dynamic_imports: Default::default(),
+      pending_dyn_mod_evaluations: Default::default(),
+      data: Default::default(),
     }
   }
 
@@ -394,26 +437,27 @@ impl ModuleMap {
     name: impl AsRef<str>,
     asserted_module_type: AssertedModuleType,
   ) -> Option<ModuleId> {
-    let map = self.by_name(asserted_module_type);
-    let first_symbolic_module = map.get(name.as_ref())?;
-    let mut mod_name = match first_symbolic_module {
-      SymbolicModule::Mod(mod_id) => return Some(*mod_id),
-      SymbolicModule::Alias(target) => target,
-    };
-    loop {
-      let symbolic_module = map.get(mod_name.as_ref())?;
-      match symbolic_module {
-        SymbolicModule::Alias(target) => {
-          debug_assert!(mod_name != target);
-          mod_name = target;
-        }
+    self.by_name(asserted_module_type, |map| {
+      let first_symbolic_module = map.get(name.as_ref())?;
+      let mut mod_name = match first_symbolic_module {
         SymbolicModule::Mod(mod_id) => return Some(*mod_id),
+        SymbolicModule::Alias(target) => target,
+      };
+      loop {
+        let symbolic_module = map.get(mod_name.as_ref())?;
+        match symbolic_module {
+          SymbolicModule::Alias(target) => {
+            debug_assert!(mod_name != target);
+            mod_name = target;
+          }
+          SymbolicModule::Mod(mod_id) => return Some(*mod_id),
+        }
       }
-    }
+    })
   }
 
   pub(crate) fn new_json_module(
-    &mut self,
+    &self,
     scope: &mut v8::HandleScope,
     name: ModuleName,
     source: ModuleCode,
@@ -448,7 +492,11 @@ impl ModuleMap {
 
     let handle = v8::Global::<v8::Module>::new(tc_scope, module);
     let value_handle = v8::Global::<v8::Value>::new(tc_scope, parsed_json);
-    self.json_value_store.insert(handle.clone(), value_handle);
+    self
+      .data
+      .borrow_mut()
+      .json_value_store
+      .insert(handle.clone(), value_handle);
 
     let id =
       self.create_module_info(name, ModuleType::Json, handle, false, vec![]);
@@ -458,7 +506,7 @@ impl ModuleMap {
 
   /// Create and compile an ES module.
   pub(crate) fn new_es_module(
-    &mut self,
+    &self,
     scope: &mut v8::HandleScope,
     main: bool,
     name: ModuleName,
@@ -517,7 +565,7 @@ impl ModuleMap {
         return Err(ModuleError::Exception(exception));
       }
 
-      let module_specifier = match self.loader.resolve(
+      let module_specifier = match self.loader.borrow().resolve(
         &import_specifier,
         name.as_ref(),
         if is_dynamic_import {
@@ -539,12 +587,13 @@ impl ModuleMap {
     }
 
     if main {
-      let maybe_main_module = self.info.iter().find(|module| module.main);
-      if let Some(main_module) = maybe_main_module {
+      let data = self.data.borrow();
+      if let Some(main_module) = data.main_module_id {
+        let main_name = self.get_name_by_id(main_module).unwrap();
         return Err(ModuleError::Other(generic_error(
           format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})",
           name.as_ref(),
-          main_module.name,
+          main_name,
         ))));
       }
     }
@@ -562,7 +611,7 @@ impl ModuleMap {
   }
 
   pub(crate) fn instantiate_module(
-    &mut self,
+    &self,
     scope: &mut v8::HandleScope,
     id: ModuleId,
   ) -> Result<(), v8::Global<v8::Value>> {
@@ -606,10 +655,9 @@ impl ModuleMap {
 
     let referrer_global = v8::Global::new(scope, referrer);
 
-    let referrer_info = module_map
-      .get_info(&referrer_global)
+    let referrer_name = module_map
+      .get_name_by_module(&referrer_global)
       .expect("ModuleInfo not found");
-    let referrer_name = referrer_info.name.as_str();
 
     let specifier_str = specifier.to_rust_string_lossy(scope);
 
@@ -621,7 +669,7 @@ impl ModuleMap {
     let maybe_module = module_map.resolve_callback(
       scope,
       &specifier_str,
-      referrer_name,
+      &referrer_name,
       assertions,
     );
     if let Some(module) = maybe_module {
@@ -645,6 +693,7 @@ impl ModuleMap {
   ) -> Option<v8::Local<'s, v8::Module>> {
     let resolved_specifier = self
       .loader
+      .borrow()
       .resolve(specifier, referrer, ResolutionKind::Import)
       .expect("Module should have been already resolved");
 
@@ -660,10 +709,6 @@ impl ModuleMap {
     None
   }
 
-  pub(crate) fn clear(&mut self) {
-    *self = Self::new(self.loader.clone())
-  }
-
   pub(crate) fn get_handle_by_name(
     &self,
     name: impl AsRef<str>,
@@ -675,7 +720,7 @@ impl ModuleMap {
   }
 
   pub(crate) fn inject_handle(
-    &mut self,
+    &self,
     name: ModuleName,
     module_type: ModuleType,
     handle: v8::Global<v8::Module>,
@@ -684,26 +729,32 @@ impl ModuleMap {
   }
 
   fn create_module_info(
-    &mut self,
+    &self,
     name: FastString,
     module_type: ModuleType,
     handle: v8::Global<v8::Module>,
     main: bool,
     requests: Vec<ModuleRequest>,
   ) -> ModuleId {
-    let id = self.handles.len();
+    let mut data = self.data.borrow_mut();
+    let id = data.handles.len();
     let (name1, name2) = name.into_cheap_copy();
-    self
-      .by_name_mut(module_type.into())
-      .insert(name1, SymbolicModule::Mod(id));
-    self.handles.push(handle);
-    self.info.push(ModuleInfo {
+    data.handles_inverted.insert(handle.clone(), id);
+    data.handles.push(handle);
+    if main {
+      data.main_module_id = Some(id);
+    }
+    data.info.push(ModuleInfo {
       id,
       main,
       name: name2,
       requests,
       module_type,
     });
+    drop(data);
+    self.by_name_mut(module_type.into(), |map| {
+      map.insert(name1, SymbolicModule::Mod(id))
+    });
 
     id
   }
@@ -711,8 +762,9 @@ impl ModuleMap {
   pub(crate) fn get_requested_modules(
     &self,
     id: ModuleId,
-  ) -> Option<&Vec<ModuleRequest>> {
-    self.info.get(id).map(|i| &i.requests)
+  ) -> Option<Vec<ModuleRequest>> {
+    // TODO(mmastrac): Remove cloning. We were originally cloning this at the call sites but that's no excuse.
+    self.data.borrow().info.get(id).map(|i| i.requests.clone())
   }
 
   fn is_registered(
@@ -721,43 +773,47 @@ impl ModuleMap {
     asserted_module_type: AssertedModuleType,
   ) -> bool {
     if let Some(id) = self.get_id(specifier.as_ref(), asserted_module_type) {
-      let info = self.get_info_by_id(id).unwrap();
-      return asserted_module_type == info.module_type.into();
+      return asserted_module_type
+        == self.data.borrow().info.get(id).unwrap().module_type.into();
     }
 
     false
   }
 
-  pub(crate) fn by_name(
+  pub(crate) fn by_name<T>(
     &self,
     asserted_module_type: AssertedModuleType,
-  ) -> &HashMap<ModuleName, SymbolicModule> {
+    f: impl FnOnce(&HashMap<ModuleName, SymbolicModule>) -> T,
+  ) -> T {
     match asserted_module_type {
-      AssertedModuleType::Json => &self.by_name_json,
-      AssertedModuleType::JavaScriptOrWasm => &self.by_name_js,
+      AssertedModuleType::Json => f(&self.data.borrow().by_name_json),
+      AssertedModuleType::JavaScriptOrWasm => f(&self.data.borrow().by_name_js),
     }
   }
 
-  pub(crate) fn by_name_mut(
-    &mut self,
+  pub(crate) fn by_name_mut<T>(
+    &self,
     asserted_module_type: AssertedModuleType,
-  ) -> &mut HashMap<ModuleName, SymbolicModule> {
+    f: impl FnOnce(&mut HashMap<ModuleName, SymbolicModule>) -> T,
+  ) -> T {
     match asserted_module_type {
-      AssertedModuleType::Json => &mut self.by_name_json,
-      AssertedModuleType::JavaScriptOrWasm => &mut self.by_name_js,
+      AssertedModuleType::Json => f(&mut self.data.borrow_mut().by_name_json),
+      AssertedModuleType::JavaScriptOrWasm => {
+        f(&mut self.data.borrow_mut().by_name_js)
+      }
     }
   }
 
   pub(crate) fn alias(
-    &mut self,
+    &self,
     name: FastString,
     asserted_module_type: AssertedModuleType,
     target: FastString,
   ) {
     debug_assert_ne!(name, target);
-    self
-      .by_name_mut(asserted_module_type)
-      .insert(name, SymbolicModule::Alias(target));
+    self.by_name_mut(asserted_module_type, |map| {
+      map.insert(name, SymbolicModule::Alias(target))
+    });
   }
 
   #[cfg(test)]
@@ -766,34 +822,56 @@ impl ModuleMap {
     name: &str,
     asserted_module_type: AssertedModuleType,
   ) -> bool {
-    let cond = self.by_name(asserted_module_type).get(name);
-    matches!(cond, Some(SymbolicModule::Alias(_)))
+    self.by_name(asserted_module_type, |map| {
+      matches!(map.get(name), Some(SymbolicModule::Alias(_)))
+    })
   }
 
   pub(crate) fn get_handle(
     &self,
     id: ModuleId,
   ) -> Option<v8::Global<v8::Module>> {
-    self.handles.get(id).cloned()
+    self.data.borrow().handles.get(id).cloned()
   }
 
-  pub(crate) fn get_info(
+  pub(crate) fn get_name_by_module(
     &self,
     global: &v8::Global<v8::Module>,
-  ) -> Option<&ModuleInfo> {
-    if let Some(id) = self.handles.iter().position(|module| module == global) {
-      return self.info.get(id);
+  ) -> Option<String> {
+    if let Some(id) = self.data.borrow().handles_inverted.get(global) {
+      self.get_name_by_id(*id)
+    } else {
+      None
     }
+  }
 
-    None
+  pub(crate) fn is_main_module(&self, global: &v8::Global<v8::Module>) -> bool {
+    let data = self.data.borrow();
+    data
+      .main_module_id
+      .map(|id| data.handles_inverted.get(global) == Some(&id))
+      .unwrap_or_default()
   }
 
-  pub(crate) fn get_info_by_id(&self, id: ModuleId) -> Option<&ModuleInfo> {
-    self.info.get(id)
+  pub(crate) fn get_name_by_id(&self, id: ModuleId) -> Option<String> {
+    // TODO(mmastrac): Don't clone
+    self
+      .data
+      .borrow()
+      .info
+      .get(id)
+      .map(|info| info.name.as_str().to_owned())
+  }
+
+  pub(crate) fn get_module_type_by_id(
+    &self,
+    id: ModuleId,
+  ) -> Option<ModuleType> {
+    self.data.borrow().info.get(id).map(|info| info.module_type)
   }
 
   pub(crate) async fn load_main(
-    module_map_rc: Rc<RefCell<ModuleMap>>,
+    module_map_rc: Rc<ModuleMap>,
     specifier: impl AsRef<str>,
   ) -> Result<RecursiveModuleLoad, Error> {
     let load =
@@ -803,7 +881,7 @@ impl ModuleMap {
   }
 
   pub(crate) async fn load_side(
-    module_map_rc: Rc<RefCell<ModuleMap>>,
+    module_map_rc: Rc<ModuleMap>,
     specifier: impl AsRef<str>,
   ) -> Result<RecursiveModuleLoad, Error> {
     let load =
@@ -814,7 +892,7 @@ impl ModuleMap {
 
   // Initiate loading of a module graph imported using `import()`.
   pub(crate) fn load_dynamic_import(
-    module_map_rc: Rc<RefCell<ModuleMap>>,
+    module_map_rc: Rc<ModuleMap>,
     specifier: &str,
     referrer: &str,
     asserted_module_type: AssertedModuleType,
@@ -827,15 +905,20 @@ impl ModuleMap {
       module_map_rc.clone(),
     );
 
-    let this: &mut ModuleMap = &mut module_map_rc.borrow_mut();
-    this.dynamic_import_map.insert(load.id, resolver_handle);
+    module_map_rc
+      .dynamic_import_map
+      .borrow_mut()
+      .insert(load.id, resolver_handle);
 
-    let loader = this.loader.clone();
-    let resolve_result =
-      loader.resolve(specifier, referrer, ResolutionKind::DynamicImport);
+    let loader = module_map_rc.loader.clone();
+    let resolve_result = loader.borrow().resolve(
+      specifier,
+      referrer,
+      ResolutionKind::DynamicImport,
+    );
     let fut = match resolve_result {
       Ok(module_specifier) => {
-        if this.is_registered(module_specifier, asserted_module_type) {
+        if module_map_rc.is_registered(module_specifier, asserted_module_type) {
           async move { (load.id, Ok(load)) }.boxed_local()
         } else {
           async move { (load.id, load.prepare().await.map(|()| load)) }
@@ -844,12 +927,308 @@ impl ModuleMap {
       }
       Err(error) => async move { (load.id, Err(error)) }.boxed_local(),
     };
-    this.preparing_dynamic_imports.push(fut);
+    module_map_rc
+      .preparing_dynamic_imports
+      .borrow_mut()
+      .push(fut);
   }
 
   pub(crate) fn has_pending_dynamic_imports(&self) -> bool {
-    !(self.preparing_dynamic_imports.is_empty()
-      && self.pending_dynamic_imports.is_empty())
+    !(self.preparing_dynamic_imports.borrow().is_empty()
+      && self.pending_dynamic_imports.borrow().is_empty())
+  }
+
+  pub(crate) fn has_pending_dyn_module_evaluation(&self) -> bool {
+    !self.pending_dyn_mod_evaluations.borrow().is_empty()
+  }
+
+  fn dynamic_import_module_evaluate(
+    &self,
+    scope: &mut v8::HandleScope,
+    load_id: ModuleLoadId,
+    id: ModuleId,
+  ) -> Result<(), Error> {
+    let module_handle = self.get_handle(id).expect("ModuleInfo not found");
+
+    let status = {
+      let module = module_handle.open(scope);
+      module.get_status()
+    };
+
+    match status {
+      v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated => {}
+      _ => return Ok(()),
+    }
+
+    // IMPORTANT: Top-level-await is enabled, which means that return value
+    // of module evaluation is a promise.
+    //
+    // This promise is internal, and not the same one that gets returned to
+    // the user. We add an empty `.catch()` handler so that it does not result
+    // in an exception if it rejects. That will instead happen for the other
+    // promise if not handled by the user.
+    //
+    // For more details see:
+    // https://github.com/denoland/deno/issues/4908
+    // https://v8.dev/features/top-level-await#module-execution-order
+    let tc_scope = &mut v8::TryCatch::new(scope);
+    let module = v8::Local::new(tc_scope, &module_handle);
+    let maybe_value = module.evaluate(tc_scope);
+
+    // Update status after evaluating.
+    let status = module.get_status();
+
+    if let Some(value) = maybe_value {
+      assert!(
+        status == v8::ModuleStatus::Evaluated
+          || status == v8::ModuleStatus::Errored
+      );
+      let promise = v8::Local::<v8::Promise>::try_from(value)
+        .expect("Expected to get promise as module evaluation result");
+      let empty_fn =
+        crate::runtime::bindings::create_empty_fn(tc_scope).unwrap();
+      promise.catch(tc_scope, empty_fn);
+      let promise_global = v8::Global::new(tc_scope, promise);
+      let module_global = v8::Global::new(tc_scope, module);
+
+      let dyn_import_mod_evaluate = DynImportModEvaluate {
+        load_id,
+        module_id: id,
+        promise: promise_global,
+        module: module_global,
+      };
+
+      self
+        .pending_dyn_mod_evaluations
+        .borrow_mut()
+        .push(dyn_import_mod_evaluate);
+    } else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
+      return Err(
+        generic_error("Cannot evaluate dynamically imported module, because JavaScript execution has been terminated.")
+      );
+    } else {
+      assert!(status == v8::ModuleStatus::Errored);
+    }
+
+    Ok(())
+  }
+
+  // Returns true if some dynamic import was resolved.
+  pub(crate) fn evaluate_dyn_imports(
+    &self,
+    scope: &mut v8::HandleScope,
+  ) -> bool {
+    let pending =
+      std::mem::take(self.pending_dyn_mod_evaluations.borrow_mut().deref_mut());
+    if pending.is_empty() {
+      return false;
+    }
+    let mut resolved_any = false;
+    let mut still_pending = vec![];
+    for pending_dyn_evaluate in pending {
+      let maybe_result = {
+        let module_id = pending_dyn_evaluate.module_id;
+        let promise = pending_dyn_evaluate.promise.open(scope);
+        let _module = pending_dyn_evaluate.module.open(scope);
+        let promise_state = promise.state();
+
+        match promise_state {
+          v8::PromiseState::Pending => {
+            still_pending.push(pending_dyn_evaluate);
+            None
+          }
+          v8::PromiseState::Fulfilled => {
+            Some(Ok((pending_dyn_evaluate.load_id, module_id)))
+          }
+          v8::PromiseState::Rejected => {
+            let exception = promise.result(scope);
+            let exception = v8::Global::new(scope, exception);
+            Some(Err((pending_dyn_evaluate.load_id, exception)))
+          }
+        }
+      };
+
+      if let Some(result) = maybe_result {
+        resolved_any = true;
+        match result {
+          Ok((dyn_import_id, module_id)) => {
+            self.dynamic_import_resolve(scope, dyn_import_id, module_id);
+          }
+          Err((dyn_import_id, exception)) => {
+            self.dynamic_import_reject(scope, dyn_import_id, exception);
+          }
+        }
+      }
+    }
+    *self.pending_dyn_mod_evaluations.borrow_mut() = still_pending;
+    resolved_any
+  }
+
+  pub(crate) fn dynamic_import_reject(
+    &self,
+    scope: &mut v8::HandleScope,
+    id: ModuleLoadId,
+    exception: v8::Global<v8::Value>,
+  ) {
+    let resolver_handle = self
+      .dynamic_import_map
+      .borrow_mut()
+      .remove(&id)
+      .expect("Invalid dynamic import id");
+    let resolver = resolver_handle.open(scope);
+
+    let exception = v8::Local::new(scope, exception);
+    resolver.reject(scope, exception).unwrap();
+    scope.perform_microtask_checkpoint();
+  }
+
+  pub(crate) fn dynamic_import_resolve(
+    &self,
+    scope: &mut v8::HandleScope,
+    id: ModuleLoadId,
+    mod_id: ModuleId,
+  ) {
+    let resolver_handle = self
+      .dynamic_import_map
+      .borrow_mut()
+      .remove(&id)
+      .expect("Invalid dynamic import id");
+    let resolver = resolver_handle.open(scope);
+
+    let module = self
+      .get_handle(mod_id)
+      .map(|handle| v8::Local::new(scope, handle))
+      .expect("Dyn import module info not found");
+    // Resolution success
+    assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated);
+
+    // IMPORTANT: No borrows to `ModuleMap` can be held at this point because
+    // resolving the promise might initiate another `import()` which will
+    // in turn call `bindings::host_import_module_dynamically_callback` which
+    // will reach into `ModuleMap` from within the isolate.
+    let module_namespace = module.get_module_namespace();
+    resolver.resolve(scope, module_namespace).unwrap();
+    self.dyn_module_evaluate_idle_counter.set(0);
+    scope.perform_microtask_checkpoint();
+  }
+
+  pub(crate) fn poll_prepare_dyn_imports(
+    &self,
+    scope: &mut v8::HandleScope,
+    cx: &mut Context,
+  ) -> Poll<Result<(), Error>> {
+    if self.preparing_dynamic_imports.borrow().is_empty() {
+      return Poll::Ready(Ok(()));
+    }
+
+    loop {
+      let poll_result = self
+        .preparing_dynamic_imports
+        .borrow_mut()
+        .poll_next_unpin(cx);
+
+      if let Poll::Ready(Some(prepare_poll)) = poll_result {
+        let dyn_import_id = prepare_poll.0;
+        let prepare_result = prepare_poll.1;
+
+        match prepare_result {
+          Ok(load) => {
+            self
+              .pending_dynamic_imports
+              .borrow_mut()
+              .push(load.into_future());
+          }
+          Err(err) => {
+            let exception = to_v8_type_error(scope, err);
+            self.dynamic_import_reject(scope, dyn_import_id, exception);
+          }
+        }
+        // Continue polling for more prepared dynamic imports.
+        continue;
+      }
+
+      // There are no active dynamic import loads, or none are ready.
+      return Poll::Ready(Ok(()));
+    }
+  }
+
+  pub(crate) fn poll_dyn_imports(
+    &self,
+    scope: &mut v8::HandleScope,
+    cx: &mut Context,
+  ) -> Poll<Result<(), Error>> {
+    if self.pending_dynamic_imports.borrow().is_empty() {
+      return Poll::Ready(Ok(()));
+    }
+
+    loop {
+      let poll_result = self
+        .pending_dynamic_imports
+        .borrow_mut()
+        .poll_next_unpin(cx);
+
+      if let Poll::Ready(Some(load_stream_poll)) = poll_result {
+        let maybe_result = load_stream_poll.0;
+        let mut load = load_stream_poll.1;
+        let dyn_import_id = load.id;
+
+        if let Some(load_stream_result) = maybe_result {
+          match load_stream_result {
+            Ok((request, info)) => {
+              // A module (not necessarily the one dynamically imported) has been
+              // fetched. Create and register it, and if successful, poll for the
+              // next recursive-load event related to this dynamic import.
+              let register_result =
+                load.register_and_recurse(scope, &request, info);
+
+              match register_result {
+                Ok(()) => {
+                  // Keep importing until it's fully drained
+                  self
+                    .pending_dynamic_imports
+                    .borrow_mut()
+                    .push(load.into_future());
+                }
+                Err(err) => {
+                  let exception = match err {
+                    ModuleError::Exception(e) => e,
+                    ModuleError::Other(e) => to_v8_type_error(scope, e),
+                  };
+                  self.dynamic_import_reject(scope, dyn_import_id, exception)
+                }
+              }
+            }
+            Err(err) => {
+              // A non-javascript error occurred; this could be due to a an invalid
+              // module specifier, or a problem with the source map, or a failure
+              // to fetch the module source code.
+              let exception = to_v8_type_error(scope, err);
+              self.dynamic_import_reject(scope, dyn_import_id, exception);
+            }
+          }
+        } else {
+          // The top-level module from a dynamic import has been instantiated.
+          // Load is done.
+          let module_id =
+            load.root_module_id.expect("Root module should be loaded");
+          let result = self.instantiate_module(scope, module_id);
+          if let Err(exception) = result {
+            self.dynamic_import_reject(scope, dyn_import_id, exception);
+          }
+          self.dynamic_import_module_evaluate(
+            scope,
+            dyn_import_id,
+            module_id,
+          )?;
+        }
+
+        // Continue polling for more ready dynamic imports.
+        continue;
+      }
+
+      // There are no active dynamic import loads, or none are ready.
+      return Poll::Ready(Ok(()));
+    }
   }
 
   /// Returns the namespace object of a module.
@@ -886,12 +1265,12 @@ impl ModuleMap {
   /// Clear the module map, meant to be used after initializing extensions.
   /// Optionally pass a list of exceptions `(old_name, new_name)` representing
   /// specifiers which will be renamed and preserved in the module map.
-  pub fn clear_module_map(&mut self, exceptions: &'static [&'static str]) {
+  pub fn clear_module_map(&self, exceptions: &'static [&'static str]) {
     let handles = exceptions
       .iter()
       .map(|mod_name| (self.get_handle_by_name(mod_name).unwrap(), mod_name))
       .collect::<Vec<_>>();
-    self.clear();
+    *self.data.borrow_mut() = ModuleMapData::default();
     for (handle, new_name) in handles {
       self.inject_handle(
         ModuleName::from_static(new_name),
@@ -906,7 +1285,8 @@ impl ModuleMap {
     scope: &mut v8::HandleScope,
     module_id: ModuleId,
   ) -> Vec<v8::Global<v8::Message>> {
-    let module_handle = self.handles.get(module_id).unwrap();
+    let data = self.data.borrow();
+    let module_handle = data.handles.get(module_id).unwrap();
 
     let module = v8::Local::new(scope, module_handle);
     let stalled = module.get_stalled_top_level_await_message(scope);
@@ -922,8 +1302,14 @@ impl ModuleMap {
     scope: &mut v8::HandleScope,
   ) -> Vec<v8::Global<v8::Message>> {
     // First check if that's root module
-    let root_module_id =
-      self.info.iter().filter(|m| m.main).map(|m| m.id).next();
+    let root_module_id = self
+      .data
+      .borrow()
+      .info
+      .iter()
+      .filter(|m| m.main)
+      .map(|m| m.id)
+      .next();
 
     if let Some(root_module_id) = root_module_id {
       let messages = self
@@ -935,7 +1321,7 @@ impl ModuleMap {
 
     // It wasn't a top module, so iterate over all modules and try to find
     // any with stalled top level await
-    for module_id in 0..self.handles.len() {
+    for module_id in 0..self.data.borrow().handles.len() {
       let messages =
         self.get_stalled_top_level_await_message_for_module(scope, module_id);
       if !messages.is_empty() {
@@ -945,6 +1331,33 @@ impl ModuleMap {
 
     vec![]
   }
+
+  // TODO(mmastrac): this is better than giving the entire crate access to the internals.
+  #[cfg(test)]
+  pub fn assert_module_map(&self, modules: &Vec<ModuleInfo>) {
+    let data = self.data.borrow();
+    assert_eq!(data.handles.len(), modules.len());
+    assert_eq!(data.info.len(), modules.len());
+    assert_eq!(data.next_load_id as usize, modules.len());
+    drop(data);
+
+    assert_eq!(
+      self.by_name(AssertedModuleType::Json, |map| map.len())
+        + self.by_name(AssertedModuleType::JavaScriptOrWasm, |map| map.len()),
+      modules.len()
+    );
+
+    for info in modules {
+      let data = self.data.borrow();
+      assert!(data.handles.get(info.id).is_some());
+      assert_eq!(data.info.get(info.id).unwrap(), info);
+      drop(data);
+      assert!(self.by_name(AssertedModuleType::JavaScriptOrWasm, |map| map
+        .get(&info.name)
+        .unwrap()
+        == &SymbolicModule::Mod(info.id)));
+    }
+  }
 }
 
 impl Default for ModuleMap {
@@ -967,6 +1380,7 @@ fn json_module_evaluation_steps<'a>(
 
   let handle = v8::Global::<v8::Module>::new(tc_scope, module);
   let value_handle = module_map
+    .data
     .borrow_mut()
     .json_value_store
     .remove(&handle)
diff --git a/core/modules/mod.rs b/core/modules/mod.rs
index 64093f6..d077c4b 100644
--- a/core/modules/mod.rs
+++ b/core/modules/mod.rs
@@ -41,8 +41,6 @@ pub use loaders::ModuleLoader;
 pub use loaders::NoopModuleLoader;
 pub use loaders::StaticModuleLoader;
 pub(crate) use map::ModuleMap;
-#[cfg(test)]
-pub(crate) use map::SymbolicModule;
 
 pub type ModuleId = usize;
 pub(crate) type ModuleLoadId = i32;
@@ -307,7 +305,7 @@ pub(crate) struct RecursiveModuleLoad {
   root_asserted_module_type: Option<AssertedModuleType>,
   root_module_type: Option<ModuleType>,
   state: LoadState,
-  module_map_rc: Rc<RefCell<ModuleMap>>,
+  module_map_rc: Rc<ModuleMap>,
   pending: FuturesUnordered<Pin<Box<ModuleLoadFuture>>>,
   visited: HashSet<ModuleRequest>,
   visited_as_alias: Rc<RefCell<HashSet<String>>>,
@@ -321,12 +319,12 @@ impl RecursiveModuleLoad {
   ///
   /// The module corresponding for the given `specifier` will be marked as
   // "the main module" (`import.meta.main` will return `true` for this module).
-  fn main(specifier: &str, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
+  fn main(specifier: &str, module_map_rc: Rc<ModuleMap>) -> Self {
     Self::new(LoadInit::Main(specifier.to_string()), module_map_rc)
   }
 
   /// Starts a new asynchronous load of the module graph for given specifier.
-  fn side(specifier: &str, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
+  fn side(specifier: &str, module_map_rc: Rc<ModuleMap>) -> Self {
     Self::new(LoadInit::Side(specifier.to_string()), module_map_rc)
   }
 
@@ -336,7 +334,7 @@ impl RecursiveModuleLoad {
     specifier: &str,
     referrer: &str,
     asserted_module_type: AssertedModuleType,
-    module_map_rc: Rc<RefCell<ModuleMap>>,
+    module_map_rc: Rc<ModuleMap>,
   ) -> Self {
     Self::new(
       LoadInit::DynamicImport(
@@ -348,14 +346,9 @@ impl RecursiveModuleLoad {
     )
   }
 
-  fn new(init: LoadInit, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
-    let id = {
-      let mut module_map = module_map_rc.borrow_mut();
-      let id = module_map.next_load_id;
-      module_map.next_load_id += 1;
-      id
-    };
-    let loader = module_map_rc.borrow().loader.clone();
+  fn new(init: LoadInit, module_map_rc: Rc<ModuleMap>) -> Self {
+    let id = module_map_rc.next_load_id();
+    let loader = module_map_rc.loader.borrow().clone();
     let asserted_module_type = match init {
       LoadInit::DynamicImport(_, _, module_type) => module_type,
       _ => AssertedModuleType::JavaScriptOrWasm,
@@ -376,19 +369,13 @@ impl RecursiveModuleLoad {
     // FIXME(bartlomieju): this seems fishy
     // Ignore the error here, let it be hit in `Stream::poll_next()`.
     if let Ok(root_specifier) = load.resolve_root() {
-      if let Some(module_id) = module_map_rc
-        .borrow()
-        .get_id(root_specifier, asserted_module_type)
+      if let Some(module_id) =
+        module_map_rc.get_id(root_specifier, asserted_module_type)
       {
         load.root_module_id = Some(module_id);
         load.root_asserted_module_type = Some(asserted_module_type);
-        load.root_module_type = Some(
-          module_map_rc
-            .borrow()
-            .get_info_by_id(module_id)
-            .unwrap()
-            .module_type,
-        );
+        load.root_module_type =
+          Some(module_map_rc.get_module_type_by_id(module_id).unwrap());
       }
     }
     load
@@ -474,7 +461,7 @@ impl RecursiveModuleLoad {
     let module_url_found = if let Some(module_url_found) = module_url_found {
       let (module_url_found1, module_url_found2) =
         module_url_found.into_cheap_copy();
-      self.module_map_rc.borrow_mut().alias(
+      self.module_map_rc.alias(
         module_url_specified,
         expected_asserted_module_type,
         module_url_found1,
@@ -486,7 +473,6 @@ impl RecursiveModuleLoad {
 
     let maybe_module_id = self
       .module_map_rc
-      .borrow()
       .get_id(&module_url_found, expected_asserted_module_type);
     let module_id = match maybe_module_id {
       Some(id) => {
@@ -497,16 +483,14 @@ impl RecursiveModuleLoad {
         id
       }
       None => match module_source.module_type {
-        ModuleType::JavaScript => {
-          self.module_map_rc.borrow_mut().new_es_module(
-            scope,
-            self.is_currently_loading_main_module(),
-            module_url_found,
-            module_source.code,
-            self.is_dynamic_import(),
-          )?
-        }
-        ModuleType::Json => self.module_map_rc.borrow_mut().new_json_module(
+        ModuleType::JavaScript => self.module_map_rc.new_es_module(
+          scope,
+          self.is_currently_loading_main_module(),
+          module_url_found,
+          module_source.code,
+          self.is_dynamic_import(),
+        )?,
+        ModuleType::Json => self.module_map_rc.new_json_module(
           scope,
           module_url_found,
           module_source.code,
@@ -530,7 +514,6 @@ impl RecursiveModuleLoad {
       let referrer = ModuleSpecifier::parse(&module_request.specifier).unwrap();
       let imports = self
         .module_map_rc
-        .borrow()
         .get_requested_modules(module_id)
         .unwrap()
         .clone();
@@ -541,7 +524,7 @@ impl RecursiveModuleLoad {
             .borrow()
             .contains(&module_request.specifier)
         {
-          if let Some(module_id) = self.module_map_rc.borrow().get_id(
+          if let Some(module_id) = self.module_map_rc.get_id(
             module_request.specifier.as_str(),
             module_request.asserted_module_type,
           ) {
@@ -725,7 +708,6 @@ pub(crate) struct ModuleRequest {
 pub(crate) struct ModuleInfo {
   #[allow(unused)]
   pub id: ModuleId,
-  // Used in "bindings.rs" for "import.meta.main" property value.
   pub main: bool,
   pub name: ModuleName,
   pub requests: Vec<ModuleRequest>,
diff --git a/core/ops.rs b/core/ops.rs
index f9ccc5c..cab1249 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -28,6 +28,37 @@ pub type PromiseId = i32;
 pub type OpId = u16;
 pub struct PendingOp(pub PromiseId, pub OpId, pub OpResult, pub bool);
 
+#[cfg(debug_assertions)]
+thread_local! {
+  static CURRENT_OP: std::cell::Cell<Option<&'static OpDecl>> = None.into();
+}
+
+#[cfg(debug_assertions)]
+pub struct ReentrancyGuard {}
+
+#[cfg(debug_assertions)]
+impl Drop for ReentrancyGuard {
+  fn drop(&mut self) {
+    CURRENT_OP.with(|f| f.set(None));
+  }
+}
+
+/// Creates an op re-entrancy check for the given [`OpDecl`].
+#[cfg(debug_assertions)]
+#[doc(hidden)]
+pub fn reentrancy_check(decl: &'static OpDecl) -> Option<ReentrancyGuard> {
+  if decl.is_reentrant {
+    return None;
+  }
+
+  let current = CURRENT_OP.with(|f| f.get());
+  if let Some(current) = current {
+    panic!("op {} was not marked as #[op2(reentrant)], but re-entrantly invoked op {}", current.name, decl.name);
+  }
+  CURRENT_OP.with(|f| f.set(Some(decl)));
+  Some(ReentrancyGuard {})
+}
+
 #[allow(clippy::type_complexity)]
 pub enum OpResult {
   Ok(serde_v8::SerializablePkg),
diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs
index dbe9450..13fbd57 100644
--- a/core/ops_builtin_v8.rs
+++ b/core/ops_builtin_v8.rs
@@ -14,6 +14,7 @@ use crate::source_map::SourceMapApplication;
 use crate::JsBuffer;
 use crate::JsRealm;
 use crate::JsRuntime;
+use crate::OpState;
 use anyhow::Error;
 use serde::Deserialize;
 use serde::Serialize;
@@ -62,7 +63,7 @@ pub fn op_queue_microtask(
 
 // 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(nofast)]
+#[op2(nofast, reentrant)]
 pub fn op_run_microtasks(isolate: *mut v8::Isolate) {
   // SAFETY: we know v8 provides us with a valid, non-null isolate
   unsafe {
@@ -97,7 +98,7 @@ pub struct EvalContextResult<'s>(
   Option<EvalContextError<'s>>,
 );
 
-#[op2]
+#[op2(reentrant)]
 #[serde]
 pub fn op_eval_context<'a>(
   scope: &mut v8::HandleScope<'a>,
@@ -360,7 +361,8 @@ pub struct SerializeDeserializeOptions<'a> {
   for_storage: bool,
 }
 
-#[op2]
+// May be reentrant in the case of errors.
+#[op2(reentrant)]
 #[buffer]
 pub fn op_serialize(
   scope: &mut v8::HandleScope,
@@ -731,30 +733,25 @@ pub fn op_set_wasm_streaming_callback(
   Ok(())
 }
 
+// This op is re-entrant as it makes a v8 call. It also cannot be fast because
+// we require a JS execution scope.
 #[allow(clippy::let_and_return)]
-#[op2]
+#[op2(nofast, reentrant)]
 pub fn op_abort_wasm_streaming(
-  scope: &mut v8::HandleScope,
+  state: Rc<RefCell<OpState>>,
   rid: u32,
   error: v8::Local<v8::Value>,
 ) -> Result<(), Error> {
-  let wasm_streaming = {
-    let state_rc = JsRuntime::state_from(scope);
-    let state = state_rc.borrow();
-    let wsr = state
-      .op_state
-      .borrow_mut()
-      .resource_table
-      .take::<WasmStreamingResource>(rid)?;
-    wsr
-  };
+  // NOTE: v8::WasmStreaming::abort can't be called while `state` is borrowed;
+  let wasm_streaming = state
+    .borrow_mut()
+    .resource_table
+    .take::<WasmStreamingResource>(rid)?;
 
   // At this point there are no clones of Rc<WasmStreamingResource> on the
   // resource table, and no one should own a reference because we're never
   // cloning them. So we can be sure `wasm_streaming` is the only reference.
   if let Ok(wsr) = std::rc::Rc::try_unwrap(wasm_streaming) {
-    // NOTE: v8::WasmStreaming::abort can't be called while `state` is borrowed;
-    // see https://github.com/denoland/deno/issues/13917
     wsr.0.into_inner().abort(Some(error));
   } else {
     panic!("Couldn't consume WasmStreamingResource.");
@@ -762,7 +759,8 @@ pub fn op_abort_wasm_streaming(
   Ok(())
 }
 
-#[op2]
+// This op calls `op_apply_source_map` re-entrantly.
+#[op2(reentrant)]
 #[serde]
 pub fn op_destructure_error(
   scope: &mut v8::HandleScope,
diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs
index 81822ef..68ea720 100644
--- a/core/runtime/bindings.rs
+++ b/core/runtime/bindings.rs
@@ -343,21 +343,21 @@ pub extern "C" fn host_initialize_import_meta_object_callback(
 ) {
   // SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
   let scope = &mut unsafe { v8::CallbackScope::new(context) };
-  let module_map_rc = JsRealm::module_map_from(scope);
-  let module_map = module_map_rc.borrow();
+  let module_map = JsRealm::module_map_from(scope);
 
   let module_global = v8::Global::new(scope, module);
-  let info = module_map
-    .get_info(&module_global)
+  let name = module_map
+    .get_name_by_module(&module_global)
     .expect("Module not found");
 
   let url_key = v8::String::new_external_onebyte_static(scope, b"url").unwrap();
-  let url_val = info.name.v8(scope);
+  let url_val = v8::String::new(scope, &name).unwrap();
   meta.create_data_property(scope, url_key.into(), url_val.into());
 
   let main_key =
     v8::String::new_external_onebyte_static(scope, b"main").unwrap();
-  let main_val = v8::Boolean::new(scope, info.main);
+  let main = module_map.is_main_module(&module_global);
+  let main_val = v8::Boolean::new(scope, main);
   meta.create_data_property(scope, main_key.into(), main_val.into());
 
   let builder =
@@ -387,7 +387,7 @@ fn import_meta_resolve(
     url_prop.to_rust_string_lossy(scope)
   };
   let module_map_rc = JsRealm::module_map_from(scope);
-  let loader = module_map_rc.borrow().loader.clone();
+  let loader = module_map_rc.loader.clone();
   let specifier_str = specifier.to_rust_string_lossy(scope);
 
   if specifier_str.starts_with("npm:") {
@@ -395,8 +395,11 @@ fn import_meta_resolve(
     return;
   }
 
-  match loader.resolve(&specifier_str, &referrer, ResolutionKind::DynamicImport)
-  {
+  match loader.borrow().resolve(
+    &specifier_str,
+    &referrer,
+    ResolutionKind::DynamicImport,
+  ) {
     Ok(resolved) => {
       let resolved_val = serde_v8::to_v8(scope, resolved.as_str()).unwrap();
       rv.set(resolved_val);
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
index d501873..10f9d95 100644
--- a/core/runtime/jsrealm.rs
+++ b/core/runtime/jsrealm.rs
@@ -2,12 +2,10 @@
 use super::bindings;
 use crate::error::exception_to_err_result;
 use crate::error::generic_error;
-use crate::error::to_v8_type_error;
 use crate::module_specifier::ModuleSpecifier;
 use crate::modules::ModuleCode;
 use crate::modules::ModuleError;
 use crate::modules::ModuleId;
-use crate::modules::ModuleLoadId;
 use crate::modules::ModuleMap;
 use crate::ops::OpCtx;
 use crate::ops::PendingOp;
@@ -24,8 +22,6 @@ use std::hash::BuildHasherDefault;
 use std::hash::Hasher;
 use std::option::Option;
 use std::rc::Rc;
-use std::task::Context;
-use std::task::Poll;
 use v8::HandleScope;
 use v8::Local;
 
@@ -48,13 +44,6 @@ impl Hasher for IdentityHasher {
   }
 }
 
-struct DynImportModEvaluate {
-  load_id: ModuleLoadId,
-  module_id: ModuleId,
-  promise: v8::Global<v8::Promise>,
-  module: v8::Global<v8::Module>,
-}
-
 pub(crate) struct ModEvaluate {
   promise: Option<v8::Global<v8::Promise>>,
   pub(crate) has_evaluated: bool,
@@ -71,7 +60,6 @@ pub(crate) struct ContextState {
   pub(crate) js_wasm_streaming_cb: Option<Rc<v8::Global<v8::Function>>>,
   pub(crate) pending_promise_rejections:
     VecDeque<(v8::Global<v8::Promise>, v8::Global<v8::Value>)>,
-  pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
   pub(crate) pending_mod_evaluate: Option<ModEvaluate>,
   pub(crate) unrefed_ops: HashSet<i32, BuildHasherDefault<IdentityHasher>>,
   pub(crate) pending_ops: JoinSet<PendingOp>,
@@ -134,7 +122,7 @@ pub struct JsRealm(pub(crate) JsRealmInner);
 pub(crate) struct JsRealmInner {
   context_state: Rc<RefCell<ContextState>>,
   context: Rc<v8::Global<v8::Context>>,
-  module_map: Rc<RefCell<ModuleMap>>,
+  module_map: Rc<ModuleMap>,
   runtime_state: Rc<RefCell<JsRuntimeState>>,
   is_main_realm: bool,
 }
@@ -143,7 +131,7 @@ impl JsRealmInner {
   pub(crate) fn new(
     context_state: Rc<RefCell<ContextState>>,
     context: v8::Global<v8::Context>,
-    module_map: Rc<RefCell<ModuleMap>>,
+    module_map: Rc<ModuleMap>,
     runtime_state: Rc<RefCell<JsRuntimeState>>,
     is_main_realm: bool,
   ) -> Self {
@@ -156,26 +144,27 @@ impl JsRealmInner {
     }
   }
 
+  #[inline(always)]
   pub fn num_pending_ops(&self) -> usize {
     self.context_state.borrow().pending_ops.len()
   }
 
+  #[inline(always)]
   pub fn num_unrefed_ops(&self) -> usize {
     self.context_state.borrow().unrefed_ops.len()
   }
 
+  #[inline(always)]
   pub fn has_pending_dyn_imports(&self) -> bool {
-    self.module_map.borrow().has_pending_dynamic_imports()
+    self.module_map.has_pending_dynamic_imports()
   }
 
+  #[inline(always)]
   pub fn has_pending_dyn_module_evaluation(&self) -> bool {
-    !self
-      .context_state
-      .borrow()
-      .pending_dyn_mod_evaluate
-      .is_empty()
+    self.module_map.has_pending_dyn_module_evaluation()
   }
 
+  #[inline(always)]
   pub fn has_pending_module_evaluation(&self) -> bool {
     self.context_state.borrow().pending_mod_evaluate.is_some()
   }
@@ -191,7 +180,7 @@ impl JsRealmInner {
   }
 
   #[inline(always)]
-  pub(crate) fn module_map(&self) -> Rc<RefCell<ModuleMap>> {
+  pub(crate) fn module_map(&self) -> Rc<ModuleMap> {
     self.module_map.clone()
   }
 
@@ -270,14 +259,9 @@ impl JsRealm {
   }
 
   #[inline(always)]
-  pub(crate) fn module_map_from(
-    scope: &mut v8::HandleScope,
-  ) -> Rc<RefCell<ModuleMap>> {
+  pub(crate) fn module_map_from(scope: &mut v8::HandleScope) -> Rc<ModuleMap> {
     let context = scope.get_current_context();
-    context
-      .get_slot::<Rc<RefCell<ModuleMap>>>(scope)
-      .unwrap()
-      .clone()
+    context.get_slot::<Rc<ModuleMap>>(scope).unwrap().clone()
   }
 
   #[inline(always)]
@@ -402,13 +386,6 @@ 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.
@@ -421,7 +398,6 @@ impl JsRealm {
     self
       .0
       .module_map()
-      .borrow()
       .get_module_namespace(&mut self.handle_scope(isolate), module_id)
   }
 
@@ -433,89 +409,9 @@ impl JsRealm {
     self
       .0
       .module_map()
-      .borrow_mut()
       .instantiate_module(&mut self.handle_scope(isolate), id)
   }
 
-  fn dynamic_import_module_evaluate(
-    &self,
-    isolate: &mut v8::Isolate,
-    load_id: ModuleLoadId,
-    id: ModuleId,
-  ) -> Result<(), Error> {
-    let module_handle = self
-      .0
-      .module_map()
-      .borrow()
-      .get_handle(id)
-      .expect("ModuleInfo not found");
-
-    let status = {
-      let scope = &mut self.handle_scope(isolate);
-      let module = module_handle.open(scope);
-      module.get_status()
-    };
-
-    match status {
-      v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated => {}
-      _ => return Ok(()),
-    }
-
-    // IMPORTANT: Top-level-await is enabled, which means that return value
-    // of module evaluation is a promise.
-    //
-    // This promise is internal, and not the same one that gets returned to
-    // the user. We add an empty `.catch()` handler so that it does not result
-    // in an exception if it rejects. That will instead happen for the other
-    // promise if not handled by the user.
-    //
-    // For more details see:
-    // https://github.com/denoland/deno/issues/4908
-    // https://v8.dev/features/top-level-await#module-execution-order
-    let scope = &mut self.handle_scope(isolate);
-    let tc_scope = &mut v8::TryCatch::new(scope);
-    let module = v8::Local::new(tc_scope, &module_handle);
-    let maybe_value = module.evaluate(tc_scope);
-
-    // Update status after evaluating.
-    let status = module.get_status();
-
-    if let Some(value) = maybe_value {
-      assert!(
-        status == v8::ModuleStatus::Evaluated
-          || status == v8::ModuleStatus::Errored
-      );
-      let promise = v8::Local::<v8::Promise>::try_from(value)
-        .expect("Expected to get promise as module evaluation result");
-      let empty_fn = bindings::create_empty_fn(tc_scope).unwrap();
-      promise.catch(tc_scope, empty_fn);
-      let promise_global = v8::Global::new(tc_scope, promise);
-      let module_global = v8::Global::new(tc_scope, module);
-
-      let dyn_import_mod_evaluate = DynImportModEvaluate {
-        load_id,
-        module_id: id,
-        promise: promise_global,
-        module: module_global,
-      };
-
-      self
-        .0
-        .context_state
-        .borrow_mut()
-        .pending_dyn_mod_evaluate
-        .push(dyn_import_mod_evaluate);
-    } else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
-      return Err(
-        generic_error("Cannot evaluate dynamically imported module, because JavaScript execution has been terminated.")
-      );
-    } else {
-      assert!(status == v8::ModuleStatus::Errored);
-    }
-
-    Ok(())
-  }
-
   // TODO(bartlomieju): make it return `ModuleEvaluationFuture`?
   /// Evaluates an already instantiated ES module.
   ///
@@ -538,7 +434,6 @@ impl JsRealm {
     let tc_scope = &mut v8::TryCatch::new(scope);
 
     let module = module_map_rc
-      .borrow()
       .get_handle(id)
       .map(|handle| v8::Local::new(tc_scope, handle))
       .expect("ModuleInfo not found");
@@ -552,12 +447,7 @@ impl JsRealm {
       } else {
         "Module not instantiated"
       },
-      module_map_rc
-        .borrow()
-        .get_info_by_id(id)
-        .unwrap()
-        .name
-        .as_str(),
+      module_map_rc.get_name_by_id(id).unwrap(),
       id,
     );
 
@@ -659,212 +549,13 @@ impl JsRealm {
     receiver
   }
 
-  fn dynamic_import_reject(
-    &self,
-    isolate: &mut v8::Isolate,
-    id: ModuleLoadId,
-    exception: v8::Global<v8::Value>,
-  ) {
-    let module_map_rc = self.0.module_map();
-    let scope = &mut self.handle_scope(isolate);
-
-    let resolver_handle = module_map_rc
-      .borrow_mut()
-      .dynamic_import_map
-      .remove(&id)
-      .expect("Invalid dynamic import id");
-    let resolver = resolver_handle.open(scope);
-
-    // IMPORTANT: No borrows to `ModuleMap` can be held at this point because
-    // rejecting the promise might initiate another `import()` which will
-    // in turn call `bindings::host_import_module_dynamically_callback` which
-    // will reach into `ModuleMap` from within the isolate.
-    let exception = v8::Local::new(scope, exception);
-    resolver.reject(scope, exception).unwrap();
-    scope.perform_microtask_checkpoint();
-  }
-
-  fn dynamic_import_resolve(
-    &self,
-    isolate: &mut v8::Isolate,
-    id: ModuleLoadId,
-    mod_id: ModuleId,
-  ) {
-    let state_rc = self.0.runtime_state.clone();
-    let module_map_rc = self.0.module_map();
-    let scope = &mut self.handle_scope(isolate);
-
-    let resolver_handle = module_map_rc
-      .borrow_mut()
-      .dynamic_import_map
-      .remove(&id)
-      .expect("Invalid dynamic import id");
-    let resolver = resolver_handle.open(scope);
-
-    let module = {
-      module_map_rc
-        .borrow()
-        .get_handle(mod_id)
-        .map(|handle| v8::Local::new(scope, handle))
-        .expect("Dyn import module info not found")
-    };
-    // Resolution success
-    assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated);
-
-    // IMPORTANT: No borrows to `ModuleMap` can be held at this point because
-    // resolving the promise might initiate another `import()` which will
-    // in turn call `bindings::host_import_module_dynamically_callback` which
-    // will reach into `ModuleMap` from within the isolate.
-    let module_namespace = module.get_module_namespace();
-    resolver.resolve(scope, module_namespace).unwrap();
-    state_rc.borrow_mut().dyn_module_evaluate_idle_counter = 0;
-    scope.perform_microtask_checkpoint();
-  }
-
-  pub(in crate::runtime) fn prepare_dyn_imports(
-    &self,
-    isolate: &mut v8::Isolate,
-    cx: &mut Context,
-  ) -> Poll<Result<(), Error>> {
-    if self
-      .0
-      .module_map()
-      .borrow()
-      .preparing_dynamic_imports
-      .is_empty()
-    {
-      return Poll::Ready(Ok(()));
-    }
-
-    loop {
-      let poll_result = self
-        .0
-        .module_map()
-        .borrow_mut()
-        .preparing_dynamic_imports
-        .poll_next_unpin(cx);
-
-      if let Poll::Ready(Some(prepare_poll)) = poll_result {
-        let dyn_import_id = prepare_poll.0;
-        let prepare_result = prepare_poll.1;
-
-        match prepare_result {
-          Ok(load) => {
-            self
-              .0
-              .module_map()
-              .borrow_mut()
-              .pending_dynamic_imports
-              .push(load.into_future());
-          }
-          Err(err) => {
-            let exception =
-              to_v8_type_error(&mut self.handle_scope(isolate), err);
-            self.dynamic_import_reject(isolate, dyn_import_id, exception);
-          }
-        }
-        // Continue polling for more prepared dynamic imports.
-        continue;
-      }
-
-      // There are no active dynamic import loads, or none are ready.
-      return Poll::Ready(Ok(()));
-    }
+  pub(crate) fn modules_idle(&self) -> bool {
+    self.0.module_map.dyn_module_evaluate_idle_counter.get() > 1
   }
 
-  pub(in crate::runtime) fn poll_dyn_imports(
-    &self,
-    isolate: &mut v8::Isolate,
-    cx: &mut Context,
-  ) -> Poll<Result<(), Error>> {
-    if self
-      .0
-      .module_map()
-      .borrow()
-      .pending_dynamic_imports
-      .is_empty()
-    {
-      return Poll::Ready(Ok(()));
-    }
-
-    loop {
-      let poll_result = self
-        .0
-        .module_map()
-        .borrow_mut()
-        .pending_dynamic_imports
-        .poll_next_unpin(cx);
-
-      if let Poll::Ready(Some(load_stream_poll)) = poll_result {
-        let maybe_result = load_stream_poll.0;
-        let mut load = load_stream_poll.1;
-        let dyn_import_id = load.id;
-
-        if let Some(load_stream_result) = maybe_result {
-          match load_stream_result {
-            Ok((request, info)) => {
-              // A module (not necessarily the one dynamically imported) has been
-              // fetched. Create and register it, and if successful, poll for the
-              // next recursive-load event related to this dynamic import.
-              let register_result = load.register_and_recurse(
-                &mut self.handle_scope(isolate),
-                &request,
-                info,
-              );
-
-              match register_result {
-                Ok(()) => {
-                  // Keep importing until it's fully drained
-                  self
-                    .0
-                    .module_map()
-                    .borrow_mut()
-                    .pending_dynamic_imports
-                    .push(load.into_future());
-                }
-                Err(err) => {
-                  let exception = match err {
-                    ModuleError::Exception(e) => e,
-                    ModuleError::Other(e) => {
-                      to_v8_type_error(&mut self.handle_scope(isolate), e)
-                    }
-                  };
-                  self.dynamic_import_reject(isolate, dyn_import_id, exception)
-                }
-              }
-            }
-            Err(err) => {
-              // A non-javascript error occurred; this could be due to a an invalid
-              // module specifier, or a problem with the source map, or a failure
-              // to fetch the module source code.
-              let exception =
-                to_v8_type_error(&mut self.handle_scope(isolate), err);
-              self.dynamic_import_reject(isolate, dyn_import_id, exception);
-            }
-          }
-        } else {
-          // The top-level module from a dynamic import has been instantiated.
-          // Load is done.
-          let module_id =
-            load.root_module_id.expect("Root module should be loaded");
-          let result = self.instantiate_module(isolate, module_id);
-          if let Err(exception) = result {
-            self.dynamic_import_reject(isolate, dyn_import_id, exception);
-          }
-          self.dynamic_import_module_evaluate(
-            isolate,
-            dyn_import_id,
-            module_id,
-          )?;
-        }
-
-        // Continue polling for more ready dynamic imports.
-        continue;
-      }
-
-      // There are no active dynamic import loads, or none are ready.
-      return Poll::Ready(Ok(()));
-    }
+  pub(crate) fn increment_modules_idle(&self) {
+    let count = &self.0.module_map.dyn_module_evaluate_idle_counter;
+    count.set(count.get() + 1)
   }
 
   /// "deno_core" runs V8 with Top Level Await enabled. It means that each
@@ -933,60 +624,6 @@ impl JsRealm {
     }
   }
 
-  // Returns true if some dynamic import was resolved.
-  pub(in crate::runtime) fn evaluate_dyn_imports(
-    &self,
-    isolate: &mut v8::Isolate,
-  ) -> bool {
-    let pending = std::mem::take(
-      &mut self.0.context_state.borrow_mut().pending_dyn_mod_evaluate,
-    );
-    if pending.is_empty() {
-      return false;
-    }
-    let mut resolved_any = false;
-    let mut still_pending = vec![];
-    for pending_dyn_evaluate in pending {
-      let maybe_result = {
-        let scope = &mut self.handle_scope(isolate);
-
-        let module_id = pending_dyn_evaluate.module_id;
-        let promise = pending_dyn_evaluate.promise.open(scope);
-        let _module = pending_dyn_evaluate.module.open(scope);
-        let promise_state = promise.state();
-
-        match promise_state {
-          v8::PromiseState::Pending => {
-            still_pending.push(pending_dyn_evaluate);
-            None
-          }
-          v8::PromiseState::Fulfilled => {
-            Some(Ok((pending_dyn_evaluate.load_id, module_id)))
-          }
-          v8::PromiseState::Rejected => {
-            let exception = promise.result(scope);
-            let exception = v8::Global::new(scope, exception);
-            Some(Err((pending_dyn_evaluate.load_id, exception)))
-          }
-        }
-      };
-
-      if let Some(result) = maybe_result {
-        resolved_any = true;
-        match result {
-          Ok((dyn_import_id, module_id)) => {
-            self.dynamic_import_resolve(isolate, dyn_import_id, module_id);
-          }
-          Err((dyn_import_id, exception)) => {
-            self.dynamic_import_reject(isolate, dyn_import_id, exception);
-          }
-        }
-      }
-    }
-    self.0.context_state.borrow_mut().pending_dyn_mod_evaluate = still_pending;
-    resolved_any
-  }
-
   /// Asynchronously load specified module and all of its dependencies.
   ///
   /// The module will be marked as "main", and because of that
@@ -1006,7 +643,6 @@ impl JsRealm {
       let scope = &mut self.handle_scope(isolate);
       // true for main module
       module_map_rc
-        .borrow_mut()
         .new_es_module(scope, true, specifier, code, false)
         .map_err(|e| match e {
           ModuleError::Exception(exception) => {
@@ -1062,7 +698,6 @@ impl JsRealm {
       let scope = &mut self.handle_scope(isolate);
       // false for side module (not main module)
       module_map_rc
-        .borrow_mut()
         .new_es_module(scope, false, specifier, code, false)
         .map_err(|e| match e {
           ModuleError::Exception(exception) => {
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
index d0f2fbe..80fbbd6 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -305,9 +305,6 @@ pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
 pub struct JsRuntimeState {
   known_realms: Vec<JsRealmInner>,
   pub(crate) has_tick_scheduled: bool,
-  /// A counter used to delay our dynamic import deadlock detection by one spin
-  /// of the event loop.
-  pub(crate) dyn_module_evaluate_idle_counter: u32,
   pub(crate) source_map_getter: Option<Rc<Box<dyn SourceMapGetter>>>,
   pub(crate) source_map_cache: Rc<RefCell<SourceMapCache>>,
   pub(crate) op_state: Rc<RefCell<OpState>>,
@@ -618,7 +615,6 @@ impl JsRuntime {
       .unwrap_or_else(|| Box::new(crate::modules::validate_import_attributes));
 
     let state_rc = Rc::new(RefCell::new(JsRuntimeState {
-      dyn_module_evaluate_idle_counter: 0,
       has_tick_scheduled: false,
       source_map_getter: options.source_map_getter.map(Rc::new),
       source_map_cache: Default::default(),
@@ -755,18 +751,17 @@ impl JsRuntime {
       .module_loader
       .unwrap_or_else(|| Rc::new(NoopModuleLoader));
 
-    let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader)));
+    let module_map = Rc::new(ModuleMap::new(loader));
     if let Some(snapshotted_data) = snapshotted_data {
-      let mut module_map = module_map_rc.borrow_mut();
       module_map.update_with_snapshotted_data(scope, snapshotted_data);
     }
-    context.set_slot(scope, module_map_rc.clone());
+    context.set_slot(scope, module_map.clone());
 
     {
       let main_realm = JsRealmInner::new(
         context_state,
         main_context,
-        module_map_rc.clone(),
+        module_map.clone(),
         state_rc.clone(),
         true,
       );
@@ -810,9 +805,7 @@ impl JsRuntime {
     if let Some(preserve_snapshotted_modules) =
       options.preserve_snapshotted_modules
     {
-      module_map_rc
-        .borrow_mut()
-        .clear_module_map(preserve_snapshotted_modules);
+      module_map.clear_module_map(preserve_snapshotted_modules);
     }
 
     js_runtime
@@ -820,7 +813,7 @@ impl JsRuntime {
 
   #[cfg(test)]
   #[inline]
-  pub(crate) fn module_map(&mut self) -> Rc<RefCell<ModuleMap>> {
+  pub(crate) fn module_map(&mut self) -> Rc<ModuleMap> {
     self.main_realm().0.module_map()
   }
 
@@ -918,20 +911,18 @@ impl JsRuntime {
       let loader = options
         .module_loader
         .unwrap_or_else(|| Rc::new(NoopModuleLoader));
-      let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader)));
+      let module_map = Rc::new(ModuleMap::new(loader));
       if self.init_mode.is_from_snapshot() {
         let snapshotted_data =
           snapshot_util::get_snapshotted_data(scope, context);
-        module_map_rc
-          .borrow_mut()
-          .update_with_snapshotted_data(scope, snapshotted_data);
+        module_map.update_with_snapshotted_data(scope, snapshotted_data);
       }
-      context.set_slot(scope, module_map_rc.clone());
+      context.set_slot(scope, module_map.clone());
 
       let realm = JsRealmInner::new(
         context_state,
         v8::Global::new(scope, context),
-        module_map_rc,
+        module_map,
         self.inner.state.clone(),
         false,
       );
@@ -953,7 +944,6 @@ impl JsRuntime {
       realm
         .0
         .module_map()
-        .borrow_mut()
         .clear_module_map(preserve_snapshotted_modules);
     }
 
@@ -974,14 +964,14 @@ impl JsRuntime {
     // 2. Iterate through all extensions:
     //  a. If an extension has a `esm_entry_point`, execute it.
 
-    let module_map_rc = realm.0.module_map();
+    let module_map = realm.0.module_map();
 
     // Take extensions temporarily so we can avoid have a mutable reference to self
     let extensions = std::mem::take(&mut self.extensions);
 
-    let loader = module_map_rc.borrow().loader.clone();
+    let loader = module_map.loader.borrow().clone();
     let ext_loader = Rc::new(ExtModuleLoader::new(&extensions));
-    module_map_rc.borrow_mut().loader = ext_loader;
+    *module_map.loader.borrow_mut() = ext_loader;
 
     let mut esm_entrypoints = vec![];
 
@@ -1025,8 +1015,7 @@ impl JsRuntime {
 
       for specifier in esm_entrypoints {
         let mod_id = {
-          module_map_rc
-            .borrow()
+          module_map
             .get_id(specifier, AssertedModuleType::JavaScriptOrWasm)
             .unwrap_or_else(|| {
               panic!("{} not present in the module map", specifier)
@@ -1047,7 +1036,6 @@ impl JsRuntime {
             // Find the TLA location to display it on the panic.
             let location = {
               let scope = &mut realm.handle_scope(self.v8_isolate());
-              let module_map = module_map_rc.borrow();
               let messages = module_map.find_stalled_top_level_await(scope);
               assert!(!messages.is_empty());
               let msg = v8::Local::new(scope, &messages[0]);
@@ -1067,7 +1055,6 @@ impl JsRuntime {
       #[cfg(debug_assertions)]
       {
         let mut scope = realm.handle_scope(self.v8_isolate());
-        let module_map = module_map_rc.borrow();
         module_map.assert_all_modules_evaluated(&mut scope);
       }
 
@@ -1075,7 +1062,8 @@ impl JsRuntime {
     })?;
 
     self.extensions = extensions;
-    module_map_rc.borrow_mut().loader = loader;
+    let module_map = realm.0.module_map();
+    *module_map.loader.borrow_mut() = loader;
 
     Ok(())
   }
@@ -1533,20 +1521,23 @@ impl JsRuntime {
         let mut has_evaluated = false;
 
         // Fast main realm poll
-        let main_realm = self.inner.main_realm.as_ref().unwrap();
-        loop {
-          let poll_imports =
-            main_realm.prepare_dyn_imports(&mut self.inner.v8_isolate, cx)?;
-          assert!(poll_imports.is_ready());
-
-          let poll_imports =
-            main_realm.poll_dyn_imports(&mut self.inner.v8_isolate, cx)?;
-          assert!(poll_imports.is_ready());
-
-          if main_realm.evaluate_dyn_imports(&mut self.inner.v8_isolate) {
-            has_evaluated = true;
-          } else {
-            break;
+        {
+          let main_realm = self.inner.main_realm.as_ref().unwrap();
+          let mut scope = main_realm.handle_scope(&mut self.inner.v8_isolate);
+          let modules = main_realm.0.module_map();
+          loop {
+            let poll_imports =
+              modules.poll_prepare_dyn_imports(&mut scope, cx)?;
+            assert!(poll_imports.is_ready());
+
+            let poll_imports = modules.poll_dyn_imports(&mut scope, cx)?;
+            assert!(poll_imports.is_ready());
+
+            if modules.evaluate_dyn_imports(&mut scope) {
+              has_evaluated = true;
+            } else {
+              break;
+            }
           }
         }
 
@@ -1557,19 +1548,20 @@ impl JsRuntime {
           drop(state);
           for inner_realm in realms {
             let realm = JsRealm::new(inner_realm);
+            let mut scope = realm.handle_scope(&mut self.inner.v8_isolate);
+            let modules = realm.0.module_map();
 
             // Try and resolve as many dynamic imports in each realm as possible
             // before moving to the next.
             loop {
               let poll_imports =
-                realm.prepare_dyn_imports(&mut self.inner.v8_isolate, cx)?;
+                modules.poll_prepare_dyn_imports(&mut scope, cx)?;
               assert!(poll_imports.is_ready());
 
-              let poll_imports =
-                realm.poll_dyn_imports(&mut self.inner.v8_isolate, cx)?;
+              let poll_imports = modules.poll_dyn_imports(&mut scope, cx)?;
               assert!(poll_imports.is_ready());
 
-              if realm.evaluate_dyn_imports(&mut self.inner.v8_isolate) {
+              if modules.evaluate_dyn_imports(&mut scope) {
                 has_evaluated = true;
               } else {
                 break;
@@ -1712,8 +1704,7 @@ impl JsRuntime {
         || pending_state.has_tick_scheduled
       {
         // pass, will be polled again
-      } else if self.inner.state.borrow().dyn_module_evaluate_idle_counter >= 1
-      {
+      } else if self.inner.main_realm.as_ref().unwrap().modules_idle() {
         let known_realms = &self.inner.state.borrow().known_realms;
         return Poll::Ready(Err(
           find_and_report_stalled_level_await_in_any_realm(
@@ -1722,11 +1713,16 @@ impl JsRuntime {
           ),
         ));
       } else {
-        let mut state = self.inner.state.borrow_mut();
+        let state = self.inner.state.borrow_mut();
         // Delay the above error by one spin of the event loop. A dynamic import
         // evaluation may complete during this, in which case the counter will
         // reset.
-        state.dyn_module_evaluate_idle_counter += 1;
+        self
+          .inner
+          .main_realm
+          .as_ref()
+          .unwrap()
+          .increment_modules_idle();
         state.op_state.borrow().waker.wake();
       }
     }
@@ -1742,7 +1738,7 @@ fn find_and_report_stalled_level_await_in_any_realm(
   for inner_realm in known_realms {
     let scope = &mut inner_realm.handle_scope(v8_isolate);
     let module_map = inner_realm.module_map();
-    let messages = module_map.borrow().find_stalled_top_level_await(scope);
+    let messages = module_map.find_stalled_top_level_await(scope);
 
     if !messages.is_empty() {
       // We are gonna print only a single message to provide a nice formatting
@@ -1814,8 +1810,7 @@ impl JsRuntimeForSnapshot {
     // Serialize the module map and store its data in the snapshot.
     {
       let snapshotted_data = {
-        let module_map_rc = realm.0.module_map();
-        let module_map = module_map_rc.borrow();
+        let module_map = realm.0.module_map();
         module_map.serialize_for_snapshotting(
           &mut realm.handle_scope(self.v8_isolate()),
         )
@@ -1970,13 +1965,6 @@ 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/mod.rs b/core/runtime/mod.rs
index 43fdd2f..48d9436 100644
--- a/core/runtime/mod.rs
+++ b/core/runtime/mod.rs
@@ -1,5 +1,5 @@
 // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-mod bindings;
+pub(crate) mod bindings;
 mod jsrealm;
 mod jsruntime;
 #[doc(hidden)]
diff --git a/core/runtime/snapshot_util.rs b/core/runtime/snapshot_util.rs
index c0df226..76dcdaa 100644
--- a/core/runtime/snapshot_util.rs
+++ b/core/runtime/snapshot_util.rs
@@ -187,7 +187,7 @@ pub(crate) fn get_snapshotted_data(
 
   let next_module_id = {
     let info_data: v8::Local<v8::Array> =
-      val.get_index(&mut scope, 1).unwrap().try_into().unwrap();
+      val.get_index(&mut scope, 2).unwrap().try_into().unwrap();
     info_data.length()
   };
 
diff --git a/ops/op2/config.rs b/ops/op2/config.rs
index 6f69641..ba43673 100644
--- a/ops/op2/config.rs
+++ b/ops/op2/config.rs
@@ -13,12 +13,14 @@ pub(crate) struct MacroConfig {
   pub nofast: bool,
   /// Use other ops for the fast alternatives, rather than generating one for this op.
   pub fast_alternatives: Vec<String>,
-  /// Marks an async function (either `async fn` or `fn -> impl Future`)
+  /// Marks an async function (either `async fn` or `fn -> impl Future`).
   pub r#async: bool,
-  /// Marks a lazy async function (async must also be true)
+  /// Marks a lazy async function (async must also be true).
   pub async_lazy: bool,
-  /// Marks a deferred async function (async must also be true)
+  /// Marks a deferred async function (async must also be true).
   pub async_deferred: bool,
+  /// Marks an op as re-entrant (can safely call other ops).
+  pub reentrant: bool,
 }
 
 impl MacroConfig {
@@ -80,6 +82,8 @@ impl MacroConfig {
       } else if flag == "async(deferred)" {
         config.r#async = true;
         config.async_deferred = true;
+      } else if flag == "reentrant" {
+        config.reentrant = true;
       } else {
         return Err(Op2Error::InvalidAttribute(flag));
       }
diff --git a/ops/op2/dispatch_async.rs b/ops/op2/dispatch_async.rs
index 537c2ba..c1b4b66 100644
--- a/ops/op2/dispatch_async.rs
+++ b/ops/op2/dispatch_async.rs
@@ -140,6 +140,9 @@ pub(crate) fn generate_dispatch_async(
     gs_quote!(generator_state(info, slow_function, slow_function_metrics, opctx) => {
       #[inline(always)]
       fn slow_function_impl(#info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(&<Self as deno_core::_ops::Op>::DECL);
+
         #with_scope
         #with_retval
         #with_args
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index c43e9fa..2eacf77 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -502,6 +502,9 @@ pub(crate) fn generate_dispatch_fast(
       _: deno_core::v8::Local<deno_core::v8::Object>,
       #( #fastcall_names: #fastcall_types, )*
     ) -> #output_type {
+      #[cfg(debug_assertions)]
+      let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(&<Self as deno_core::_ops::Op>::DECL);
+
       #with_fast_api_callback_options
       #with_opctx
       #with_js_runtime_state
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 498d70f..28ac8e8 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -139,6 +139,9 @@ pub(crate) fn generate_dispatch_slow(
     gs_quote!(generator_state(opctx, info, slow_function, slow_function_metrics) => {
       #[inline(always)]
       fn slow_function_impl(#info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(&<Self as deno_core::_ops::Op>::DECL);
+
         #with_scope
         #with_retval
         #with_args
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index abcc898..ca3ccbe 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -184,6 +184,7 @@ fn generate_op2(
     generate_dispatch_slow(&config, &mut generator_state, &signature)?
   };
   let is_async = signature.ret_val.is_async();
+  let is_reentrant = config.reentrant;
 
   match (is_async, config.r#async) {
     (true, false) => return Err(Op2Error::ShouldBeAsync),
@@ -252,6 +253,7 @@ fn generate_op2(
       const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         /*name*/ stringify!(#name),
         /*is_async*/ #is_async,
+        /*is_reentrant*/ #is_reentrant,
         /*arg_count*/ #arg_count as u8,
         /*slow_fn*/ Self::#slow_function as _,
         /*slow_fn_metrics*/ Self::#slow_function_metrics as _,


Test patch:
diff --git a/core/modules/tests.rs b/core/modules/tests.rs
index 531f8eb..3a4a917 100644
--- a/core/modules/tests.rs
+++ b/core/modules/tests.rs
@@ -251,7 +251,7 @@ fn test_recursive_load() {
   );
 
   let module_map_rc = runtime.module_map();
-  let modules = module_map_rc.borrow();
+  let modules = module_map_rc;
 
   assert_eq!(
     modules.get_id("file:///a.js", AssertedModuleType::JavaScriptOrWasm),
@@ -268,7 +268,7 @@ fn test_recursive_load() {
     .unwrap();
   assert_eq!(
     modules.get_requested_modules(a_id),
-    Some(&vec![
+    Some(vec![
       ModuleRequest {
         specifier: "file:///b.js".to_string(),
         asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
@@ -281,19 +281,19 @@ fn test_recursive_load() {
   );
   assert_eq!(
     modules.get_requested_modules(b_id),
-    Some(&vec![ModuleRequest {
+    Some(vec![ModuleRequest {
       specifier: "file:///c.js".to_string(),
       asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
     },])
   );
   assert_eq!(
     modules.get_requested_modules(c_id),
-    Some(&vec![ModuleRequest {
+    Some(vec![ModuleRequest {
       specifier: "file:///d.js".to_string(),
       asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
     },])
   );
-  assert_eq!(modules.get_requested_modules(d_id), Some(&vec![]));
+  assert_eq!(modules.get_requested_modules(d_id), Some(vec![]));
 }
 
 #[test]
@@ -331,11 +331,10 @@ fn test_mods() {
 
   assert_eq!(DISPATCH_COUNT.load(Ordering::Relaxed), 0);
 
-  let module_map_rc = runtime.module_map().clone();
+  let module_map = runtime.module_map().clone();
 
   let (mod_a, mod_b) = {
     let scope = &mut runtime.handle_scope();
-    let mut module_map = module_map_rc.borrow_mut();
     let specifier_a = ascii_str!("file:///a.js");
     let mod_a = module_map
       .new_es_module(
@@ -358,7 +357,7 @@ fn test_mods() {
     let imports = module_map.get_requested_modules(mod_a);
     assert_eq!(
       imports,
-      Some(&vec![ModuleRequest {
+      Some(vec![ModuleRequest {
         specifier: "file:///b.js".to_string(),
         asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
       },])
@@ -411,11 +410,10 @@ fn test_json_module() {
     )
     .unwrap();
 
-  let module_map_rc = runtime.module_map().clone();
+  let module_map = runtime.module_map().clone();
 
   let (mod_a, mod_b, mod_c) = {
     let scope = &mut runtime.handle_scope();
-    let mut module_map = module_map_rc.borrow_mut();
     let specifier_a = ascii_str!("file:///a.js");
     let specifier_b = ascii_str!("file:///b.js");
     let mod_a = module_map
@@ -437,7 +435,7 @@ fn test_json_module() {
     let imports = module_map.get_requested_modules(mod_a);
     assert_eq!(
       imports,
-      Some(&vec![ModuleRequest {
+      Some(vec![ModuleRequest {
         specifier: "file:///c.json".to_string(),
         asserted_module_type: AssertedModuleType::Json,
       },])
@@ -462,7 +460,7 @@ fn test_json_module() {
     let imports = module_map.get_requested_modules(mod_b);
     assert_eq!(
       imports,
-      Some(&vec![ModuleRequest {
+      Some(vec![ModuleRequest {
         specifier: "file:///c.json".to_string(),
         asserted_module_type: AssertedModuleType::Json,
       },])
@@ -514,11 +512,10 @@ fn test_validate_import_attributes() {
     ..Default::default()
   });
 
-  let module_map_rc = runtime.module_map().clone();
+  let module_map = runtime.module_map().clone();
 
   {
     let scope = &mut runtime.handle_scope();
-    let mut module_map = module_map_rc.borrow_mut();
     let specifier_a = ascii_str!("file:///a.js");
     let module_err = module_map
       .new_es_module(
@@ -556,9 +553,8 @@ fn test_validate_import_attributes_default() {
 
   {
     let scope = &mut runtime.handle_scope();
-    let mut module_map = module_map_rc.borrow_mut();
     let specifier_a = ascii_str!("file:///a.js");
-    let module_err = module_map
+    let module_err = module_map_rc
       .new_es_module(
         scope,
         true,
@@ -586,9 +582,8 @@ fn test_validate_import_attributes_default() {
 
   {
     let scope = &mut runtime.handle_scope();
-    let mut module_map = module_map_rc.borrow_mut();
     let specifier_a = ascii_str!("file:///a.js");
-    let module_err = module_map
+    let module_err = module_map_rc
       .new_es_module(
         scope,
         true,
@@ -691,32 +686,6 @@ 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
@@ -822,7 +791,7 @@ fn test_circular_load() {
     );
 
     let module_map_rc = runtime.module_map();
-    let modules = module_map_rc.borrow();
+    let modules = module_map_rc;
 
     assert_eq!(
       modules
@@ -835,7 +804,7 @@ fn test_circular_load() {
 
     assert_eq!(
       modules.get_requested_modules(circular1_id),
-      Some(&vec![ModuleRequest {
+      Some(vec![ModuleRequest {
         specifier: "file:///circular2.js".to_string(),
         asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
       }])
@@ -843,7 +812,7 @@ fn test_circular_load() {
 
     assert_eq!(
       modules.get_requested_modules(circular2_id),
-      Some(&vec![ModuleRequest {
+      Some(vec![ModuleRequest {
         specifier: "file:///circular3.js".to_string(),
         asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
       }])
@@ -857,7 +826,7 @@ fn test_circular_load() {
       .unwrap();
     assert_eq!(
       modules.get_requested_modules(circular3_id),
-      Some(&vec![
+      Some(vec![
         ModuleRequest {
           specifier: "file:///circular1.js".to_string(),
           asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
@@ -902,7 +871,7 @@ fn test_redirect_load() {
     );
 
     let module_map_rc = runtime.module_map();
-    let modules = module_map_rc.borrow();
+    let modules = module_map_rc;
 
     assert_eq!(
       modules
@@ -1053,7 +1022,7 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
   );
 
   let module_map_rc = runtime.module_map();
-  let modules = module_map_rc.borrow();
+  let modules = module_map_rc;
 
   assert_eq!(
     modules.get_id(
@@ -1074,7 +1043,7 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
 
   assert_eq!(
     modules.get_requested_modules(main_id),
-    Some(&vec![
+    Some(vec![
       ModuleRequest {
         specifier: "file:///b.js".to_string(),
         asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
@@ -1087,19 +1056,19 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
   );
   assert_eq!(
     modules.get_requested_modules(b_id),
-    Some(&vec![ModuleRequest {
+    Some(vec![ModuleRequest {
       specifier: "file:///c.js".to_string(),
       asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
     }])
   );
   assert_eq!(
     modules.get_requested_modules(c_id),
-    Some(&vec![ModuleRequest {
+    Some(vec![ModuleRequest {
       specifier: "file:///d.js".to_string(),
       asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
     }])
   );
-  assert_eq!(modules.get_requested_modules(d_id), Some(&vec![]));
+  assert_eq!(modules.get_requested_modules(d_id), Some(vec![]));
 }
 
 #[test]
diff --git a/core/runtime/tests/jsrealm.rs b/core/runtime/tests/jsrealm.rs
index 479ae29..db6f83d 100644
--- a/core/runtime/tests/jsrealm.rs
+++ b/core/runtime/tests/jsrealm.rs
@@ -732,101 +732,6 @@ async fn test_realms_concurrent_module_evaluations() {
   assert!(matches!(other_realm_receiver.await, Ok(Ok(()))));
 }
 
-// Make sure that loading and evaluating dynamic imported modules in different
-// realms at the same time works.
-#[tokio::test]
-async fn test_realm_concurrent_dynamic_imports() {
-  let loader = Rc::new(StaticModuleLoader::with(
-    Url::parse("file:///test.js").unwrap(),
-    ascii_str!(r#"await Deno.core.opAsync("op_wait");"#),
-  ));
-
-  #[op2(async)]
-  async fn op_wait(op_state: Rc<RefCell<OpState>>) {
-    let (sender, receiver) = oneshot::channel::<()>();
-    op_state
-      .borrow_mut()
-      .borrow_mut::<Vec<oneshot::Sender<()>>>()
-      .push(sender);
-    receiver.await.unwrap();
-  }
-  deno_core::extension!(test_ext, ops = [op_wait]);
-
-  let mut runtime = JsRuntime::new(RuntimeOptions {
-    extensions: vec![test_ext::init_ops()],
-    module_loader: Some(loader.clone()),
-    ..Default::default()
-  });
-  runtime
-    .op_state()
-    .borrow_mut()
-    .put::<Vec<oneshot::Sender<()>>>(vec![]);
-
-  let main_realm = runtime.main_realm();
-  let other_realm = runtime
-    .create_realm(CreateRealmOptions {
-      module_loader: Some(loader.clone()),
-    })
-    .unwrap();
-
-  let main_realm_promise = {
-    let global = main_realm
-      .execute_script_static(
-        runtime.v8_isolate(),
-        "",
-        r#"import("file:///test.js")"#,
-      )
-      .unwrap();
-    let scope = &mut main_realm.handle_scope(runtime.v8_isolate());
-    let local = v8::Local::new(scope, global);
-    let promise = v8::Local::<v8::Promise>::try_from(local).unwrap();
-    assert_eq!(promise.state(), v8::PromiseState::Pending);
-    v8::Global::new(scope, promise)
-  };
-  let other_realm_promise = {
-    let global = other_realm
-      .execute_script_static(
-        runtime.v8_isolate(),
-        "",
-        r#"import("file:///test.js")"#,
-      )
-      .unwrap();
-    let scope = &mut other_realm.handle_scope(runtime.v8_isolate());
-    let local = v8::Local::new(scope, global);
-    let promise = v8::Local::<v8::Promise>::try_from(local).unwrap();
-    assert_eq!(promise.state(), v8::PromiseState::Pending);
-    v8::Global::new(scope, promise)
-  };
-
-  poll_fn(|cx| {
-    let res = runtime.poll_event_loop(cx, false);
-    assert!(matches!(res, Poll::Pending));
-    Poll::Ready(())
-  })
-  .await;
-
-  // Resolve the promises
-  {
-    let senders = runtime
-      .op_state()
-      .borrow_mut()
-      .take::<Vec<oneshot::Sender<()>>>();
-    for sender in senders {
-      sender.send(()).unwrap();
-    }
-  }
-
-  runtime.run_event_loop(false).await.unwrap();
-  assert_eq!(
-    main_realm_promise.open(runtime.v8_isolate()).state(),
-    v8::PromiseState::Fulfilled
-  );
-  assert_eq!(
-    other_realm_promise.open(runtime.v8_isolate()).state(),
-    v8::PromiseState::Fulfilled
-  );
-}
-
 #[test]
 fn es_snapshot() {
   let startup_data = {
diff --git a/core/runtime/tests/misc.rs b/core/runtime/tests/misc.rs
index 1f323cb..d5427f1 100644
--- a/core/runtime/tests/misc.rs
+++ b/core/runtime/tests/misc.rs
@@ -339,7 +339,6 @@ async fn wasm_streaming_op_invocation_in_import() {
                              WebAssembly.instantiateStreaming(bytes, {
                                env: {
                                  get data() {
-                                   Deno.core.ops.op_resources();
                                    return new WebAssembly.Global({ value: "i64", mutable: false }, 42n);
                                  }
                                }
diff --git a/core/runtime/tests/snapshot.rs b/core/runtime/tests/snapshot.rs
index 1664ce0..c087f53 100644
--- a/core/runtime/tests/snapshot.rs
+++ b/core/runtime/tests/snapshot.rs
@@ -3,9 +3,7 @@ use crate::extensions::Op;
 use crate::modules::AssertedModuleType;
 use crate::modules::LoggingModuleLoader;
 use crate::modules::ModuleInfo;
-use crate::modules::ModuleLoadId;
 use crate::modules::ModuleType;
-use crate::modules::SymbolicModule;
 use crate::*;
 use anyhow::Error;
 use std::borrow::Cow;
@@ -168,34 +166,6 @@ fn es_snapshot() {
     }
   }
 
-  fn assert_module_map(runtime: &mut JsRuntime, modules: &Vec<ModuleInfo>) {
-    let module_map_rc = runtime.module_map();
-    let module_map = module_map_rc.borrow();
-    assert_eq!(module_map.handles.len(), modules.len());
-    assert_eq!(module_map.info.len(), modules.len());
-    assert_eq!(
-      module_map.by_name(AssertedModuleType::Json).len()
-        + module_map
-          .by_name(AssertedModuleType::JavaScriptOrWasm)
-          .len(),
-      modules.len()
-    );
-
-    assert_eq!(module_map.next_load_id, (modules.len() + 1) as ModuleLoadId);
-
-    for info in modules {
-      assert!(module_map.handles.get(info.id).is_some());
-      assert_eq!(module_map.info.get(info.id).unwrap(), info);
-      assert_eq!(
-        module_map
-          .by_name(AssertedModuleType::JavaScriptOrWasm)
-          .get(&info.name)
-          .unwrap(),
-        &SymbolicModule::Mod(info.id)
-      );
-    }
-  }
-
   #[op2]
   #[string]
   fn op_test() -> Result<String, Error> {
@@ -234,7 +204,7 @@ fn es_snapshot() {
 
   modules.extend((1..200).map(|i| create_module(&mut runtime, i, false)));
 
-  assert_module_map(&mut runtime, &modules);
+  runtime.module_map().assert_module_map(&modules);
 
   let snapshot = runtime.snapshot();
 
@@ -248,12 +218,12 @@ fn es_snapshot() {
     ..Default::default()
   });
 
-  assert_module_map(&mut runtime2, &modules);
+  runtime2.module_map().assert_module_map(&modules);
 
   modules.extend((200..400).map(|i| create_module(&mut runtime2, i, false)));
   modules.push(create_module(&mut runtime2, 400, true));
 
-  assert_module_map(&mut runtime2, &modules);
+  runtime2.module_map().assert_module_map(&modules);
 
   let snapshot2 = runtime2.snapshot();
 
@@ -267,7 +237,7 @@ fn es_snapshot() {
     ..Default::default()
   });
 
-  assert_module_map(&mut runtime3, &modules);
+  runtime3.module_map().assert_module_map(&modules);
 
   let source_code = r#"(async () => {
     const mod = await import("file:///400.js");
diff --git a/ops/op2/test_cases/async/async_arg_return.out b/ops/op2/test_cases/async/async_arg_return.out
index 664ea49..3edad7a 100644
--- a/ops/op2/test_cases/async/async_arg_return.out
+++ b/ops/op2/test_cases/async/async_arg_return.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_arg_return_result.out b/ops/op2/test_cases/async/async_arg_return_result.out
index e392e2e..5f78461 100644
--- a/ops/op2/test_cases/async/async_arg_return_result.out
+++ b/ops/op2/test_cases/async/async_arg_return_result.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_deferred.out b/ops/op2/test_cases/async/async_deferred.out
index 6cd73c2..0f7c703 100644
--- a/ops/op2/test_cases/async/async_deferred.out
+++ b/ops/op2/test_cases/async/async_deferred.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async_deferred {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async_deferred),
         true,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -68,6 +69,10 @@ impl op_async_deferred {
         promise_id: i32,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -87,6 +92,10 @@ impl op_async_deferred {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_jsbuffer.out b/ops/op2/test_cases/async/async_jsbuffer.out
index cc0f983..67bd92b 100644
--- a/ops/op2/test_cases/async/async_jsbuffer.out
+++ b/ops/op2/test_cases/async/async_jsbuffer.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async_v8_buffer {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async_v8_buffer),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async_v8_buffer {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/async/async_lazy.out b/ops/op2/test_cases/async/async_lazy.out
index f75039b..ea1174b 100644
--- a/ops/op2/test_cases/async/async_lazy.out
+++ b/ops/op2/test_cases/async/async_lazy.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async_lazy {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async_lazy),
         true,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -68,6 +69,10 @@ impl op_async_lazy {
         promise_id: i32,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -87,6 +92,10 @@ impl op_async_lazy {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_opstate.out b/ops/op2/test_cases/async/async_opstate.out
index eac0524..8ad7ffb 100644
--- a/ops/op2/test_cases/async/async_opstate.out
+++ b/ops/op2/test_cases/async/async_opstate.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async_opstate {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async_opstate),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async_opstate {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_result.out b/ops/op2/test_cases/async/async_result.out
index 2358320..a490a9b 100644
--- a/ops/op2/test_cases/async/async_result.out
+++ b/ops/op2/test_cases/async/async_result.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async),
         true,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_result_impl.out b/ops/op2/test_cases/async/async_result_impl.out
index 0992d11..9991173 100644
--- a/ops/op2/test_cases/async/async_result_impl.out
+++ b/ops/op2/test_cases/async/async_result_impl.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async_result_impl {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async_result_impl),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async_result_impl {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_result_smi.out b/ops/op2/test_cases/async/async_result_smi.out
index b7aa233..9ff61e3 100644
--- a/ops/op2/test_cases/async/async_result_smi.out
+++ b/ops/op2/test_cases/async/async_result_smi.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/async/async_v8_global.out b/ops/op2/test_cases/async/async_v8_global.out
index d8e742b..6c2e7af 100644
--- a/ops/op2/test_cases/async/async_v8_global.out
+++ b/ops/op2/test_cases/async/async_v8_global.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async_v8_global {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async_v8_global),
         true,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async_v8_global {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/async/async_void.out b/ops/op2/test_cases/async/async_void.out
index b18cfe4..cd29d21 100644
--- a/ops/op2/test_cases/async/async_void.out
+++ b/ops/op2/test_cases/async/async_void.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_async {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_async),
         true,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_async {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/add.out b/ops/op2/test_cases/sync/add.out
index dd7c890..54e9cdc 100644
--- a/ops/op2/test_cases/sync/add.out
+++ b/ops/op2/test_cases/sync/add.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_add {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_add),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -69,6 +70,10 @@ impl op_add {
         arg0: u32,
         arg1: u32,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = arg0 as _;
             let arg1 = arg1 as _;
@@ -78,6 +83,10 @@ impl op_add {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/add_options.out b/ops/op2/test_cases/sync/add_options.out
index bba9299..19ec8b2 100644
--- a/ops/op2/test_cases/sync/add_options.out
+++ b/ops/op2/test_cases/sync/add_options.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_test_add_option {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_test_add_option),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_test_add_option {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/bigint.out b/ops/op2/test_cases/sync/bigint.out
index 4199e74..47d6d56 100644
--- a/ops/op2/test_cases/sync/bigint.out
+++ b/ops/op2/test_cases/sync/bigint.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_bigint {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_bigint),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -63,11 +64,19 @@ impl op_bigint {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> u64 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/bool.out b/ops/op2/test_cases/sync/bool.out
index 68c5ab8..203f1f8 100644
--- a/ops/op2/test_cases/sync/bool.out
+++ b/ops/op2/test_cases/sync/bool.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_bool {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_bool),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -67,6 +68,10 @@ impl op_bool {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         arg0: bool,
     ) -> bool {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = arg0 as _;
             Self::call(arg0)
@@ -75,6 +80,10 @@ impl op_bool {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/bool_result.out b/ops/op2/test_cases/sync/bool_result.out
index 17e7792..97cbad4 100644
--- a/ops/op2/test_cases/sync/bool_result.out
+++ b/ops/op2/test_cases/sync/bool_result.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_bool {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_bool),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -68,6 +69,10 @@ impl op_bool {
         arg0: bool,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> bool {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -93,6 +98,10 @@ impl op_bool {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/buffers.out b/ops/op2/test_cases/sync/buffers.out
index 680ecfb..9f45eb1 100644
--- a/ops/op2/test_cases/sync/buffers.out
+++ b/ops/op2/test_cases/sync/buffers.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_buffers {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers),
         false,
+        false,
         4usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -86,6 +87,10 @@ impl op_buffers {
         arg2: *mut deno_core::v8::fast_api::FastApiTypedArray<u8>,
         arg3: *mut deno_core::v8::fast_api::FastApiTypedArray<u8>,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
@@ -129,6 +134,10 @@ impl op_buffers {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -262,6 +271,7 @@ impl deno_core::_ops::Op for op_buffers_32 {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers_32),
         false,
+        false,
         4usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -341,6 +351,10 @@ impl op_buffers_32 {
         arg2: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
         arg3: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
@@ -384,6 +398,10 @@ impl op_buffers_32 {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -517,6 +535,7 @@ impl deno_core::_ops::Op for op_buffers_option {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers_option),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -534,6 +553,10 @@ impl op_buffers_option {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/buffers_copy.out b/ops/op2/test_cases/sync/buffers_copy.out
index 97ce131..5a78f2f 100644
--- a/ops/op2/test_cases/sync/buffers_copy.out
+++ b/ops/op2/test_cases/sync/buffers_copy.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_buffers {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers),
         false,
+        false,
         3usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -82,6 +83,10 @@ impl op_buffers {
         arg1: *mut deno_core::v8::fast_api::FastApiTypedArray<u8>,
         arg2: *mut deno_core::v8::fast_api::FastApiTypedArray<u8>,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
@@ -110,6 +115,10 @@ impl op_buffers {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -217,6 +226,7 @@ impl deno_core::_ops::Op for op_buffers_32 {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers_32),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -288,6 +298,10 @@ impl op_buffers_32 {
         arg0: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
         arg1: *mut deno_core::v8::fast_api::FastApiTypedArray<u32>,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = unsafe {
                 deno_core::v8::fast_api::FastApiTypedArray::get_storage_from_pointer_if_aligned(
@@ -309,6 +323,10 @@ impl op_buffers_32 {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/buffers_out.out b/ops/op2/test_cases/sync/buffers_out.out
index 2eae8a3..c67dc33 100644
--- a/ops/op2/test_cases/sync/buffers_out.out
+++ b/ops/op2/test_cases/sync/buffers_out.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_buffers {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_buffers {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
@@ -110,6 +115,7 @@ impl deno_core::_ops::Op for op_buffers_option {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_buffers_option),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -127,6 +133,10 @@ impl op_buffers_option {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
@@ -220,6 +230,7 @@ impl deno_core::_ops::Op for op_arraybuffers {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_arraybuffers),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -237,6 +248,10 @@ impl op_arraybuffers {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/cfg.out b/ops/op2/test_cases/sync/cfg.out
index d1193ce..0fed2e9 100644
--- a/ops/op2/test_cases/sync/cfg.out
+++ b/ops/op2/test_cases/sync/cfg.out
@@ -9,6 +9,7 @@ impl deno_core::_ops::Op for op_maybe_windows {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_maybe_windows),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -65,11 +66,19 @@ impl op_maybe_windows {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -122,6 +131,7 @@ impl deno_core::_ops::Op for op_maybe_windows {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_maybe_windows),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -178,11 +188,19 @@ impl op_maybe_windows {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/clippy_allow.out b/ops/op2/test_cases/sync/clippy_allow.out
index 37eae1b..a4ca2f3 100644
--- a/ops/op2/test_cases/sync/clippy_allow.out
+++ b/ops/op2/test_cases/sync/clippy_allow.out
@@ -9,6 +9,7 @@ impl deno_core::_ops::Op for op_extra_annotation {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_extra_annotation),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -65,11 +66,19 @@ impl op_extra_annotation {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -120,6 +129,7 @@ impl deno_core::_ops::Op for op_clippy_internal {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_clippy_internal),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -176,11 +186,19 @@ impl op_clippy_internal {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/doc_comment.out b/ops/op2/test_cases/sync/doc_comment.out
index dbc4f79..e2ea1a1 100644
--- a/ops/op2/test_cases/sync/doc_comment.out
+++ b/ops/op2/test_cases/sync/doc_comment.out
@@ -8,6 +8,7 @@ impl deno_core::_ops::Op for op_has_doc_comment {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_has_doc_comment),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -64,11 +65,19 @@ impl op_has_doc_comment {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/fast_alternative.out b/ops/op2/test_cases/sync/fast_alternative.out
index 63b8bb2..35faefa 100644
--- a/ops/op2/test_cases/sync/fast_alternative.out
+++ b/ops/op2/test_cases/sync/fast_alternative.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_slow {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_slow),
         false,
+        false,
         3usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_slow {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
@@ -107,6 +112,7 @@ impl deno_core::_ops::Op for op_fast {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_fast),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -169,6 +175,10 @@ impl op_fast {
         arg0: u32,
         arg1: u32,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = arg0 as _;
             let arg1 = arg1 as _;
@@ -178,6 +188,10 @@ impl op_fast {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -261,6 +275,7 @@ impl<T: Trait> deno_core::_ops::Op for op_slow_generic<T> {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_slow_generic),
         false,
+        false,
         3usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -278,6 +293,10 @@ impl<T: Trait> op_slow_generic<T> {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
@@ -361,6 +380,7 @@ impl<T: Trait> deno_core::_ops::Op for op_fast_generic<T> {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_fast_generic),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -423,6 +443,10 @@ impl<T: Trait> op_fast_generic<T> {
         arg0: u32,
         arg1: u32,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = arg0 as _;
             let arg1 = arg1 as _;
@@ -432,6 +456,10 @@ impl<T: Trait> op_fast_generic<T> {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/generics.out b/ops/op2/test_cases/sync/generics.out
index afaabea..9db629c 100644
--- a/ops/op2/test_cases/sync/generics.out
+++ b/ops/op2/test_cases/sync/generics.out
@@ -7,6 +7,7 @@ impl<T: Trait> deno_core::_ops::Op for op_generics<T> {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_generics),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -63,11 +64,19 @@ impl<T: Trait> op_generics<T> {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -116,6 +125,7 @@ impl<T: Trait + 'static> deno_core::_ops::Op for op_generics_static<T> {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_generics_static),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -172,11 +182,19 @@ impl<T: Trait + 'static> op_generics_static<T> {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -225,6 +243,7 @@ impl<T: Trait + 'static> deno_core::_ops::Op for op_generics_static_where<T> {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_generics_static_where),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -281,11 +300,19 @@ impl<T: Trait + 'static> op_generics_static_where<T> {
     }
     #[allow(clippy::too_many_arguments)]
     fn v8_fn_ptr_fast(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = { Self::call() };
         result as _
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/nofast.out b/ops/op2/test_cases/sync/nofast.out
index 726eb89..156956d 100644
--- a/ops/op2/test_cases/sync/nofast.out
+++ b/ops/op2/test_cases/sync/nofast.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_nofast {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_nofast),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_nofast {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/op_state_attr.out b/ops/op2/test_cases/sync/op_state_attr.out
index 9c7ba3e..c84ff96 100644
--- a/ops/op2/test_cases/sync/op_state_attr.out
+++ b/ops/op2/test_cases/sync/op_state_attr.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_state_rc {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_state_rc),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -66,6 +67,10 @@ impl op_state_rc {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -84,6 +89,10 @@ impl op_state_rc {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/op_state_rc.out b/ops/op2/test_cases/sync/op_state_rc.out
index 446e105..63ac500 100644
--- a/ops/op2/test_cases/sync/op_state_rc.out
+++ b/ops/op2/test_cases/sync/op_state_rc.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_state_rc {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_state_rc),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -66,6 +67,10 @@ impl op_state_rc {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -81,6 +86,10 @@ impl op_state_rc {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/op_state_ref.out b/ops/op2/test_cases/sync/op_state_ref.out
index 076bf2e..716c935 100644
--- a/ops/op2/test_cases/sync/op_state_ref.out
+++ b/ops/op2/test_cases/sync/op_state_ref.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_state_ref {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_state_ref),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -66,6 +67,10 @@ impl op_state_ref {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -81,6 +86,10 @@ impl op_state_ref {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -140,6 +149,7 @@ impl deno_core::_ops::Op for op_state_mut {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_state_mut),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -199,6 +209,10 @@ impl op_state_mut {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -214,6 +228,10 @@ impl op_state_mut {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -273,6 +291,7 @@ impl deno_core::_ops::Op for op_state_and_v8 {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_state_and_v8),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -290,6 +309,10 @@ impl op_state_and_v8 {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -366,6 +389,7 @@ impl deno_core::_ops::Op for op_state_and_v8_local {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_state_and_v8_local),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -427,6 +451,10 @@ impl op_state_and_v8_local {
         arg1: deno_core::v8::Local<deno_core::v8::Value>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -449,6 +477,10 @@ impl op_state_and_v8_local {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/result_external.out b/ops/op2/test_cases/sync/result_external.out
index 4298a7c..df4fd35 100644
--- a/ops/op2/test_cases/sync/result_external.out
+++ b/ops/op2/test_cases/sync/result_external.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_external_with_result {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_external_with_result),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -66,6 +67,10 @@ impl op_external_with_result {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> *mut ::std::ffi::c_void {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -88,6 +93,10 @@ impl op_external_with_result {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/result_primitive.out b/ops/op2/test_cases/sync/result_primitive.out
index fa42cae..a6b3010 100644
--- a/ops/op2/test_cases/sync/result_primitive.out
+++ b/ops/op2/test_cases/sync/result_primitive.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_u32_with_result {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_u32_with_result),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -66,6 +67,10 @@ impl op_u32_with_result {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -88,6 +93,10 @@ impl op_u32_with_result {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/result_scope.out b/ops/op2/test_cases/sync/result_scope.out
index eb2525c..2ae891c 100644
--- a/ops/op2/test_cases/sync/result_scope.out
+++ b/ops/op2/test_cases/sync/result_scope.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_void_with_result {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_void_with_result),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_void_with_result {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/result_void.out b/ops/op2/test_cases/sync/result_void.out
index 9577868..b1015d3 100644
--- a/ops/op2/test_cases/sync/result_void.out
+++ b/ops/op2/test_cases/sync/result_void.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_void_with_result {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_void_with_result),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -66,6 +67,10 @@ impl op_void_with_result {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let opctx = unsafe {
             &*(deno_core::v8::Local::<
@@ -88,6 +93,10 @@ impl op_void_with_result {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/serde_v8.out b/ops/op2/test_cases/sync/serde_v8.out
index fbedf89..77ad73c 100644
--- a/ops/op2/test_cases/sync/serde_v8.out
+++ b/ops/op2/test_cases/sync/serde_v8.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_serde_v8 {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_serde_v8),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_serde_v8 {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/smi.out b/ops/op2/test_cases/sync/smi.out
index d9877db..dcda352 100644
--- a/ops/op2/test_cases/sync/smi.out
+++ b/ops/op2/test_cases/sync/smi.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_smi_unsigned_return {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_smi_unsigned_return),
         false,
+        false,
         4usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -80,6 +81,10 @@ impl op_smi_unsigned_return {
         arg2: i32,
         arg3: i32,
     ) -> i32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = arg0 as _;
             let arg1 = arg1 as _;
@@ -91,6 +96,10 @@ impl op_smi_unsigned_return {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -208,6 +217,7 @@ impl deno_core::_ops::Op for op_smi_signed_return {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_smi_signed_return),
         false,
+        false,
         4usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -281,6 +291,10 @@ impl op_smi_signed_return {
         arg2: i32,
         arg3: i32,
     ) -> i32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = arg0 as _;
             let arg1 = arg1 as _;
@@ -292,6 +306,10 @@ impl op_smi_signed_return {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
@@ -409,6 +427,7 @@ impl deno_core::_ops::Op for op_smi_option {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_smi_option),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -426,6 +445,10 @@ impl op_smi_option {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/string_cow.out b/ops/op2/test_cases/sync/string_cow.out
index 5355f2a..2799ecc 100644
--- a/ops/op2/test_cases/sync/string_cow.out
+++ b/ops/op2/test_cases/sync/string_cow.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_string_cow {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_cow),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -67,6 +68,10 @@ impl op_string_cow {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let mut arg0_temp: [::std::mem::MaybeUninit<
                 u8,
@@ -81,6 +86,10 @@ impl op_string_cow {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/string_onebyte.out b/ops/op2/test_cases/sync/string_onebyte.out
index bd474c3..12e3a4f 100644
--- a/ops/op2/test_cases/sync/string_onebyte.out
+++ b/ops/op2/test_cases/sync/string_onebyte.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_string_onebyte {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_onebyte),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -67,6 +68,10 @@ impl op_string_onebyte {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = deno_core::_ops::to_cow_byte_ptr(unsafe { &mut *arg0 });
             Self::call(arg0)
@@ -75,6 +80,10 @@ impl op_string_onebyte {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/string_option_return.out b/ops/op2/test_cases/sync/string_option_return.out
index 9fca320..0777463 100644
--- a/ops/op2/test_cases/sync/string_option_return.out
+++ b/ops/op2/test_cases/sync/string_option_return.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_string_return {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_return),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_string_return {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/string_owned.out b/ops/op2/test_cases/sync/string_owned.out
index 027bf9a..e3cb0db 100644
--- a/ops/op2/test_cases/sync/string_owned.out
+++ b/ops/op2/test_cases/sync/string_owned.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_string_owned {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_owned),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -67,6 +68,10 @@ impl op_string_owned {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let arg0 = deno_core::_ops::to_string_ptr(unsafe { &mut *arg0 });
             Self::call(arg0)
@@ -75,6 +80,10 @@ impl op_string_owned {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/string_ref.out b/ops/op2/test_cases/sync/string_ref.out
index fe2db25..88f6a03 100644
--- a/ops/op2/test_cases/sync/string_ref.out
+++ b/ops/op2/test_cases/sync/string_ref.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_string_owned {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_owned),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -67,6 +68,10 @@ impl op_string_owned {
         _: deno_core::v8::Local<deno_core::v8::Object>,
         arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
     ) -> u32 {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let result = {
             let mut arg0_temp: [::std::mem::MaybeUninit<
                 u8,
@@ -81,6 +86,10 @@ impl op_string_owned {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/string_return.out b/ops/op2/test_cases/sync/string_return.out
index dd452b1..d05e240 100644
--- a/ops/op2/test_cases/sync/string_return.out
+++ b/ops/op2/test_cases/sync/string_return.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_string_return {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_return),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_string_return {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
@@ -87,6 +92,7 @@ impl deno_core::_ops::Op for op_string_return_ref {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_return_ref),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -104,6 +110,10 @@ impl op_string_return_ref {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
@@ -167,6 +177,7 @@ impl deno_core::_ops::Op for op_string_return_cow {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_string_return_cow),
         false,
+        false,
         0usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -184,6 +195,10 @@ impl op_string_return_cow {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/v8_global.out b/ops/op2/test_cases/sync/v8_global.out
index 535b16e..f27e75a 100644
--- a/ops/op2/test_cases/sync/v8_global.out
+++ b/ops/op2/test_cases/sync/v8_global.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_global {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_global),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_global {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/v8_handlescope.out b/ops/op2/test_cases/sync/v8_handlescope.out
index c9ecdf7..9eeebc6 100644
--- a/ops/op2/test_cases/sync/v8_handlescope.out
+++ b/ops/op2/test_cases/sync/v8_handlescope.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_handlescope {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_handlescope),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_handlescope {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
diff --git a/ops/op2/test_cases/sync/v8_lifetime.out b/ops/op2/test_cases/sync/v8_lifetime.out
index 2483598..ed886e6 100644
--- a/ops/op2/test_cases/sync/v8_lifetime.out
+++ b/ops/op2/test_cases/sync/v8_lifetime.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_v8_lifetime {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_v8_lifetime),
         false,
+        false,
         1usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_v8_lifetime {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/v8_ref_option.out b/ops/op2/test_cases/sync/v8_ref_option.out
index e4598a7..9ffa202 100644
--- a/ops/op2/test_cases/sync/v8_ref_option.out
+++ b/ops/op2/test_cases/sync/v8_ref_option.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_v8_lifetime {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_v8_lifetime),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -70,6 +71,10 @@ impl op_v8_lifetime {
         arg1: deno_core::v8::Local<deno_core::v8::Value>,
         fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
     ) -> () {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
         let result = {
             let Ok(mut arg0) = deno_core::_ops::v8_try_convert_option::<
@@ -98,6 +103,10 @@ impl op_v8_lifetime {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });
diff --git a/ops/op2/test_cases/sync/v8_string.out b/ops/op2/test_cases/sync/v8_string.out
index 6fbe0fa..94dbd60 100644
--- a/ops/op2/test_cases/sync/v8_string.out
+++ b/ops/op2/test_cases/sync/v8_string.out
@@ -7,6 +7,7 @@ impl deno_core::_ops::Op for op_v8_string {
     const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal_op2(
         stringify!(op_v8_string),
         false,
+        false,
         2usize as u8,
         Self::v8_fn_ptr as _,
         Self::v8_fn_ptr_metrics as _,
@@ -24,6 +25,10 @@ impl op_v8_string {
     }
     #[inline(always)]
     fn slow_function_impl(info: *const deno_core::v8::FunctionCallbackInfo) -> usize {
+        #[cfg(debug_assertions)]
+        let _reentrancy_check_guard = deno_core::_ops::reentrancy_check(
+            &<Self as deno_core::_ops::Op>::DECL,
+        );
         let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
             &*info
         });