Repository: denoland/deno_core (rust)

Issue:
ModuleMap::new_synthetic_module should instantiate the module
Synthetic modules don't have dependencies (they are standalone) so these can be instantiated
immediately after creation.

Pull request:
refactor: Synthetic module gets instantiated on creation
Closes #517

Patch:
diff --git a/core/lib.rs b/core/lib.rs
index 0cfd305..249cff9 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -98,6 +98,7 @@ pub use crate::module_specifier::resolve_url;
 pub use crate::module_specifier::resolve_url_or_path;
 pub use crate::module_specifier::ModuleResolutionError;
 pub use crate::module_specifier::ModuleSpecifier;
+pub use crate::modules::CustomModuleEvaluationCtx;
 pub use crate::modules::CustomModuleEvaluationKind;
 pub use crate::modules::ExtModuleLoaderCb;
 pub use crate::modules::FsModuleLoader;
diff --git a/core/main.rs b/core/main.rs
index 7848875..fda7240 100644
--- a/core/main.rs
+++ b/core/main.rs
@@ -4,6 +4,7 @@ use anyhow::anyhow;
 use anyhow::Context;
 use deno_core::anyhow::Error;
 use deno_core::v8;
+use deno_core::CustomModuleEvaluationCtx;
 use deno_core::CustomModuleEvaluationKind;
 use deno_core::FastString;
 use deno_core::FsModuleLoader;
@@ -15,6 +16,7 @@ use std::rc::Rc;
 
 fn custom_module_evaluation_cb(
   scope: &mut v8::HandleScope,
+  _ctx: CustomModuleEvaluationCtx,
   module_type: Cow<'_, str>,
   module_name: &FastString,
   code: ModuleSourceCode,
diff --git a/core/modules/map.rs b/core/modules/map.rs
index aab8fa7..385504e 100644
--- a/core/modules/map.rs
+++ b/core/modules/map.rs
@@ -324,12 +324,16 @@ impl ModuleMap {
           ))));
         };
 
+        let ctx = JsRealm::get_custom_module_evaluation_ctx(scope);
+
         // TODO(bartlomieju): creating a global just to create a local from it
         // seems superfluous. However, changing `CustomModuleEvaluationCb` to have
         // a lifetime will have a viral effect and required `JsRuntimeOptions`
         // to have a callback as well as `JsRuntime`.
+
         let module_evaluation_kind = custom_evaluation_cb(
           scope,
+          ctx,
           module_type.clone(),
           &module_url_found,
           code,
@@ -381,10 +385,9 @@ impl ModuleMap {
     Ok(module_id)
   }
 
-  // TODO(bartlomieju): this method should instantiate the module - these have
-  // no dependencies so can be instantiated immediately.
-  /// Creates a "synthetic module", that contains only a single, "default" export,
-  /// that returns the provided value.
+  /// Creates a "synthetic module", that contains only a single, "default" export.
+  ///
+  /// The module gets instantiated and its ID is returned.
   pub fn new_synthetic_module(
     &self,
     scope: &mut v8::HandleScope,
@@ -437,6 +440,9 @@ impl ModuleMap {
       vec![],
     );
 
+    // Synthetic modules have no imports so their instantation must never fail.
+    self.instantiate_module(scope, id).unwrap();
+
     Ok(id)
   }
 
diff --git a/core/modules/mod.rs b/core/modules/mod.rs
index 6f3dd69..1f67661 100644
--- a/core/modules/mod.rs
+++ b/core/modules/mod.rs
@@ -11,6 +11,7 @@ use serde::Serialize;
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::future::Future;
+use std::rc::Rc;
 use std::sync::Arc;
 
 mod loaders;
@@ -124,11 +125,75 @@ pub(crate) fn default_import_meta_resolve_cb(
 pub type ValidateImportAttributesCb =
   Box<dyn Fn(&mut v8::HandleScope, &HashMap<String, String>)>;
 
+pub struct CustomModuleEvaluationCtx {
+  pub(crate) web_assembly_module_imports_fn: Rc<v8::Global<v8::Function>>,
+  pub(crate) web_assembly_module_exports_fn: Rc<v8::Global<v8::Function>>,
+}
+
+#[derive(Debug)]
+pub struct WasmModuleAnalysis {
+  pub imports: Vec<WebAssemblyImportDescriptor>,
+  pub exports: Vec<WebAssemblyExportDescriptor>,
+}
+#[derive(Debug, Deserialize)]
+pub struct WebAssemblyImportDescriptor {
+  pub module: String,
+  pub name: String,
+  pub kind: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct WebAssemblyExportDescriptor {
+  pub name: String,
+  pub kind: String,
+}
+
+impl CustomModuleEvaluationCtx {
+  pub fn analyze_wasm_module(
+    &self,
+    scope: &mut v8::HandleScope,
+    value: v8::Local<v8::Value>,
+  ) -> Result<WasmModuleAnalysis, Error> {
+    let web_assembly_module_imports_fn: v8::Local<v8::Function> =
+      v8::Local::new(scope, self.web_assembly_module_imports_fn.as_ref());
+    let web_assembly_module_exports_fn: v8::Local<v8::Function> =
+      v8::Local::new(scope, self.web_assembly_module_exports_fn.as_ref());
+    let receiver = v8::undefined(scope);
+
+    let tc_scope = &mut v8::TryCatch::new(scope);
+
+    let import_result =
+      web_assembly_module_imports_fn.call(tc_scope, receiver.into(), &[value]);
+
+    if tc_scope.has_caught() {
+      let exception = tc_scope.exception().unwrap();
+      return exception_to_err_result(tc_scope, exception, false, false);
+    }
+
+    let imports: Vec<WebAssemblyImportDescriptor> =
+      serde_v8::from_v8(tc_scope, import_result.unwrap()).unwrap();
+
+    let export_result =
+      web_assembly_module_exports_fn.call(tc_scope, receiver.into(), &[value]);
+
+    if tc_scope.has_caught() {
+      let exception = tc_scope.exception().unwrap();
+      return exception_to_err_result(tc_scope, exception, false, false);
+    }
+
+    let exports: Vec<WebAssemblyExportDescriptor> =
+      serde_v8::from_v8(tc_scope, export_result.unwrap()).unwrap();
+
+    Ok(WasmModuleAnalysis { imports, exports })
+  }
+}
+
 /// Callback to validate import attributes. If the validation fails and exception
 /// should be thrown using `scope.throw_exception()`.
 pub type CustomModuleEvaluationCb = Box<
   dyn Fn(
     &mut v8::HandleScope,
+    CustomModuleEvaluationCtx,
     Cow<'_, str>,
     &FastString,
     ModuleSourceCode,
diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs
index a4370d0..4477399 100644
--- a/core/runtime/bindings.rs
+++ b/core/runtime/bindings.rs
@@ -154,6 +154,10 @@ pub mod v8_static_strings {
   pub static FILENAME: &[u8] = b"filename";
   pub static DIRNAME: &[u8] = b"dirname";
   pub static SET_UP_ASYNC_STUB: &[u8] = b"setUpAsyncStub";
+  pub static WEBASSEMBLY: &[u8] = b"WebAssembly";
+  pub static MODULE: &[u8] = b"Module";
+  pub static IMPORTS: &[u8] = b"imports";
+  pub static EXPORTS: &[u8] = b"exports";
 }
 
 /// Create an object on the `globalThis` that looks like this:
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
index fb0f00d..48dad9e 100644
--- a/core/runtime/jsrealm.rs
+++ b/core/runtime/jsrealm.rs
@@ -5,6 +5,7 @@ use super::exception_state::ExceptionState;
 use super::op_driver::OpDriver;
 use crate::error::exception_to_err_result;
 use crate::module_specifier::ModuleSpecifier;
+use crate::modules::CustomModuleEvaluationCtx;
 use crate::modules::ModuleCodeString;
 use crate::modules::ModuleId;
 use crate::modules::ModuleMap;
@@ -61,6 +62,10 @@ pub(crate) struct ContextState {
     RefCell<Option<Rc<v8::Global<v8::Function>>>>,
   pub(crate) js_wasm_streaming_cb:
     RefCell<Option<Rc<v8::Global<v8::Function>>>>,
+  pub(crate) web_assembly_module_imports_fn:
+    RefCell<Option<Rc<v8::Global<v8::Function>>>>,
+  pub(crate) web_assembly_module_exports_fn:
+    RefCell<Option<Rc<v8::Global<v8::Function>>>>,
   pub(crate) unrefed_ops:
     RefCell<HashSet<i32, BuildHasherDefault<IdentityHasher>>>,
   pub(crate) pending_ops: Rc<OpDriverImpl>,
@@ -87,6 +92,8 @@ impl ContextState {
       has_next_tick_scheduled: Default::default(),
       js_event_loop_tick_cb: Default::default(),
       js_wasm_streaming_cb: Default::default(),
+      web_assembly_module_imports_fn: Default::default(),
+      web_assembly_module_exports_fn: Default::default(),
       op_ctxs,
       pending_ops: op_driver,
       task_spawner_factory: Default::default(),
@@ -362,6 +369,26 @@ impl JsRealm {
     count.set(count.get() + 1)
   }
 
+  pub(crate) fn get_custom_module_evaluation_ctx(
+    scope: &mut v8::HandleScope,
+  ) -> CustomModuleEvaluationCtx {
+    let state = Self::state_from_scope(scope);
+    let web_assembly_module_imports_fn = state
+      .web_assembly_module_imports_fn
+      .borrow()
+      .clone()
+      .unwrap();
+    let web_assembly_module_exports_fn = state
+      .web_assembly_module_exports_fn
+      .borrow()
+      .clone()
+      .unwrap();
+    CustomModuleEvaluationCtx {
+      web_assembly_module_imports_fn,
+      web_assembly_module_exports_fn,
+    }
+  }
+
   /// 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/jsruntime.rs b/core/runtime/jsruntime.rs
index 7496d64..b36650e 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -836,7 +836,7 @@ impl JsRuntime {
         )?;
       }
 
-      js_runtime.store_js_callbacks(&realm);
+      js_runtime.store_js_callbacks(&realm, will_snapshot);
 
       js_runtime.init_extension_js(
         &realm,
@@ -957,7 +957,6 @@ impl JsRuntime {
         synthetic_module_exports,
       )
       .unwrap();
-    module_map.instantiate_module(scope, mod_id).unwrap();
     module_map.mod_evaluate_sync(scope, mod_id).unwrap();
   }
 
@@ -1114,8 +1113,13 @@ impl JsRuntime {
 
   /// Grab and store JavaScript bindings to callbacks necessary for the
   /// JsRuntime to operate properly.
-  fn store_js_callbacks(&mut self, realm: &JsRealm) {
-    let (event_loop_tick_cb, build_custom_error_cb) = {
+  fn store_js_callbacks(&mut self, realm: &JsRealm, will_snapshot: bool) {
+    let (
+      event_loop_tick_cb,
+      build_custom_error_cb,
+      web_assembly_module_imports_fn,
+      web_assembly_module_exports_fn,
+    ) = {
       let scope = &mut realm.handle_scope(self.v8_isolate());
       let context = realm.context();
       let context_local = v8::Local::new(scope, context);
@@ -1140,9 +1144,43 @@ impl JsRuntime {
         "Deno.core.buildCustomError",
       );
 
+      let mut web_assembly_module_imports_fn = None;
+      let mut web_assembly_module_exports_fn = None;
+
+      if !will_snapshot {
+        let web_assembly_object: v8::Local<v8::Object> = bindings::get(
+          scope,
+          global,
+          v8_static_strings::WEBASSEMBLY,
+          "WebAssembly",
+        );
+        let web_assembly_module_object: v8::Local<v8::Object> = bindings::get(
+          scope,
+          web_assembly_object,
+          v8_static_strings::MODULE,
+          "WebAssembly.Module",
+        );
+        web_assembly_module_imports_fn =
+          Some(bindings::get::<v8::Local<v8::Function>>(
+            scope,
+            web_assembly_module_object,
+            v8_static_strings::IMPORTS,
+            "WebAssembly.Module.imports",
+          ));
+        web_assembly_module_exports_fn =
+          Some(bindings::get::<v8::Local<v8::Function>>(
+            scope,
+            web_assembly_module_object,
+            v8_static_strings::EXPORTS,
+            "WebAssembly.Module.exports",
+          ));
+      }
+
       (
         v8::Global::new(scope, event_loop_tick_cb),
         v8::Global::new(scope, build_custom_error_cb),
+        web_assembly_module_imports_fn.map(|f| v8::Global::new(scope, f)),
+        web_assembly_module_exports_fn.map(|f| v8::Global::new(scope, f)),
       )
     };
 
@@ -1157,6 +1195,20 @@ impl JsRuntime {
       .js_build_custom_error_cb
       .borrow_mut()
       .replace(Rc::new(build_custom_error_cb));
+    if let Some(web_assembly_module_imports_fn) = web_assembly_module_imports_fn
+    {
+      state_rc
+        .web_assembly_module_imports_fn
+        .borrow_mut()
+        .replace(Rc::new(web_assembly_module_imports_fn));
+    }
+    if let Some(web_assembly_module_exports_fn) = web_assembly_module_exports_fn
+    {
+      state_rc
+        .web_assembly_module_exports_fn
+        .borrow_mut()
+        .replace(Rc::new(web_assembly_module_exports_fn));
+    }
   }
 
   /// Returns the runtime's op state, which can be used to maintain ops


Test patch:
diff --git a/core/modules/tests.rs b/core/modules/tests.rs
index b18a236..9f0e00c 100644
--- a/core/modules/tests.rs
+++ b/core/modules/tests.rs
@@ -5,6 +5,7 @@ use crate::error::generic_error;
 use crate::modules::loaders::ModuleLoadEventCounts;
 use crate::modules::loaders::TestingModuleLoader;
 use crate::modules::loaders::*;
+use crate::modules::CustomModuleEvaluationCtx;
 use crate::modules::CustomModuleEvaluationKind;
 use crate::modules::ModuleCodeBytes;
 use crate::modules::ModuleError;
@@ -754,6 +755,7 @@ fn test_custom_module_type_default() {
 fn test_custom_module_type_callback_synthetic() {
   fn custom_eval_cb(
     scope: &mut v8::HandleScope,
+    _ctx: CustomModuleEvaluationCtx,
     module_type: Cow<'_, str>,
     _module_name: &FastString,
     module_code: ModuleSourceCode,
@@ -836,6 +838,7 @@ fn test_custom_module_type_callback_synthetic() {
 fn test_custom_module_type_callback_computed() {
   fn custom_eval_cb(
     scope: &mut v8::HandleScope,
+    _ctx: CustomModuleEvaluationCtx,
     module_type: Cow<'_, str>,
     module_name: &FastString,
     module_code: ModuleSourceCode,