///|
/// Exception instance - represents a caught exception
pub struct ExnInstance {
tag_addr : Int32 // Which tag was thrown
values : Array[@types.Value] // Exception values
} derive(Debug)
///|
/// Represents the ownership status of a function in the store
pub(all) enum FuncOwner {
HostFunc // Host function - never has an owner module
Unowned // WASM function not yet assigned to a module
Module(Int32) // WASM function owned by module at given index
} derive(Debug, Eq)
///|
/// The store contains all global runtime state
#cfg(target="native")
pub struct Store {
nan_canonicalization : Bool
interpreter_limits : InterpreterLimits
resource_budget : ResourceBudget?
priv mut execution_scope : ExecutionScope?
priv instance_leases : Array[ResourceLease]
priv mut gc_tracing_depth : Int32
priv mut gc_in_collect : Bool
priv mut gc_trace : GcTrace?
priv mut interpreter_reentry_depth : Int32
priv references : StoreReferences
priv funcs : Array[StoredFunction]
priv func_types : Array[@types.FuncType] // Type of each function
priv func_type_indices : Array[Int32] // Original type index for structural comparison
priv tables : Array[Table]
priv mems : Array[Memory]
priv globals : Array[GlobalInstance]
priv tags : Array[@types.FuncType] // Tag types (params only, no results)
priv tag_type_indices : Array[Int32] // Original type index for each tag
priv tag_owners : Array[FuncOwner] // Owner module for each tag
priv heap : MarkSweepHeap // GC heap for struct and array instances (interpreter mode)
// Module types for CHeap type lookups (needed to decode Int64 to Value)
priv mut module_types : Array[@types.SubType]
// Module rec groups for canonical type index computation
priv mut module_rec_groups : Array[Int32]
// Internal values wrapped as externref via extern.convert_any
// Accessed using negative ExternRef indices: ExternRef(-1-i) = wrapped_externs[i]
// Maps function address to its owning module instance
// Used for cross-module call context switching
priv func_owners : Array[FuncOwner]
// Registered module instances by their address/index
priv instances : Array[ModuleInstance]
// JIT tables corresponding to runtime tables (for cross-module sharing)
// jit_tables[i] is the JIT table for tables[i], or None if not using JIT
priv jit_tables : Array[@native_bridge.JITTable?]
// Store-only destruction capabilities corresponding to jit_tables.
priv jit_table_owners : Array[@native_bridge.JITTableOwner?]
// Current WASI module's memory address (for interpreter mode WASI callbacks)
// WASI callbacks use this to access the correct memory instead of hardcoded index 0
priv mut wasi_mem_addr : Int32?
// Opaque WASI context handle (for WAST runner to store WASI context)
// This avoids circular dependency between runtime and wasi packages
priv mut wasi_ctx_handle : Int32
// Native invocation/fiber lifetimes belong to the Store, not a JIT module.
native_execution : @native_bridge.NativeExecutionState
priv funcref_registry : StoreFuncrefRegistry
priv native_types : StoreTypeRegistry
priv suspended_executions : Map[Int64, SuspendedExecutionCleanup]
priv mut next_suspended_execution_id : Int64
priv mut guest_continuations : Map[Int64, GuestContinuationBody]
priv mut next_guest_continuation : Int64
priv mut pinned_gc_roots : Map[Int64, PinnedGcRoots]
priv mut next_pinned_gc_roots_id : Int64
priv mut instantiation : RollbackRegistrar?
priv closed : Ref[Bool]
} derive(Debug)
///|
#cfg(target="native")
fn Store::register_rollback(
self : Store,
registrar : RollbackRegistrar,
) -> Unit {
self.instantiation = Some(registrar)
self.references.instantiation = Some(registrar)
registrar.on_finish(fn() {
self.instantiation = None
self.references.instantiation = None
})
// Heap cleanup runs last, after mutation-local rollbacks restore Store roots.
registrar.on_rollback(
self.heap.make_allocation_rollback(fn() { self.collect_roots([]) }),
)
if self.references.native.c_heap is Some(heap) {
registrar.on_rollback(
heap.make_allocation_rollback(fn() { self.collect_roots([]) }),
)
}
self.register_native_exception_rollback(registrar)
}
///|
fn Store::on_instantiation_rollback(
self : Store,
rollback : () -> Unit,
) -> Unit {
if self.instantiation is Some(registrar) {
registrar.on_rollback(rollback)
}
}
///|
fn[T] Store::push_allocation(
self : Store,
values : Array[T],
value : T,
) -> Int32 {
let index = values.length()
self.on_instantiation_rollback(fn() {
values.truncate(index)
values.shrink_to_fit()
})
values.push(value)
index
}
///|
/// Optional C heap initial capacity override (bytes)
#cfg(target="native")
const GC_HEAP_CAPACITY_ENV : String = "WASMOON_GC_HEAP_CAPACITY"
///|
#cfg(target="native")
fn parse_decimal_int(value : String) -> Int32? {
if value.length() == 0 {
return None
}
let mut parsed = 0
for codepoint in value {
let digit = codepoint.to_int() - b'0'.to_int()
if digit < 0 || digit > 9 {
return None
}
parsed = parsed * 10 + digit
}
Some(parsed)
}
///|
#cfg(target="native")
fn c_heap_initial_capacity() -> Int32 {
match @env.get_env_var(GC_HEAP_CAPACITY_ENV) {
Some(raw_value) =>
match parse_decimal_int(raw_value) {
Some(parsed) if parsed > 0 => parsed
_ => @types.ONE_MIB_BYTES
}
None => @types.ONE_MIB_BYTES
}
}
///|
#cfg(target="native")
pub fn Store::Store(
nan_canonicalization~ : Bool = false,
interpreter_limits~ : InterpreterLimits = InterpreterLimits::default(),
resource_budget~ : ResourceBudget? = None,
) -> Store raise RuntimeError {
let native_execution = @native_bridge.NativeExecutionState() catch {
_ => raise OutOfMemory
}
if resource_budget is Some(budget) {
guard native_execution
.continuations()
.bind_quota(budget.native_quota(PendingWork)) &&
native_execution
.exceptions()
.bind_quotas(budget.native_quota(GcBytes), budget.native_quota(GcObjects)) else {
raise InvalidHandle
}
}
let closed = Ref(false)
let tags : Array[@types.FuncType] = []
let managed_heap = MarkSweepHeap(resource_budget~)
let funcref_registry = StoreFuncrefRegistry::new()
let references = StoreReferences::new(
resource_budget,
closed,
tags,
managed_heap,
funcref_registry,
{
native_execution,
c_heap: None,
exception_allocations: None,
native_exception_to_store: Map([]),
store_exception_to_native: Map([]),
},
)
{
nan_canonicalization,
interpreter_limits,
resource_budget,
execution_scope: None,
instance_leases: [],
interpreter_reentry_depth: 0,
gc_tracing_depth: 0,
gc_in_collect: false,
gc_trace: None,
funcs: [],
func_types: [],
func_type_indices: [],
tables: [],
mems: [],
globals: [],
tags,
tag_type_indices: [],
tag_owners: [],
heap: managed_heap,
module_types: [],
module_rec_groups: [],
func_owners: [],
instances: [],
jit_tables: [],
jit_table_owners: [],
wasi_mem_addr: None,
wasi_ctx_handle: 0,
native_execution,
funcref_registry,
native_types: StoreTypeRegistry::new(),
suspended_executions: Map([]),
next_suspended_execution_id: 0L,
guest_continuations: Map([]),
next_guest_continuation: 0L,
pinned_gc_roots: Map([]),
next_pinned_gc_roots_id: 0L,
instantiation: None,
closed,
references,
}
}
///|
/// Create a store with C heap enabled (for JIT mode)
#cfg(target="native")
pub fn Store::with_c_heap(
nan_canonicalization~ : Bool = false,
interpreter_limits~ : InterpreterLimits = InterpreterLimits::default(),
resource_budget~ : ResourceBudget? = None,
) -> Store raise RuntimeError {
let native_execution = @native_bridge.NativeExecutionState() catch {
_ => raise OutOfMemory
}
if resource_budget is Some(budget) {
guard native_execution
.continuations()
.bind_quota(budget.native_quota(PendingWork)) &&
native_execution
.exceptions()
.bind_quotas(budget.native_quota(GcBytes), budget.native_quota(GcObjects)) else {
raise InvalidHandle
}
}
let initial_capacity = c_heap_initial_capacity()
let heap = @native_bridge.CHeap(capacity=initial_capacity) catch {
_ => raise OutOfMemory
}
let closed = Ref(false)
let tags : Array[@types.FuncType] = []
let managed_heap = MarkSweepHeap(resource_budget~)
let funcref_registry = StoreFuncrefRegistry::new()
let references = StoreReferences::new(
resource_budget,
closed,
tags,
managed_heap,
funcref_registry,
{
native_execution,
c_heap: Some(heap),
exception_allocations: None,
native_exception_to_store: Map([]),
store_exception_to_native: Map([]),
},
)
let store : Store = {
nan_canonicalization,
interpreter_limits,
resource_budget,
execution_scope: None,
instance_leases: [],
interpreter_reentry_depth: 0,
gc_tracing_depth: 0,
gc_in_collect: false,
gc_trace: None,
funcs: [],
func_types: [],
func_type_indices: [],
tables: [],
mems: [],
globals: [],
tags,
tag_type_indices: [],
tag_owners: [],
heap: managed_heap,
module_types: [],
module_rec_groups: [],
func_owners: [],
instances: [],
jit_tables: [],
jit_table_owners: [],
wasi_mem_addr: None,
wasi_ctx_handle: 0,
native_execution,
funcref_registry,
native_types: StoreTypeRegistry::new(),
suspended_executions: Map([]),
next_suspended_execution_id: 0L,
guest_continuations: Map([]),
next_guest_continuation: 0L,
pinned_gc_roots: Map([]),
next_pinned_gc_roots_id: 0L,
instantiation: None,
closed,
references,
}
store.bind_gc_budget(heap)
store.native_execution.exceptions().enable_reclamation()
store
}
///|
/// Release resources owned by this store and break host-function reference
/// cycles.
///
/// This operation is idempotent and terminal. Every native invocation must
/// have returned or been cancelled before the store is closed.
#cfg(target="native")
pub fn Store::close(self : Store) -> Unit {
if self.closed.val {
return
}
self.closed.val = true
let suspended = self.suspended_executions.values().to_array()
self.suspended_executions.clear()
for cleanup in suspended {
if cleanup.lease is Some(lease) {
lease.release()
}
(cleanup.run)()
}
let guest_bodies = self.guest_continuations.values().to_array()
self.guest_continuations.clear()
for body in guest_bodies {
(body.cancel)()
}
// Clear logical contents and release retained backing slots to break cycles.
// Array.clear alone can retain references beyond the new length.
self.guest_continuations.clear()
self.native_execution.seal()
for roots in self.pinned_gc_roots.values() {
roots.active.val = false
if roots.lease is Some(lease) {
lease.release()
}
}
self.pinned_gc_roots.clear()
self.funcref_registry.clear()
self.native_types.representatives.clear()
self.native_types.representatives.shrink_to_fit()
self.native_types.group_buckets.clear()
self.native_types.mappings.clear()
self.native_types.initialization_graph = None
self.native_types.parents.clear()
self.native_types.parents.shrink_to_fit()
self.native_types.views.clear()
self.native_types.views.shrink_to_fit()
self.native_types.entries.clear()
if self.native_types.native_registry is Some(registry) {
registry.clear()
self.native_types.native_registry = None
}
self.native_types.heap_types.clear()
for function in self.funcs {
function.lifetime.active.val = false
}
self.funcs.clear()
self.funcs.shrink_to_fit()
self.func_types.clear()
self.func_types.shrink_to_fit()
self.func_type_indices.clear()
self.func_type_indices.shrink_to_fit()
self.func_owners.clear()
self.func_owners.shrink_to_fit()
self.instance_leases.clear()
self.instance_leases.shrink_to_fit()
self.instances.clear()
self.instances.shrink_to_fit()
for table in self.tables {
table.close()
}
self.tables.clear()
self.tables.shrink_to_fit()
for memory in self.mems {
memory.close()
}
self.mems.clear()
self.mems.shrink_to_fit()
for global in self.globals {
global.close()
}
self.globals.clear()
self.globals.shrink_to_fit()
self.tags.clear()
self.tags.shrink_to_fit()
self.tag_type_indices.clear()
self.tag_type_indices.shrink_to_fit()
self.tag_owners.clear()
self.tag_owners.shrink_to_fit()
self.references.native.native_exception_to_store.clear()
self.references.native.store_exception_to_native.clear()
self.references.exns.clear()
self.references.auxiliary_leases.clear()
self.heap.close()
if self.references.native.c_heap is Some(heap) {
heap.free()
self.references.native.c_heap = None
}
// These arrays are borrowed from the parsed module and may be reused by
// other stores. Closing a store must only drop its own references.
self.module_types = []
self.module_rec_groups = []
self.references.wrapped_externs.clear()
for owner in self.jit_table_owners {
match owner {
Some(owner) => owner.close()
None => ()
}
}
self.jit_table_owners.clear()
self.jit_table_owners.shrink_to_fit()
self.jit_tables.clear()
self.jit_tables.shrink_to_fit()
self.wasi_mem_addr = None
self.wasi_ctx_handle = 0
self.instantiation = None
}
///|
fn Store::ensure_open(self : Store) -> Unit raise RuntimeError {
if self.closed.val {
raise StoreClosed
}
}
///|
/// Return the Store-owned native invocation registry.
#cfg(target="native")
pub fn Store::native_execution_state(
self : Store,
) -> @native_bridge.NativeExecutionState {
self.native_execution
}
///|
/// One Store-owned lease that keeps a snapshot of GC references alive.
///
/// Executors use this while an invocation is parked outside an active Wasm
/// stack. Releasing a lease is idempotent.
pub struct GcRootLease {
priv mut store : Store?
priv id : Int64
priv active : Ref[Bool]
}
///|
pub fn Store::pin_gc_roots(
self : Store,
values : Array[@types.Value],
) -> GcRootLease raise RuntimeError {
let id = self.next_pinned_gc_roots_id
let active = Ref(true)
self.next_pinned_gc_roots_id = self.next_pinned_gc_roots_id + 1L
if self.closed.val {
return { store: None, id, active: Ref(false), }
}
let lease = self.resource_budget.map(budget => budget.reserve(PendingWork, 1L))
self.pinned_gc_roots.set(id, {
lease,
values: values.copy(),
active,
guest_owned: false,
})
{ store: Some(self), id, active, }
}
///|
pub fn GcRootLease::release(self : GcRootLease) -> Unit {
self.active.val = false
if self.store is Some(store) {
if store.pinned_gc_roots.get(self.id) is Some(roots) {
if roots.lease is Some(lease) {
lease.release()
}
}
store.pinned_gc_roots.remove(self.id)
}
self.store = None
}
///|
priv struct PinnedGcRoots {
lease : ResourceLease?
values : Array[@types.Value]
active : Ref[Bool]
mut guest_owned : Bool
} derive(Debug)
///|
fn GcRootLease::delegate_to_guest(self : GcRootLease) -> Unit {
if self.store is Some(store) &&
store.pinned_gc_roots.get(self.id) is Some(roots) {
roots.guest_owned = true
}
}
///|
/// Copy the explicit roots owned by this lease for continuation transfer.
pub fn GcRootLease::snapshot(self : GcRootLease) -> Array[@types.Value] {
if self.store is Some(store) &&
store.pinned_gc_roots.get(self.id) is Some(roots) {
roots.values.copy()
} else {
[]
}
}
///|
/// Enable C heap for this store
#cfg(target="native")
pub fn Store::enable_c_heap(self : Store) -> Unit raise RuntimeError {
self.ensure_open()
if self.references.native.c_heap is None {
guard self.heap.stats().live_objects == 0 else {
raise HeapRepresentationInUse
}
let heap = @native_bridge.CHeap(capacity=c_heap_initial_capacity()) catch {
_ => raise OutOfMemory
}
self.bind_gc_budget(heap)
self.references.native.c_heap = Some(heap)
self.native_execution.exceptions().enable_reclamation()
}
}
///|
/// Set module types and rec groups (needed for CHeap type lookups and canonical indices)
pub fn Store::set_module_types(
self : Store,
types : Array[@types.SubType],
rec_groups~ : Array[Int32] = [],
) -> Unit raise RuntimeError {
self.ensure_open()
let old_types = self.module_types
let old_rec_groups = self.module_rec_groups
let old_graph = self.native_types.initialization_graph
self.on_instantiation_rollback(fn() {
if !self.closed.val {
self.module_types = old_types
self.module_rec_groups = old_rec_groups
self.native_types.initialization_graph = old_graph
}
})
self.module_types = types.copy()
self.module_rec_groups = rec_groups.copy()
self.native_types.initialization_graph = None
}
///|
/// Get the C heap pointer (for JIT)
#cfg(target="native")
pub fn Store::get_c_heap_ptr(self : Store) -> @native_bridge.GCHeapHandle {
match self.references.native.c_heap {
Some(heap) => heap.get_ptr()
None => @native_bridge.GCHeapHandle::null()
}
}
///|
/// Check if C heap is enabled
#cfg(target="native")
pub fn Store::has_c_heap(self : Store) -> Bool {
self.references.native.c_heap is Some(_)
}
///|
/// Wrap an internal GC value as an externref
/// Returns ExternRef with negative index pointing to wrapped_externs
pub fn Store::wrap_as_extern(
self : Store,
value : @types.Value,
) -> @types.Value raise RuntimeError {
self.references.wrap_as_extern(value)
}
///|
/// Unwrap an externref back to its internal value
/// For host externrefs (positive index), returns the ExternRef unchanged
/// For wrapped internals (negative index), returns the original value
pub fn Store::unwrap_extern(
self : Store,
value : @types.Value,
) -> @types.Value raise RuntimeError {
self.references.unwrap_extern(value)
}
///|
fn Store::alloc_func_inst(
self : Store,
func : FuncInst,
func_type : @types.FuncType,
type_idx : Int32,
owner : FuncOwner,
) -> Int32 raise RuntimeError {
self.ensure_open()
let lifetime = FunctionIdentity::{ active: Ref(true), }
self.on_instantiation_rollback(fn() { lifetime.active.val = false })
let idx = self.push_allocation(self.funcs, { callable: func, lifetime, })
self.push_allocation(self.func_types, func_type) |> ignore
self.push_allocation(self.func_type_indices, type_idx) |> ignore
self.push_allocation(self.func_owners, owner) |> ignore
idx
}
///|
/// Allocate a WASM function with its type
pub fn Store::alloc_func(
self : Store,
module_ : @validator.ValidatedModule,
index : Int32,
) -> Int32 raise RuntimeError {
self.ensure_open()
guard module_.function(index) is Some((code, type_idx, signature)) else {
raise UndefinedElement
}
self.alloc_func_inst(WasmFunc(code), signature, type_idx, Unowned)
}
///|
/// Allocate a host function with its type
pub fn Store::alloc_host_func(
self : Store,
func : (Array[@types.Value]) -> Array[@types.Value] raise RuntimeError,
func_type~ : @types.FuncType = { params: [], results: [], },
type_idx~ : Int32 = -1,
) -> Int32 raise RuntimeError {
// Back-compat: old-style host funcs don't take a Caller.
let wrapped : (Caller, Array[@types.Value]) -> Array[@types.Value] raise RuntimeError = (
_caller,
args,
) => func(args)
self.alloc_func_inst(HostFunc(wrapped), func_type, type_idx, HostFunc)
}
///|
/// Allocate a host function that receives the caller context.
pub fn Store::alloc_host_func_with_caller(
self : Store,
func : (Caller, Array[@types.Value]) -> Array[@types.Value] raise RuntimeError,
func_type~ : @types.FuncType = { params: [], results: [], },
type_idx~ : Int32 = -1,
) -> Int32 raise RuntimeError {
self.alloc_func_inst(HostFunc(func), func_type, type_idx, HostFunc)
}
///|
/// Allocate an asynchronous host function with its type.
pub fn Store::alloc_async_host_func(
self : Store,
func : async (Array[@types.Value]) -> Array[@types.Value] raise Error,
func_type~ : @types.FuncType = { params: [], results: [], },
type_idx~ : Int32 = -1,
) -> Int32 raise RuntimeError {
let wrapped : AsyncHostFunction = async fn(_caller, args) {
try func(args) catch {
error => Err(error)
} noraise {
values => Ok(values)
}
}
self.alloc_func_inst(AsyncHostFunc(wrapped), func_type, type_idx, HostFunc)
}
///|
/// Allocate an asynchronous host function that receives the caller context.
pub fn Store::alloc_async_host_func_with_caller(
self : Store,
func : async (Caller, Array[@types.Value]) -> Array[@types.Value] raise Error,
func_type~ : @types.FuncType = { params: [], results: [], },
type_idx~ : Int32 = -1,
) -> Int32 raise RuntimeError {
let wrapped : AsyncHostFunction = async fn(caller, args) {
try func(caller, args) catch {
error => Err(error)
} noraise {
values => Ok(values)
}
}
self.alloc_func_inst(AsyncHostFunc(wrapped), func_type, type_idx, HostFunc)
}
///|
/// Allocate a table in the store
/// Always creates a corresponding JIT table for cross-module sharing
#cfg(target="native")
pub fn Store::alloc_table(
self : Store,
table : Table,
use_jit~ : Bool = true,
) -> Int32 raise RuntimeError {
self.ensure_open()
table.ensure_open()
let binding = resource_binding(table.binding, self.closed)
for index, existing in self.tables {
if physical_equal(existing, table) {
return index
}
}
let previous_reservation = table.reservation
errdefer {
table.reservation = previous_reservation
}
if self.resource_budget is Some(budget) {
table.bind_budget(budget)
}
let owner = if use_jit {
match
@native_bridge.JITTableOwner::try_alloc(
table.size(),
table.get_limits64().1,
) {
Some(owner) => {
if table.reservation is Some(reservation) {
guard owner.table().raw_ptr().bind_reservation(reservation) else {
owner.close()
raise InvalidHandle
}
}
Some(owner)
}
None => raise TableAllocationFailed
}
} else {
None
}
let idx = self.tables.length()
if owner is Some(owner) {
errdefer owner.close()
self.bind_table_storage(table, owner.table())
}
table.binding = Some(binding)
self.on_instantiation_rollback(fn() {
binding.active.val = false
if idx < self.jit_table_owners.length() {
match self.jit_table_owners[idx] {
Some(owner) => owner.close()
None => ()
}
}
table.close()
self.tables.truncate(idx)
self.tables.shrink_to_fit()
self.jit_tables.truncate(idx)
self.jit_tables.shrink_to_fit()
self.jit_table_owners.truncate(idx)
self.jit_table_owners.shrink_to_fit()
})
self.tables.push(table)
self.jit_tables.push(owner.map(fn(owner) { owner.table() }))
self.jit_table_owners.push(owner)
idx
}
///|
pub fn Store::alloc_mem(self : Store, mem : Memory) -> Int32 raise RuntimeError {
self.ensure_open()
let binding = resource_binding(mem.binding, self.closed)
for index, existing in self.mems {
if physical_equal(existing, mem) {
return index
}
}
mem.ensure_memory_open()
if self.resource_budget is Some(budget) {
mem.bind_budget(budget)
}
let idx = self.mems.length()
mem.binding = Some(binding)
self.on_instantiation_rollback(fn() {
binding.active.val = false
if idx < self.mems.length() {
self.mems[idx].close()
}
self.mems.truncate(idx)
self.mems.shrink_to_fit()
})
self.mems.push(mem)
idx
}
///|
pub fn Store::alloc_global(
self : Store,
global : GlobalInstance,
) -> Int32 raise RuntimeError {
self.ensure_open()
let binding = resource_binding(global.binding, self.closed)
guard value_fits_kind(global.value, global.type_.value_type) else {
raise TypeMismatch
}
for index, existing in self.globals {
if physical_equal(existing, global) {
return index
}
}
global.binding = Some(binding)
self.on_instantiation_rollback(fn() { binding.active.val = false })
self.push_allocation(self.globals, global)
}
///|
/// Allocate a tag with its type (params from FuncType, results ignored)
pub fn Store::alloc_tag(
self : Store,
tag_type : @types.FuncType,
type_idx~ : Int32 = -1,
) -> Int32 raise RuntimeError {
self.ensure_open()
let idx = self.push_allocation(self.tags, tag_type)
self.push_allocation(self.tag_type_indices, type_idx) |> ignore
self.push_allocation(self.tag_owners, Unowned) |> ignore
idx
}
///|
pub fn Store::get_tag_type(
self : Store,
idx : Int32,
) -> @types.FuncType raise RuntimeError {
self.ensure_open()
if idx < 0 || idx >= self.tags.length() {
raise UndefinedElement
}
self.tags[idx]
}
///|
/// Get the original type index for a tag
pub fn Store::get_tag_type_idx(self : Store, tag_addr : Int32) -> Int32 {
if tag_addr < 0 || tag_addr >= self.tag_type_indices.length() {
return -1
}
self.tag_type_indices[tag_addr]
}
///|
/// Get the owner module for a tag
pub fn Store::get_tag_owner(self : Store, tag_addr : Int32) -> ModuleInstance? {
if tag_addr < 0 || tag_addr >= self.tag_owners.length() {
return None
}
match self.tag_owners[tag_addr] {
Module(idx) =>
if idx >= 0 && idx < self.instances.length() {
Some(self.instances[idx])
} else {
None
}
_ => None
}
}
///|
/// Allocate an exception instance and return its index
pub fn Store::alloc_exn(
self : Store,
tag_addr : Int32,
values : Array[@types.Value],
) -> Int32 raise RuntimeError {
self.references.alloc_exn(tag_addr, values)
}
///|
/// Get an exception instance by index
pub fn Store::get_exn(
self : Store,
idx : Int32,
) -> ExnInstance raise RuntimeError {
self.references.get_exn(idx)
}
///|
/// Get the field type of a struct from module_types
#cfg(target="native")
fn Store::get_struct_field_type(
self : Store,
type_idx : Int32,
field_idx : Int32,
reference : Int32,
) -> @types.ValueType {
guard self.gc_type_definition(reference, type_idx) is Some(definition) else {
return I64
}
match definition.composite {
Struct(st) =>
if field_idx >= 0 && field_idx < st.fields.length() {
match st.fields[field_idx].storage_type {
Val(vt) => vt
Packed(I8) | Packed(I16) => I32
}
} else {
I64
}
_ => I64
}
}
///|
/// Get the element type of an array from module_types
#cfg(target="native")
fn Store::get_array_elem_type(
self : Store,
type_idx : Int32,
reference : Int32,
) -> @types.ValueType {
guard self.gc_type_definition(reference, type_idx) is Some(definition) else {
return I64
}
match definition.composite {
Array(at) =>
match at.element.storage_type {
Val(vt) => vt
Packed(I8) | Packed(I16) => I32
}
_ => I64
}
}
///|
/// Allocate a struct instance and return its reference index
#cfg(target="native")
pub fn Store::alloc_struct(
self : Store,
type_idx : Int32,
fields : Array[@types.Value],
instance? : ModuleInstance,
) -> Int32 raise RuntimeError {
self.ensure_open()
if instance is Some(instance) {
self.check_instance(instance)
}
let reference = match self.references.native.c_heap {
Some(heap) => {
let encoded = fields.map(value => self.encode_heap_value(value))
heap.alloc_struct(type_idx, encoded) catch {
OutOfBoundsArrayAccess => raise OutOfBoundsArrayAccess
NullReference => raise NullReference
OutOfMemory => raise self.gc_allocation_error()
}
}
None => self.heap.alloc_struct(type_idx, fields)
}
self.record_gc_type(reference, instance, type_idx)
reference
}
///|
/// Allocate an array instance and return its reference index
#cfg(target="native")
pub fn Store::alloc_array(
self : Store,
type_idx : Int32,
elements : Array[@types.Value],
instance? : ModuleInstance,
) -> Int32 raise RuntimeError {
self.ensure_open()
if instance is Some(instance) {
self.check_instance(instance)
}
let reference = match self.references.native.c_heap {
Some(heap) => {
let encoded = elements.map(value => self.encode_heap_value(value))
heap.alloc_array_from_values(type_idx, encoded) catch {
OutOfBoundsArrayAccess => raise OutOfBoundsArrayAccess
NullReference => raise NullReference
OutOfMemory => raise self.gc_allocation_error()
}
}
None => self.heap.alloc_array(type_idx, elements)
}
self.record_gc_type(reference, instance, type_idx)
reference
}
///|
/// Get a struct field value
#cfg(target="native")
pub fn Store::struct_get(
self : Store,
struct_idx : Int32,
field_idx : Int32,
) -> @types.Value raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) => {
let type_idx = heap.get_type_idx(struct_idx)
let field_type = self.get_struct_field_type(
type_idx, field_idx, struct_idx,
)
self.decode_heap_value(
field_type,
heap.struct_get(struct_idx, field_idx, field_type),
)
}
None => self.heap.struct_get(struct_idx, field_idx)
}
}
///|
/// Set a struct field value
#cfg(target="native")
pub fn Store::struct_set(
self : Store,
struct_idx : Int32,
field_idx : Int32,
value : @types.Value,
) -> Unit raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) =>
heap.struct_set(struct_idx, field_idx, self.encode_heap_value(value))
None => self.heap.struct_set(struct_idx, field_idx, value)
}
}
///|
/// Get array length
#cfg(target="native")
pub fn Store::array_len(
self : Store,
array_idx : Int32,
) -> Int32 raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) => heap.array_len(array_idx)
None => self.heap.array_len(array_idx)
}
}
///|
/// Get an array element value
#cfg(target="native")
pub fn Store::array_get(
self : Store,
array_idx : Int32,
elem_idx : Int32,
) -> @types.Value raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) => {
let type_idx = heap.get_type_idx(array_idx)
let elem_type = self.get_array_elem_type(type_idx, array_idx)
let value = heap.array_get(array_idx, elem_idx, elem_type) catch {
OutOfBoundsArrayAccess => raise OutOfBoundsArrayAccess
NullReference => raise NullReference
OutOfMemory => raise OutOfMemory
}
self.decode_heap_value(elem_type, value)
}
None => self.heap.array_get(array_idx, elem_idx)
}
}
///|
/// Set an array element value
#cfg(target="native")
pub fn Store::array_set(
self : Store,
array_idx : Int32,
elem_idx : Int32,
value : @types.Value,
) -> Unit raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) => {
let encoded = self.encode_heap_value(value)
heap.array_set(array_idx, elem_idx, encoded) catch {
OutOfBoundsArrayAccess => raise OutOfBoundsArrayAccess
NullReference => raise NullReference
OutOfMemory => raise OutOfMemory
}
}
None => self.heap.array_set(array_idx, elem_idx, value)
}
}
///|
/// Fill array elements with a value
#cfg(target="native")
pub fn Store::array_fill(
self : Store,
array_idx : Int32,
offset : Int32,
value : @types.Value,
count : Int32,
) -> Unit raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) => {
let encoded = self.encode_heap_value(value)
heap.array_fill(array_idx, offset, encoded, count) catch {
OutOfBoundsArrayAccess => raise OutOfBoundsArrayAccess
NullReference => raise NullReference
OutOfMemory => raise OutOfMemory
}
}
None => self.heap.array_fill(array_idx, offset, value, count)
}
}
///|
/// Copy elements between arrays
#cfg(target="native")
pub fn Store::array_copy(
self : Store,
dst_idx : Int32,
dst_offset : Int32,
src_idx : Int32,
src_offset : Int32,
count : Int32,
) -> Unit raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) =>
heap.array_copy(dst_idx, dst_offset, src_idx, src_offset, count) catch {
OutOfBoundsArrayAccess => raise OutOfBoundsArrayAccess
NullReference => raise NullReference
OutOfMemory => raise OutOfMemory
}
None =>
self.heap.array_copy(dst_idx, dst_offset, src_idx, src_offset, count)
}
}
///|
/// Get the type index of a GC object
#cfg(target="native")
pub fn Store::get_gc_type_idx(
self : Store,
idx : Int32,
) -> Int32 raise RuntimeError {
self.ensure_open()
match self.references.native.c_heap {
Some(heap) => heap.get_type_idx(idx)
None => self.heap.get_type_idx(idx)
}
}
///|
/// Check if a GC object reference is valid
#cfg(target="native")
pub fn Store::is_gc_valid(self : Store, idx : Int32) -> Bool {
match self.references.native.c_heap {
Some(heap) => heap.is_valid(idx)
None => self.heap.is_valid(idx)
}
}
///|
pub fn Store::get_func_inst(
self : Store,
idx : Int32,
) -> FuncInst raise RuntimeError {
self.ensure_open()
if idx < 0 || idx >= self.funcs.length() {
raise UndefinedElement
}
self.funcs[idx].callable
}
///|
/// Get function instance by store address (returns None if out of range).
///
/// This is useful for embedding/JIT callbacks that must not raise.
pub fn Store::get_func_inst_opt(self : Store, idx : Int32) -> FuncInst? {
if self.closed.val {
return None
}
if idx < 0 || idx >= self.funcs.length() {
None
} else {
Some(self.funcs[idx].callable)
}
}
///|
/// Get function type by store address
pub fn Store::get_func_type(
self : Store,
idx : Int32,
) -> @types.FuncType raise RuntimeError {
self.ensure_open()
if idx < 0 || idx >= self.func_types.length() {
raise UndefinedElement
}
self.func_types[idx]
}
///|
/// Get function type by store address (returns None if out of range).
///
/// This is useful for embedding/JIT callbacks that must not raise.
pub fn Store::get_func_type_opt(self : Store, idx : Int32) -> @types.FuncType? {
if self.closed.val {
return None
}
if idx < 0 || idx >= self.func_types.length() {
None
} else {
Some(self.func_types[idx])
}
}
///|
/// Get function type index by store address (for structural comparison)
pub fn Store::get_func_type_idx(self : Store, idx : Int32) -> Int32 {
if idx < 0 || idx >= self.func_type_indices.length() {
-1
} else {
self.func_type_indices[idx]
}
}
///|
/// Get canonical type index for a function by store address
/// Returns the canonical type index from the function's owning module
/// Returns -1 if the function or owner is not found
pub fn Store::get_func_canonical_type_idx(
self : Store,
func_addr : Int32,
) -> Int32 {
if func_addr < 0 || func_addr >= self.func_owners.length() {
return -1
}
let owner_idx = match self.func_owners[func_addr] {
Module(idx) => idx
HostFunc => {
// Host function types are declared in the importing module's type graph.
for instance in self.instances {
for index in 0.. return -1
}
if owner_idx >= self.instances.length() {
return -1
}
let instance = self.instances[owner_idx]
let type_idx = self.func_type_indices[func_addr]
if type_idx < 0 || type_idx >= instance.canonical_type_indices.length() {
return -1
}
self.native_types.register(instance)[type_idx]
}
///|
/// Get function code for a store address that is known to refer to a Wasm function.
pub fn Store::get_func(
self : Store,
idx : Int32,
) -> @types.FunctionCode raise RuntimeError {
match self.get_func_inst(idx) {
WasmFunc(code) => code
HostFunc(_) | AsyncHostFunc(_) => raise TypeMismatch // Cannot get code from host function
}
}
///|
pub fn Store::get_table(self : Store, idx : Int32) -> Table raise RuntimeError {
self.ensure_open()
if idx < 0 || idx >= self.tables.length() {
raise UndefinedElement
}
self.tables[idx]
}
///|
/// Get the JIT table corresponding to a runtime table (if exists)
#cfg(target="native")
pub fn Store::get_jit_table(
self : Store,
idx : Int32,
) -> @native_bridge.JITTable? {
if idx < 0 || idx >= self.jit_tables.length() {
return None
}
self.jit_tables[idx]
}
///|
pub fn Store::get_mem(self : Store, idx : Int32) -> Memory raise RuntimeError {
self.ensure_open()
if idx < 0 || idx >= self.mems.length() {
raise UndefinedElement
}
self.mems[idx]
}
///|
/// Get memory by index, rejecting missing memories without fabricating a handle.
pub fn Store::get_memory(
self : Store,
idx : Int32,
) -> Memory raise RuntimeError {
self.get_mem(idx)
}
///|
/// Get WASI module's memory
/// Uses the stored wasi_mem_addr if set, otherwise falls back to index 0
pub fn Store::get_wasi_memory(self : Store) -> Memory raise RuntimeError {
match self.wasi_mem_addr {
Some(addr) => self.get_memory(addr)
None => self.get_memory(0)
}
}
///|
/// Set the current WASI module's memory address
/// Should be called after instantiating a module that uses WASI
pub fn Store::set_wasi_mem_addr(
self : Store,
addr : Int32,
) -> Unit raise RuntimeError {
self.ensure_open()
let old_addr = self.wasi_mem_addr
self.on_instantiation_rollback(fn() { self.wasi_mem_addr = old_addr })
self.wasi_mem_addr = Some(addr)
}
///|
/// Get the WASI context handle (for WAST runner to retrieve WASI context)
pub fn Store::get_wasi_ctx_handle(self : Store) -> Int32 {
self.wasi_ctx_handle
}
///|
/// Set the WASI context handle (for WAST runner to store WASI context)
pub fn Store::set_wasi_ctx_handle(
self : Store,
handle : Int32,
) -> Unit raise RuntimeError {
self.ensure_open()
self.wasi_ctx_handle = handle
}
///|
pub fn Store::get_global(
self : Store,
idx : Int32,
) -> GlobalInstance raise RuntimeError {
self.ensure_open()
if idx < 0 || idx >= self.globals.length() {
raise UndefinedElement
}
self.globals[idx]
}
///|
/// Register a module instance and set ownership for its functions
/// Only sets ownership for functions that don't already have an owner
/// (imported functions already belong to their original module)
pub fn Store::register_instance(
self : Store,
instance : ModuleInstance,
admission? : InstanceAdmission,
) -> Int32 raise RuntimeError {
self.ensure_open()
let instance_idx = self.instances.length()
let owned_funcs : Array[Int32] = []
let owned_tags : Array[Int32] = []
if instance.owner is Some(_) {
self.check_instance(instance)
return instance.store_idx
}
let lease_index = self.instance_leases.length()
let admission = admission.unwrap_or_else(() => self.reserve_instance())
guard physical_equal(admission.owner, self.closed) else { raise ForeignStore }
guard !admission.consumed.val else { raise InvalidHandle }
admission.consumed.val = true
if admission.lease is Some(lease) {
self.instance_leases.push(lease)
}
admission.lease = None
if admission.compilation is Some(lease) {
self.instance_leases.push(lease)
}
admission.compilation = None
admission.validated = None
instance.owner = Some(self.closed)
instance.store_idx = instance_idx
self.instances.push(instance)
// Set ownership only for unowned WASM functions
// HostFunc and already-owned functions are not changed
for func_addr in instance.func_addrs {
if func_addr >= 0 &&
func_addr < self.func_owners.length() &&
self.func_owners[func_addr] == Unowned {
self.func_owners[func_addr] = Module(instance_idx)
owned_funcs.push(func_addr)
}
}
// Set ownership for unowned tags
for tag_addr in instance.tag_addrs {
if tag_addr >= 0 &&
tag_addr < self.tag_owners.length() &&
self.tag_owners[tag_addr] == Unowned {
self.tag_owners[tag_addr] = Module(instance_idx)
owned_tags.push(tag_addr)
}
}
self.on_instantiation_rollback(fn() {
for func_addr in owned_funcs {
if func_addr < self.func_owners.length() {
self.func_owners[func_addr] = Unowned
}
}
for tag_addr in owned_tags {
if tag_addr < self.tag_owners.length() {
self.tag_owners[tag_addr] = Unowned
}
}
for index in lease_index.. ModuleInstance? {
if func_addr < 0 || func_addr >= self.func_owners.length() {
return None
}
match self.func_owners[func_addr] {
Module(idx) =>
if idx < self.instances.length() {
Some(self.instances[idx])
} else {
None
}
_ => None
}
}
///|
/// Get the owning module instance index for a function address
/// Returns -1 if no owner (host function or unowned)
pub fn Store::get_func_owner_idx(self : Store, func_addr : Int32) -> Int32 {
if func_addr < 0 || func_addr >= self.func_owners.length() {
return -1
}
match self.func_owners[func_addr] {
Module(idx) => idx
_ => -1
}
}
// ============================================================
// Garbage Collection
// ============================================================
///|
/// Collect the transitive object roots of this Store and explicit host roots.
pub fn Store::collect_roots(
self : Store,
additional_roots : Array[@types.Value],
) -> Array[@types.Value] {
self.trace_gc_roots(additional_roots).objects
}
///|
/// Reclaim unreachable objects and temporary reference handles.
/// Host Values held outside guest execution must be pinned or supplied here.
#cfg(target="native")
pub fn Store::collect_garbage(
self : Store,
additional_roots~ : Array[@types.Value] = [],
) -> Int32 {
if self.gc_in_collect {
return -1
}
self.gc_in_collect = true
defer {
self.gc_in_collect = false
}
match self.references.native.c_heap {
Some(heap) => {
self.begin_gc_tracing()
defer self.end_gc_tracing()
let trace = self.trace_gc_roots(additional_roots)
self.gc_trace = Some(trace)
let roots = self.native_trace_roots(trace) catch {
_ => {
self.gc_trace = None
return -1
}
}
heap.collect_raw(roots)
}
None => {
let trace = self.trace_gc_roots(additional_roots)
if trace.failed {
return -1
}
let collected = self.heap.collect(trace.objects)
self.release_unreachable_handles(trace)
collected
}
}
}
///|
/// Check if GC should be triggered based on heap usage
/// Returns true if heap usage exceeds the threshold (default 75%)
#cfg(target="native")
pub fn Store::should_collect(self : Store, threshold~ : Double = 0.75) -> Bool {
match self.references.native.c_heap {
Some(heap) => heap.should_collect(threshold~)
None => false
}
}
///|
/// Perform garbage collection if heap usage exceeds the threshold
/// Returns the number of objects collected, or 0 if GC was not triggered
pub fn Store::maybe_collect(
self : Store,
additional_roots~ : Array[@types.Value] = [],
threshold~ : Double = 0.75,
) -> Int32 {
if self.should_collect(threshold~) {
self.collect_garbage(additional_roots~)
} else {
0
}
}
///|
/// Get GC statistics for the C heap
/// Returns (size, capacity, object_count, total_allocations, total_collections)
/// or (0, 0, 0, 0, 0) if no C heap is enabled
#cfg(target="native")
pub fn Store::get_gc_stats(self : Store) -> (Int64, Int64, Int32, Int32, Int32) {
match self.references.native.c_heap {
Some(heap) =>
(
heap.get_size(),
heap.get_capacity(),
heap.get_object_count(),
heap.get_total_allocations(),
heap.get_total_collections(),
)
None => (0L, 0L, 0, 0, 0)
}
}
///|
/// Import shared memory with ownership independent of the source Store.
/// Closing either Store invalidates only its own Memory handle.
pub fn Store::alloc_shared_mem(
self : Store,
memory : Memory,
) -> Int32 raise RuntimeError {
self.ensure_open()
let owned = memory.share()
errdefer owned.close()
self.alloc_mem(owned)
}
///|
fn GcRootLease::retain_for_host(self : GcRootLease) -> Unit {
if self.store is Some(store) &&
store.pinned_gc_roots.get(self.id) is Some(roots) {
roots.guest_owned = false
}
}