///|
/// Linker for managing multiple module instances and their dependencies
struct Linker {
store : Store
modules : Array[(String, ModuleInstance)] // (name, instance) pairs
}
///|
/// Create a new linker with a fresh store
#cfg(target="native")
pub fn Linker::Linker() -> Linker raise RuntimeError {
{ store: Store(), modules: [], }
}
///|
/// Create a new linker with an existing store
pub fn Linker::with_store(store : Store) -> Linker {
{ store, modules: [], }
}
///|
/// Get the underlying store
pub fn Linker::get_store(self : Linker) -> Store {
self.store
}
///|
/// Register rollback for core allocations and names added by an instantiation.
pub fn Linker::register_rollback(
self : Linker,
registrar : RollbackRegistrar,
) -> Unit {
self.store.register_rollback(registrar)
}
///|
fn Linker::host_module(
self : Linker,
name : String,
) -> (ModuleInstance, Int32) raise RuntimeError {
let modules = self.modules.length()
for entry in self.modules {
let (module_name, instance) = entry
if module_name == name {
return (instance, modules)
}
}
let instance = self.store.host_exports([])
self.modules.push((name, instance))
(instance, modules)
}
///|
fn Linker::rollback_module_change(
self : Linker,
modules : Int32,
rollback : () -> Unit,
) -> Unit {
self.store.on_instantiation_rollback(fn() {
rollback()
self.modules.truncate(modules)
})
}
///|
/// Release this linker's store after all module execution has ended.
///
/// This operation is idempotent and terminal.
pub fn Linker::close(self : Linker) -> Unit {
self.modules.clear()
self.modules.shrink_to_fit()
self.store.close()
}
///|
/// Register a module instance with a name
/// The module's exports will be available as imports for subsequent modules
pub fn Linker::register(
self : Linker,
name : String,
instance : ModuleInstance,
) -> Unit raise RuntimeError {
self.store.check_instance(instance)
let modules = self.modules.length()
self.store.on_instantiation_rollback(fn() { self.modules.truncate(modules) })
self.modules.push((name, instance))
}
///|
/// Build Imports from all registered modules
/// This allows a new module to import from any previously registered module
pub fn Linker::build_imports(self : Linker) -> Imports {
let imports = Imports::Imports(self.store)
for entry in self.modules {
let (mod_name, instance) = entry
for exp in instance.exports {
match exp.desc {
Func(idx) => {
let func_addr = instance.func_addrs[idx]
imports.add_func(mod_name, exp.name, func_addr)
}
Table(idx) => {
let table_addr = instance.table_addrs[idx]
imports.add_table(mod_name, exp.name, table_addr)
}
Memory(idx) => {
let mem_addr = instance.mem_addrs[idx]
imports.add_memory(mod_name, exp.name, mem_addr)
}
Global(idx) => {
let global_addr = instance.global_addrs[idx]
imports.add_global(mod_name, exp.name, global_addr)
}
Tag(idx) => {
let tag_addr = instance.tag_addrs[idx]
imports.add_tag(mod_name, exp.name, tag_addr)
}
}
}
}
imports
}
///|
/// Get a registered module instance by name
pub fn Linker::get_module(self : Linker, name : String) -> ModuleInstance? {
for entry in self.modules {
let (mod_name, instance) = entry
if mod_name == name {
return Some(instance)
}
}
None
}
///|
fn Linker::add_func_export(
self : Linker,
mod_name : String,
name : String,
func_addr : Int32,
) -> Unit raise RuntimeError {
let (instance, modules) = self.host_module(mod_name)
let func_addrs = instance.func_addrs
let exports = instance.exports
self.rollback_module_change(modules, fn() {
instance.func_addrs = func_addrs
instance.exports = exports
})
instance.func_addrs = append_readonly(instance.func_addrs, func_addr)
instance.exports = append_readonly(instance.exports, {
name,
desc: Func(instance.func_addrs.length() - 1),
})
}
///|
/// Add a host function to the linker
/// This function will be available as an import for all subsequently instantiated modules
pub fn Linker::add_host_func(
self : Linker,
mod_name : String,
name : String,
func : (Array[@types.Value]) -> Array[@types.Value] raise RuntimeError,
func_type~ : @types.FuncType = { params: [], results: [], },
) -> Unit raise RuntimeError {
self.store.ensure_open()
let func_addr = self.store.alloc_host_func(func, func_type~)
self.add_func_export(mod_name, name, func_addr)
}
///|
/// Add a host function that receives the caller context.
pub fn Linker::add_host_func_with_caller(
self : Linker,
mod_name : String,
name : String,
func : (Caller, Array[@types.Value]) -> Array[@types.Value] raise RuntimeError,
func_type~ : @types.FuncType = { params: [], results: [], },
) -> Unit raise RuntimeError {
self.store.ensure_open()
let func_addr = self.store.alloc_host_func_with_caller(func, func_type~)
self.add_func_export(mod_name, name, func_addr)
}
///|
/// Add an asynchronous host function to the linker.
///
/// The function can only be invoked through an asynchronous Wasm call. Its
/// future is polled while the Wasm continuation is parked.
pub fn Linker::add_async_host_func(
self : Linker,
mod_name : String,
name : String,
func : async (Array[@types.Value]) -> Array[@types.Value] raise Error,
func_type~ : @types.FuncType = { params: [], results: [], },
) -> Unit raise RuntimeError {
self.store.ensure_open()
let func_addr = self.store.alloc_async_host_func(func, func_type~)
self.add_func_export(mod_name, name, func_addr)
}
///|
/// Add an asynchronous host function that receives the caller context.
pub fn Linker::add_async_host_func_with_caller(
self : Linker,
mod_name : String,
name : String,
func : async (Caller, Array[@types.Value]) -> Array[@types.Value] raise Error,
func_type~ : @types.FuncType = { params: [], results: [], },
) -> Unit raise RuntimeError {
self.store.ensure_open()
let func_addr = self.store.alloc_async_host_func_with_caller(func, func_type~)
self.add_func_export(mod_name, name, func_addr)
}
///|
/// Add a memory to the linker
pub fn Linker::add_memory(
self : Linker,
mod_name : String,
name : String,
mem : Memory,
) -> Unit raise RuntimeError {
self.store.ensure_open()
let mem_addr = self.store.alloc_mem(mem)
let (instance, modules) = self.host_module(mod_name)
let mem_addrs = instance.mem_addrs
let exports = instance.exports
self.rollback_module_change(modules, fn() {
instance.mem_addrs = mem_addrs
instance.exports = exports
})
instance.mem_addrs = append_readonly(instance.mem_addrs, mem_addr)
instance.exports = append_readonly(instance.exports, {
name,
desc: Memory(instance.mem_addrs.length() - 1),
})
}
///|
/// Add a table to the linker
pub fn Linker::add_table(
self : Linker,
mod_name : String,
name : String,
table : Table,
) -> Unit raise RuntimeError {
self.store.ensure_open()
let table_addr = self.store.alloc_table(table)
let (instance, modules) = self.host_module(mod_name)
let table_addrs = instance.table_addrs
let exports = instance.exports
self.rollback_module_change(modules, fn() {
instance.table_addrs = table_addrs
instance.exports = exports
})
instance.table_addrs = append_readonly(instance.table_addrs, table_addr)
instance.exports = append_readonly(instance.exports, {
name,
desc: Table(instance.table_addrs.length() - 1),
})
}
///|
/// Add a global to the linker
pub fn Linker::add_global(
self : Linker,
mod_name : String,
name : String,
global : GlobalInstance,
) -> Unit raise RuntimeError {
self.store.ensure_open()
let global_addr = self.store.alloc_global(global)
let (instance, modules) = self.host_module(mod_name)
let global_addrs = instance.global_addrs
let exports = instance.exports
self.rollback_module_change(modules, fn() {
instance.global_addrs = global_addrs
instance.exports = exports
})
instance.global_addrs = append_readonly(instance.global_addrs, global_addr)
instance.exports = append_readonly(instance.exports, {
name,
desc: Global(instance.global_addrs.length() - 1),
})
}
///|
fn[T] append_readonly(values : ReadOnlyArray[T], value : T) -> ReadOnlyArray[T] {
ReadOnlyArray::makei(values.length() + 1, i => {
if i < values.length() {
values[i]
} else {
value
}
})
}
///|
#cfg(target="wasm")
pub fn Linker::Linker() -> Linker {
{ store: Store(), modules: [], }
}