6.5 Registration and instantiation

Overview

All the modules are ready to get into v8. This happens in two steps:
  • Compile and register module
  • Instantiate module
In the end, they are loaded into v8 and ready for execution or evaluation.

Registration

Let's revisit the registration procedure as we've some imports in this example. This time the procedure would be a bit longer as imports would also get loaded into v8. Let's see in detail:
  • load_module
    • Recursive loading and prepare is done
    • Loop over a pending list and register module
  • register_during_load
    • Loop over all the imports
    • Add them to the pending list if they're not registered
The loop in the load_module would keep registering modules till the pending list gets empty. Here is an abbreviated code of all the three functions (only import specific code is shown, rest is suppressed):
pub async fn load_module(
&mut self,
specifier: &ModuleSpecifier,
code: Option<String>,
) -> Result<ModuleId, AnyError> {
// -- LOAD MODULE RECURSIVELY ------
while let Some(info_result) = load.next().await {
let info = info_result?;
self.register_during_load(info, &mut load)?;
}
// -- CODE TO INSTANTIATE ROOT MODULE
}
fn register_during_load(
&mut self,
info: ModuleSource,
load: &mut RecursiveModuleLoad,
) -> Result<(), AnyError> {
// -- LOAD MODULE IN V8 USING MOD_NEW
// Now we must iterate over all imports of the module and load them.
let imports = {
let state_rc = Self::state(self.v8_isolate());
let state = state_rc.borrow();
state.modules.get_children(module_id).unwrap().clone()
};
for module_specifier in imports {
debug!("register_during_load: iteration in imports sp={}", module_specifier);
let is_registered = {
let state_rc = Self::state(self.v8_isolate());
let state = state_rc.borrow();
state.modules.is_registered(&module_specifier)
};
if !is_registered {
load
.add_import(module_specifier.to_owned(), referrer_specifier.clone());
}
}
// -- OTHER CODE ---
Ok(())
}
fn mod_new(
&mut self,
main: bool,
name: &str,
source: &str,
) -> Result<ModuleId, AnyError> {
// -- COMPILE MODULE IN V8
let module = maybe_module.unwrap();
let mut import_specifiers: Vec<ModuleSpecifier> = vec![];
for i in 0..module.get_module_requests_length() {
let import_specifier =
module.get_module_request(i).to_rust_string_lossy(tc_scope);
let state = state_rc.borrow();
let module_specifier = state.loader.resolve(
state.op_state.clone(),
&import_specifier,
name,
false,
)?;
import_specifiers.push(module_specifier);
}
let id = state_rc.borrow_mut().modules.register(
name,
main,
v8::Global::<v8::Module>::new(tc_scope, module),
import_specifiers,
);
debug!("mod_new: registered with id={}", id);
Ok(id)
}
The procedure is quite straightforward. It's almost like building the module graph. However, to make it complete, we'll go over all the steps that result in the registration of modules for the hello world v2 program.

Step 1

Compile and register module file:///Users/mayankc/Work/source/deno-vs-nodejs/helloLogV2.ts.
ID
Module
1
file:///Users/mayankc/Work/source/deno-vs-nodejs/helloLogV2.ts

Step 2

Go through imports and add them to the pending list
  • add https://deno.land/x/doze/mod.ts to the pending list
  • add https://deno.land/x/machine_id/mod.ts to the pending list

Step 3

Compile and register module https://deno.land/x/doze/mod.ts.
ID
Module
2
https://deno.land/x/doze/mod.ts

Step 4

Go through imports and add them to the pending list
  • add https://deno.land/x/doze/doze.ts to the pending list

Step 5

Compile and register module https://deno.land/x/machine_id/mod.ts.
ID
Module
3
https://deno.land/x/machine_id/mod.ts

Step 6

There are no imports, nothing gets added to the pending list

Step 7

Compile and register module https://deno.land/x/doze/doze.ts.
ID
Module
4
https://deno.land/x/doze/doze.ts

Step 8

As the pending list is empty, the registration procedure is done. All the modules recursively imported from the main module has been compiled and registered.
It's very important to note that the modules have been loaded independently into v8. There is no connection between them. Not yet. The connection would happen during instantiation.

Instantiation

Regardless of the number of imports or modules, instantiation happens only on the root or main module. To recall here is the relevant code where instantiate is called.
pub async fn load_module(
&mut self,
specifier: &ModuleSpecifier,
code: Option<String>,
) -> Result<ModuleId, AnyError> {
// -- LOAD AND REGISTER MODULES RECURSIVELY ---
let root_id = load.root_module_id.expect("Root module id empty");
self.mod_instantiate(root_id).map(|_| root_id)
}
We've seen the mod_instantiate function in the last chapter. This function instantiates a module into v8. There were no callbacks required as the last chapter's hello world program had no imports. However, this time there are imports. So, the v8 engine expects a callback to resolve imported modules. In the context of v8, resolving the module would mean returning the v8's module handle when the module was compiled into v8.
Here are the steps:
  • Instantiate the main module
  • For each import present in each module
    • V8 makes a callback to get the module handle
    • In each callback, V8 provides the module to resolve as well the module which is requesting the resolution (referrer). This is useful in setting the context for resolution.
    • This goes on recursively till all the modules are done
This is a recursive process, so there is no need to instantiate every module. It always starts at the main module, and V8 instantiates all the other modules recursively.
Let's go over how instantiation happens for our hello world v2 program.

Step 1

Instantiate main module id=1.

Step 2

Receive callback to resolve:
Resolve
Referrer
https://deno.land/x/doze/mod.ts
file:///Users/mayankc/Work/source/deno-vs-nodejs/helloLogV2.ts

Step 3

Receive callback to resolve:
Resolve
Referrer
https://deno.land/x/machine_id/mod.ts
file:///Users/mayankc/Work/source/deno-vs-nodejs/helloLogV2.ts

Step 4

Receive callback to resolve:
Resolve
Referrer
./doze.ts
https://deno.land/x/doze/mod.ts
--
Once all the import callbacks get resolved successfully, v8 finishes module instantiation. The main module and all the imports have been recursively instantiated into v8. They are ready for evaluation.
However, before jumping to evaluation, we'll first see how ops are registered. The reason being that our hello world v2 program has both sync and async ops. We've briefly seen the ops registration process in one of the earlier chapters. Let's see the process in detail before we go to the execution of the program.