Skip to main content

baedeker_core/runtime/
store.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Mutable runtime state for an instantiated module: linear memories and
5//! globals. See [Spec §4.4](https://webassembly.github.io/spec/core/exec/runtime.html).
6
7use alloc::rc::Rc;
8use alloc::string::String;
9use alloc::vec::Vec;
10
11use crate::lower::{RegConstInstr, RegDataMode, RegElemValue, RegElementMode, RegModule};
12use crate::runtime::gpu::{GpuBackend, GpuError, GpuKernelId};
13use crate::runtime::host::HostFunction;
14use crate::runtime::{RuntimeError, RuntimeErrorKind, RuntimeTrap, Value, trap};
15use crate::types::{FuncIdx, GlobalType, Limits, MemType, TableType};
16
17use crate::runtime::verify::F32_ADD_WGSL;
18
19/// Default element count at or above which bulk SIMD work dispatches to
20/// GPU. Below this, CPU execution avoids dispatch overhead. Per-platform
21/// tuning (unified-memory vs discrete GPU) adjusts this at runtime.
22pub const DEFAULT_OFFLOAD_THRESHOLD: usize = 1024;
23
24/// Size of one WebAssembly memory page in bytes.
25pub const PAGE_SIZE: usize = 65536;
26
27/// Entities provided by the host to satisfy a module's state imports
28/// (memories, globals, tables), resolved eagerly at instantiation.
29/// Function imports resolve separately and lazily via
30/// [`Store::register_host_func`].
31#[derive(Debug, Default)]
32pub struct Imports {
33    memories: Vec<MemoryProvider>,
34    globals: Vec<GlobalProvider>,
35    tables: Vec<TableProvider>,
36}
37
38impl Imports {
39    /// An empty import set.
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Provide an imported memory `(module, name)` (fresh allocation).
45    pub fn memory(mut self, module: &str, name: &str, ty: MemType) -> Self {
46        self.memories.push(MemoryProvider {
47            module: module.into(),
48            name: name.into(),
49            ty,
50            shared: None,
51        });
52        self
53    }
54
55    /// Provide an imported memory `(module, name)` linked from another
56    /// module's exports (shared handle).
57    pub fn shared_memory(
58        mut self,
59        module: &str,
60        name: &str,
61        ty: MemType,
62        shared: alloc::rc::Rc<core::cell::RefCell<Vec<u8>>>,
63    ) -> Self {
64        self.memories.push(MemoryProvider {
65            module: module.into(),
66            name: name.into(),
67            ty,
68            shared: Some(shared),
69        });
70        self
71    }
72
73    /// Provide an imported global `(module, name)` with its value.
74    pub fn global(mut self, module: &str, name: &str, ty: GlobalType, value: Value) -> Self {
75        self.globals.push(GlobalProvider {
76            module: module.into(),
77            name: name.into(),
78            ty,
79            value: Some(value),
80            shared: None,
81        });
82        self
83    }
84
85    /// Provide an imported global `(module, name)` linked from another
86    /// module's exports (shared cell).
87    pub fn shared_global(
88        mut self,
89        module: &str,
90        name: &str,
91        ty: GlobalType,
92        shared: alloc::rc::Rc<core::cell::Cell<Value>>,
93    ) -> Self {
94        self.globals.push(GlobalProvider {
95            module: module.into(),
96            name: name.into(),
97            ty,
98            value: None,
99            shared: Some(shared),
100        });
101        self
102    }
103
104    /// Provide an imported table `(module, name)` (fresh allocation).
105    pub fn table(mut self, module: &str, name: &str, ty: TableType) -> Self {
106        self.tables.push(TableProvider {
107            module: module.into(),
108            name: name.into(),
109            ty,
110            shared: None,
111        });
112        self
113    }
114
115    /// Provide an imported table `(module, name)` linked from another
116    /// module's exports (shared handle).
117    pub fn shared_table(
118        mut self,
119        module: &str,
120        name: &str,
121        ty: TableType,
122        shared: alloc::rc::Rc<core::cell::RefCell<crate::runtime::Table>>,
123    ) -> Self {
124        self.tables.push(TableProvider {
125            module: module.into(),
126            name: name.into(),
127            ty,
128            shared: Some(shared),
129        });
130        self
131    }
132}
133
134/// A host-provided memory for import resolution.
135#[derive(Debug)]
136struct MemoryProvider {
137    module: String,
138    name: String,
139    ty: MemType,
140    /// Shared handle when the memory is linked from another module.
141    shared: Option<alloc::rc::Rc<core::cell::RefCell<Vec<u8>>>>,
142}
143
144/// A host-provided global for import resolution.
145#[derive(Debug)]
146struct GlobalProvider {
147    module: String,
148    name: String,
149    ty: GlobalType,
150    value: Option<Value>,
151    /// Shared cell when the global is linked from another module.
152    shared: Option<alloc::rc::Rc<core::cell::Cell<Value>>>,
153}
154
155/// A host-provided table for import resolution.
156#[derive(Debug)]
157struct TableProvider {
158    module: String,
159    name: String,
160    ty: TableType,
161    /// Shared handle when the table is linked from another module.
162    shared: Option<alloc::rc::Rc<core::cell::RefCell<crate::runtime::Table>>>,
163}
164
165/// The null value for a table's element type.
166fn table_null(ty: TableType) -> Value {
167    match ty.elem {
168        crate::types::RefType::ExternRef => Value::ExternRef(None),
169        _ => Value::FuncRef(None),
170    }
171}
172
173/// Allocate `n` copies of `value` with a fallible reservation, failing
174/// cleanly instead of aborting on huge sizes.
175fn try_alloc_n<T: Clone>(n: usize, value: T) -> Option<Vec<T>> {
176    let mut vec = Vec::new();
177    vec.try_reserve(n).ok()?;
178    vec.resize(n, value);
179    Some(vec)
180}
181
182/// Spec limits matching: provided limits must be at least as permissive as
183/// declared import limits.
184fn limits_match(provided: &Limits, declared: &Limits) -> bool {
185    if provided.min < declared.min {
186        return false;
187    }
188    match (provided.max, declared.max) {
189        (_, None) => true,
190        (Some(provided), Some(declared)) => provided <= declared,
191        (None, Some(_)) => false,
192    }
193}
194
195fn allocation_failed(what: &'static str) -> RuntimeError {
196    RuntimeError {
197        kind: RuntimeErrorKind::ResourceLimitExceeded { what },
198    }
199}
200
201fn unknown_import(module: &str, name: &str) -> RuntimeError {
202    RuntimeError {
203        kind: RuntimeErrorKind::UnknownImport {
204            module: module.into(),
205            name: name.into(),
206        },
207    }
208}
209
210fn import_type_mismatch(module: &str, name: &str) -> RuntimeError {
211    RuntimeError {
212        kind: RuntimeErrorKind::ImportTypeMismatch {
213            module: module.into(),
214            name: name.into(),
215        },
216    }
217}
218
219/// Whether a provided global type matches a declared import type per the
220/// spec's import matching: immutable globals are covariant (provided may be
221/// a subtype of declared), mutable globals are invariant (types must be
222/// exactly equal), and mutability must match.
223fn global_type_matches(provided: &GlobalType, declared: &GlobalType) -> bool {
224    use crate::types::{HeapType, Mutability, RefType, ValType};
225
226    fn valtype_is_subtype(provided: &ValType, declared: &ValType) -> bool {
227        match (provided, declared) {
228            (ValType::Ref(provided), ValType::Ref(declared)) => match (provided, declared) {
229                (RefType::FuncRef, RefType::FuncRef) | (RefType::ExternRef, RefType::ExternRef) => {
230                    true
231                }
232                (
233                    RefType::Typed {
234                        nullable: provided_nullable,
235                        heap: provided_heap,
236                    },
237                    RefType::Typed {
238                        nullable: declared_nullable,
239                        heap: declared_heap,
240                    },
241                ) => {
242                    (!*provided_nullable || *declared_nullable)
243                        && provided_heap.is_subtype_of(*declared_heap)
244                }
245                (RefType::Typed { heap, .. }, RefType::FuncRef) => {
246                    heap.is_subtype_of(HeapType::Func)
247                }
248                (RefType::Typed { heap, .. }, RefType::ExternRef) => *heap == HeapType::Extern,
249                _ => false,
250            },
251            (provided, declared) => provided == declared,
252        }
253    }
254
255    match (provided.mutability, declared.mutability) {
256        (Mutability::Const, Mutability::Const) => {
257            valtype_is_subtype(&provided.val_type, &declared.val_type)
258        }
259        (Mutability::Var, Mutability::Var) => provided.val_type == declared.val_type,
260        _ => false,
261    }
262}
263
264/// A link group: instances that can reach each other's funcref values by
265/// instance id. Created per link cluster (e.g. one per spec test file).
266pub type LinkGroup = alloc::collections::BTreeMap<
267    u32,
268    (
269        alloc::rc::Rc<crate::lower::RegModule>,
270        alloc::rc::Rc<core::cell::RefCell<Store>>,
271    ),
272>;
273
274/// Mutable state of an instantiated module.
275///
276/// Defined memories and globals only; the imported index-space prefix is
277/// tracked so accesses to imported memories/globals can fail explicitly.
278#[derive(Debug)]
279pub struct Store {
280    memories: Vec<alloc::rc::Rc<core::cell::RefCell<Vec<u8>>>>,
281    memory_types: Vec<MemType>,
282    globals: Vec<alloc::rc::Rc<core::cell::Cell<Value>>>,
283    global_types: Vec<GlobalType>,
284    tables: Vec<alloc::rc::Rc<core::cell::RefCell<crate::runtime::Table>>>,
285    table_types: Vec<TableType>,
286    /// Element segment storage; `None` after the segment is dropped (or was
287    /// active/declarative at instantiation).
288    elements: core::cell::RefCell<Vec<Option<Vec<Value>>>>,
289    /// Data segment storage; `None` after the segment is dropped (or was
290    /// active at instantiation).
291    data: core::cell::RefCell<Vec<Option<Vec<u8>>>>,
292    /// Export declarations (from the lowered module).
293    exports: Vec<crate::lower::RegExport>,
294    /// This instance's id within its link group (0 when unlinked).
295    instance_id: u32,
296    /// Remaining instruction fuel; `None` = unlimited (default). Embedders
297    /// running untrusted modules set a budget via `set_fuel`; the
298    /// interpreter errors with `FuelExhausted` at zero.
299    fuel: core::cell::Cell<Option<u64>>,
300    /// The start function, deferred until [`Store::run_start`] is called
301    /// (host functions must be registered before it runs).
302    pending_start: Option<crate::types::FuncIdx>,
303    /// The link group this instance belongs to, if any.
304    link_group: Option<alloc::rc::Rc<core::cell::RefCell<LinkGroup>>>,
305    /// Optional GPU backend for bulk SIMD offload (Borsalino Level 1).
306    gpu: core::cell::RefCell<Option<alloc::boxed::Box<dyn GpuBackend>>>,
307    /// Element count at or above which bulk SIMD work dispatches to GPU.
308    offload_threshold: usize,
309    /// Lazily compiled offload kernels, keyed by kernel name.
310    offload_kernels: alloc::collections::BTreeMap<&'static str, GpuKernelId>,
311    /// Function import declarations (from the lowered module).
312    imported_funcs: Vec<crate::lower::RegImport>,
313    /// Registered host functions, one slot per function import.
314    host_funcs: Vec<Option<core::cell::RefCell<HostFunction>>>,
315    imported_memories: Vec<alloc::rc::Rc<core::cell::RefCell<Vec<u8>>>>,
316    imported_memory_types: Vec<MemType>,
317    imported_global_values: Vec<alloc::rc::Rc<core::cell::Cell<Value>>>,
318    imported_global_types: Vec<GlobalType>,
319    imported_tables: Vec<alloc::rc::Rc<core::cell::RefCell<crate::runtime::Table>>>,
320    imported_table_types: Vec<TableType>,
321    imported_memory_count: u32,
322    imported_global_count: u32,
323    imported_table_count: u32,
324}
325
326impl Store {
327    /// Instantiate a lowered module with an empty import set. Modules with
328    /// state imports (memories, globals, tables) need
329    /// [`Store::instantiate_with_imports`].
330    pub fn instantiate(module: &RegModule) -> Result<Self, RuntimeError> {
331        Self::instantiate_with_imports(module, &Imports::new())
332    }
333
334    /// Instantiate and register the instance in a link group with the given
335    /// instance id. Use for modules that link against each other so funcref
336    /// values carry cross-instance identity.
337    pub fn instantiate_linked(
338        module: &Rc<RegModule>,
339        imports: &Imports,
340        group: &alloc::rc::Rc<core::cell::RefCell<LinkGroup>>,
341        instance_id: u32,
342    ) -> Result<alloc::rc::Rc<core::cell::RefCell<Store>>, RuntimeError> {
343        let (mut store, error) = Self::instantiate_internal(module, imports, instance_id)?;
344        store.link_group = Some(group.clone());
345        let store = alloc::rc::Rc::new(core::cell::RefCell::new(store));
346        // Register the instance even when instantiation failed partway:
347        // spec semantics persist element segment writes into shared tables
348        // from failed instantiations, and those functions remain callable.
349        group
350            .borrow_mut()
351            .insert(instance_id, (module.clone(), store.clone()));
352        match error {
353            Some(error) => Err(error),
354            None => Ok(store),
355        }
356    }
357
358    /// Instantiate a lowered module: resolve state imports eagerly from
359    /// `imports`, zero memories to their minimum size, evaluate global
360    /// initializers in declaration order, apply active data segments, and
361    /// run the start function if present.
362    pub fn instantiate_with_imports(
363        module: &RegModule,
364        imports: &Imports,
365    ) -> Result<Self, RuntimeError> {
366        let (store, error) = Self::instantiate_internal(module, imports, 0)?;
367        match error {
368            Some(error) => Err(error),
369            None => Ok(store),
370        }
371    }
372
373    /// Early-phase failures (import resolution, allocation, global init)
374    /// produce no instance. Late-phase failures (segment application, start)
375    /// produce a partially instantiated instance that callers may still
376    /// register (spec: writes before the failure persist in shared tables).
377    fn instantiate_internal(
378        module: &RegModule,
379        imports: &Imports,
380        instance_id: u32,
381    ) -> Result<(Self, Option<RuntimeError>), RuntimeError> {
382        // Resolve imported memories eagerly (spec limits matching).
383        let mut imported_memories = Vec::with_capacity(module.imported_memories.len());
384        let mut imported_memory_types = Vec::with_capacity(module.imported_memories.len());
385        for declared in &module.imported_memories {
386            let provider = imports
387                .memories
388                .iter()
389                .find(|provider| {
390                    provider.module == declared.module && provider.name == declared.name
391                })
392                .ok_or_else(|| unknown_import(&declared.module, &declared.name))?;
393            // Import matching for linked entities uses the entity's CURRENT
394            // size as the effective minimum (spec: external limits grow).
395            let provided_limits = match &provider.shared {
396                Some(shared) => Limits {
397                    min: (shared.borrow().len() / PAGE_SIZE) as u32,
398                    max: provider.ty.limits.max,
399                },
400                None => provider.ty.limits,
401            };
402            if !limits_match(&provided_limits, &declared.ty.limits) {
403                return Err(import_type_mismatch(&declared.module, &declared.name));
404            }
405            let entity = match provider.shared.clone() {
406                Some(shared) => shared,
407                None => {
408                    let bytes = try_alloc_n(provider.ty.limits.min as usize * PAGE_SIZE, 0u8)
409                        .ok_or_else(|| allocation_failed("imported memory"))?;
410                    alloc::rc::Rc::new(core::cell::RefCell::new(bytes))
411                }
412            };
413            imported_memories.push(entity);
414            imported_memory_types.push(provider.ty);
415        }
416
417        // Resolve imported globals eagerly (exact type match).
418        let mut imported_global_values = Vec::with_capacity(module.imported_globals.len());
419        let mut imported_global_types = Vec::with_capacity(module.imported_globals.len());
420        for declared in &module.imported_globals {
421            let provider = imports
422                .globals
423                .iter()
424                .find(|provider| {
425                    provider.module == declared.module && provider.name == declared.name
426                })
427                .ok_or_else(|| unknown_import(&declared.module, &declared.name))?;
428            if !global_type_matches(&provider.ty, &declared.ty) {
429                return Err(import_type_mismatch(&declared.module, &declared.name));
430            }
431            let entity = match (&provider.shared, provider.value) {
432                (Some(shared), _) => shared.clone(),
433                (None, Some(value)) => {
434                    if value.val_type() != declared.ty.val_type {
435                        return Err(import_type_mismatch(&declared.module, &declared.name));
436                    }
437                    alloc::rc::Rc::new(core::cell::Cell::new(value))
438                }
439                (None, None) => unreachable!("global provider sets value or shared"),
440            };
441            imported_global_values.push(entity);
442            imported_global_types.push(provider.ty);
443        }
444
445        // Resolve imported tables eagerly (elem type exact, limits matching).
446        let mut imported_tables = Vec::with_capacity(module.imported_tables.len());
447        let mut imported_table_types = Vec::with_capacity(module.imported_tables.len());
448        for declared in &module.imported_tables {
449            let provider = imports
450                .tables
451                .iter()
452                .find(|provider| {
453                    provider.module == declared.module && provider.name == declared.name
454                })
455                .ok_or_else(|| unknown_import(&declared.module, &declared.name))?;
456            let provided_limits = match &provider.shared {
457                Some(shared) => Limits {
458                    min: shared.borrow().len(),
459                    max: provider.ty.limits.max,
460                },
461                None => provider.ty.limits,
462            };
463            if provider.ty.elem != declared.ty.elem
464                || !limits_match(&provided_limits, &declared.ty.limits)
465            {
466                return Err(import_type_mismatch(&declared.module, &declared.name));
467            }
468            let entity = match provider.shared.clone() {
469                Some(shared) => shared,
470                None => {
471                    let values = crate::runtime::Table::new(
472                        provider.ty.clone(),
473                        table_null(provider.ty.clone()),
474                    )
475                    .ok_or_else(|| allocation_failed("imported table"))?;
476                    alloc::rc::Rc::new(core::cell::RefCell::new(values))
477                }
478            };
479            imported_tables.push(entity);
480            imported_table_types.push(provider.ty.clone());
481        }
482
483        let imported_globals_plain: Vec<Value> = imported_global_values
484            .iter()
485            .map(|cell| cell.get())
486            .collect();
487        let mut globals_plain: Vec<Value> = Vec::with_capacity(module.globals.len());
488        let mut globals: Vec<alloc::rc::Rc<core::cell::Cell<Value>>> =
489            Vec::with_capacity(module.globals.len());
490        let mut global_types = Vec::with_capacity(module.globals.len());
491        for global in &module.globals {
492            let value = eval_const(
493                &global.init,
494                &globals_plain,
495                &imported_globals_plain,
496                module.imported_global_count,
497                instance_id,
498            )?;
499            globals_plain.push(value);
500            globals.push(alloc::rc::Rc::new(core::cell::Cell::new(value)));
501            global_types.push(crate::types::GlobalType {
502                val_type: global.ty,
503                mutability: if global.mutable {
504                    crate::types::Mutability::Var
505                } else {
506                    crate::types::Mutability::Const
507                },
508            });
509        }
510
511        let mut memories = Vec::with_capacity(module.memories.len());
512        for mem in &module.memories {
513            let bytes = try_alloc_n(mem.limits.min as usize * PAGE_SIZE, 0u8)
514                .ok_or_else(|| allocation_failed("memory"))?;
515            memories.push(alloc::rc::Rc::new(core::cell::RefCell::new(bytes)));
516        }
517        let memory_types = module.memories.clone();
518
519        let mut tables = Vec::with_capacity(module.tables.len());
520        for table in &module.tables {
521            let fill = match &table.init {
522                Some(init) => {
523                    let lowered =
524                        crate::lower::lower_const_expr(init, 0).map_err(|_error| RuntimeError {
525                            kind: RuntimeErrorKind::InvalidConstExpr,
526                        })?;
527                    eval_const(
528                        &lowered,
529                        &globals_plain,
530                        &imported_globals_plain,
531                        module.imported_global_count,
532                        instance_id,
533                    )?
534                }
535                None => table_null(table.clone()),
536            };
537            let values = crate::runtime::Table::new(table.clone(), fill)
538                .ok_or_else(|| allocation_failed("table"))?;
539            tables.push(alloc::rc::Rc::new(core::cell::RefCell::new(values)));
540        }
541        let table_types = module.tables.clone();
542
543        let mut store = Self {
544            memories,
545            memory_types,
546            globals,
547            global_types,
548            tables,
549            table_types,
550            elements: core::cell::RefCell::new(alloc::vec![None; module.elements.len()]),
551            data: core::cell::RefCell::new(Vec::new()),
552            exports: module.exports.clone(),
553            instance_id,
554            fuel: core::cell::Cell::new(None),
555            pending_start: None,
556            link_group: None,
557            gpu: core::cell::RefCell::new(None),
558            offload_threshold: DEFAULT_OFFLOAD_THRESHOLD,
559            offload_kernels: alloc::collections::BTreeMap::new(),
560            imported_funcs: module.imported_funcs.clone(),
561            host_funcs: (0..module.imported_funcs.len()).map(|_| None).collect(),
562            imported_memories,
563            imported_memory_types,
564            imported_global_values,
565            imported_global_types,
566            imported_tables,
567            imported_table_types,
568            imported_memory_count: module.imported_memory_count,
569            imported_global_count: module.imported_global_count,
570            imported_table_count: module.imported_table_count,
571        };
572
573        let mut instantiation_error: Option<RuntimeError> = None;
574
575        // Element segments: active ones are written into their tables (and
576        // dropped); passive ones are retained for `table.init`. On the first
577        // failure, remaining segments are skipped but earlier writes persist
578        // (spec: writes before the failure persist in shared tables).
579        for (idx, segment) in module.elements.iter().enumerate() {
580            if instantiation_error.is_some() {
581                break;
582            }
583            let values: Vec<Value> = segment
584                .values
585                .iter()
586                .map(|value| match *value {
587                    RegElemValue::FuncRef(func) => Value::FuncRef(Some((instance_id, func.0))),
588                    RegElemValue::GlobalGet(global) => {
589                        let idx = global.0 as usize;
590                        if idx < store.imported_global_count as usize {
591                            store
592                                .imported_global_values
593                                .get(idx)
594                                .expect("imported globals resolved")
595                                .get()
596                        } else {
597                            store
598                                .globals
599                                .get(idx - store.imported_global_count as usize)
600                                .expect("defined globals initialized")
601                                .get()
602                        }
603                    }
604                    RegElemValue::Null => Value::FuncRef(None),
605                })
606                .collect();
607            match &segment.mode {
608                RegElementMode::Active { table, offset } => {
609                    let offset = match eval_const(
610                        offset,
611                        &globals_plain,
612                        &imported_globals_plain,
613                        store.imported_global_count,
614                        store.instance_id,
615                    ) {
616                        Ok(offset) => offset,
617                        Err(error) => {
618                            instantiation_error = Some(error);
619                            break;
620                        }
621                    };
622                    let Value::I32(offset) = offset else {
623                        instantiation_error = Some(RuntimeError {
624                            kind: RuntimeErrorKind::InvalidConstExpr,
625                        });
626                        break;
627                    };
628                    let applied = store
629                        .with_table_mut(table.0, |target| {
630                            if target.write_slice(offset as u32, &values) {
631                                Ok(())
632                            } else {
633                                Err(trap(RuntimeTrap::OutOfBoundsTableAccess))
634                            }
635                        })
636                        .ok_or(RuntimeError {
637                            kind: RuntimeErrorKind::UnknownTable { table: table.0 },
638                        })
639                        .and_then(|result| result);
640                    if let Err(error) = applied {
641                        instantiation_error = Some(error);
642                        break;
643                    }
644                }
645                RegElementMode::Passive => {
646                    store.elements.borrow_mut()[idx] = Some(values);
647                }
648                RegElementMode::Dropped => {}
649            }
650        }
651
652        for segment in &module.data {
653            if instantiation_error.is_some() {
654                break;
655            }
656            match &segment.mode {
657                RegDataMode::Active { memory, offset } => {
658                    let offset = match eval_const(
659                        offset,
660                        &globals_plain,
661                        &imported_globals_plain,
662                        store.imported_global_count,
663                        store.instance_id,
664                    ) {
665                        Ok(offset) => offset,
666                        Err(error) => {
667                            instantiation_error = Some(error);
668                            break;
669                        }
670                    };
671                    let Value::I32(offset) = offset else {
672                        instantiation_error = Some(RuntimeError {
673                            kind: RuntimeErrorKind::InvalidConstExpr,
674                        });
675                        break;
676                    };
677                    let applied = store
678                        .with_memory_mut(memory.0, |mem| {
679                            let start = offset as usize;
680                            let Some(end) = start.checked_add(segment.bytes.len()) else {
681                                return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
682                            };
683                            if end > mem.len() {
684                                return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
685                            }
686                            mem[start..end].copy_from_slice(&segment.bytes);
687                            Ok(())
688                        })
689                        .ok_or(RuntimeError {
690                            kind: RuntimeErrorKind::UnknownMemory { memory: memory.0 },
691                        })
692                        .and_then(|result| result);
693                    if let Err(error) = applied {
694                        instantiation_error = Some(error);
695                        break;
696                    }
697                    store.data.borrow_mut().push(None);
698                }
699                RegDataMode::Passive => {
700                    store.data.borrow_mut().push(Some(segment.bytes.clone()));
701                }
702            }
703        }
704
705        // The start function is deferred to [`Store::run_start`] so embedders
706        // can register host functions first (skipped when instantiation
707        // already failed).
708        if instantiation_error.is_none() {
709            store.pending_start = module.start;
710        }
711
712        Ok((store, instantiation_error))
713    }
714
715    /// Run the deferred start function, if any. Call after registering host
716    /// functions; safe to call multiple times (runs at most once).
717    pub fn run_start(&mut self, module: &RegModule) -> Result<(), RuntimeError> {
718        let Some(start_idx) = self.pending_start.take() else {
719            return Ok(());
720        };
721        if start_idx.0 < module.imported_func_count {
722            self.call_host(start_idx.0, &[])?;
723            return Ok(());
724        }
725        let func = module
726            .funcs
727            .iter()
728            .find(|func| func.idx == start_idx)
729            .ok_or(RuntimeError {
730                kind: RuntimeErrorKind::UnknownFunction { func: start_idx.0 },
731            })?;
732        crate::runtime::execute_func_in(Some(module), Some(self), func, &[], 0)?;
733        Ok(())
734    }
735
736    /// The shared handle of a memory by index-space index (imported first).
737    ///
738    /// Exposed so host-module crates (e.g. the GPU host module) can capture a
739    /// guest-memory handle at registration time and read/write it from inside
740    /// host-function closures. The handle stays valid across `memory.grow`,
741    /// which mutates the `Vec` in place rather than replacing it.
742    pub fn shared_memory(&self, idx: u32) -> Option<alloc::rc::Rc<core::cell::RefCell<Vec<u8>>>> {
743        let idx = idx as usize;
744        if idx < self.imported_memory_count as usize {
745            return self.imported_memories.get(idx).cloned();
746        }
747        self.memories
748            .get(idx - self.imported_memory_count as usize)
749            .cloned()
750    }
751
752    /// The declared type of a memory by index-space index (imported first).
753    pub(crate) fn memory_type(&self, idx: u32) -> Option<&MemType> {
754        let idx = idx as usize;
755        if idx < self.imported_memory_count as usize {
756            return self.imported_memory_types.get(idx);
757        }
758        self.memory_types
759            .get(idx - self.imported_memory_count as usize)
760    }
761
762    /// Read a global by index-space index (imported first).
763    pub(crate) fn global(&self, idx: u32) -> Option<Value> {
764        self.shared_global(idx).map(|cell| cell.get())
765    }
766
767    /// Write a global by index-space index (imported first).
768    pub(crate) fn set_global(&self, idx: u32, value: Value) -> Option<()> {
769        let cell = self.shared_global(idx)?;
770        cell.set(value);
771        Some(())
772    }
773
774    /// The shared cell of a global by index-space index (imported first).
775    pub(crate) fn shared_global(&self, idx: u32) -> Option<alloc::rc::Rc<core::cell::Cell<Value>>> {
776        let idx = idx as usize;
777        if idx < self.imported_global_count as usize {
778            return self.imported_global_values.get(idx).cloned();
779        }
780        self.globals
781            .get(idx - self.imported_global_count as usize)
782            .cloned()
783    }
784
785    /// The declared type of a global by index-space index (imported first).
786    pub(crate) fn global_type(&self, idx: u32) -> Option<&GlobalType> {
787        let idx = idx as usize;
788        if idx < self.imported_global_count as usize {
789            return self.imported_global_types.get(idx);
790        }
791        self.global_types
792            .get(idx - self.imported_global_count as usize)
793    }
794
795    /// Read a table by index-space index (imported first) via closure.
796    pub(crate) fn with_table<R>(
797        &self,
798        idx: u32,
799        f: impl FnOnce(&crate::runtime::Table) -> R,
800    ) -> Option<R> {
801        let shared = self.shared_table(idx)?;
802        let table = shared.borrow();
803        Some(f(&table))
804    }
805
806    /// Mutate a table by index-space index (imported first) via closure.
807    pub(crate) fn with_table_mut<R>(
808        &self,
809        idx: u32,
810        f: impl FnOnce(&mut crate::runtime::Table) -> R,
811    ) -> Option<R> {
812        let shared = self.shared_table(idx)?;
813        let mut table = shared.borrow_mut();
814        Some(f(&mut table))
815    }
816
817    /// The shared handle of a table by index-space index (imported first).
818    pub(crate) fn shared_table(
819        &self,
820        idx: u32,
821    ) -> Option<alloc::rc::Rc<core::cell::RefCell<crate::runtime::Table>>> {
822        let idx = idx as usize;
823        if idx < self.imported_table_count as usize {
824            return self.imported_tables.get(idx).cloned();
825        }
826        self.tables
827            .get(idx - self.imported_table_count as usize)
828            .cloned()
829    }
830
831    /// The declared type of a table by index-space index (imported first).
832    pub(crate) fn table_type(&self, idx: u32) -> Option<&TableType> {
833        let idx = idx as usize;
834        if idx < self.imported_table_count as usize {
835            return self.imported_table_types.get(idx);
836        }
837        self.table_types
838            .get(idx - self.imported_table_count as usize)
839    }
840
841    /// An exported function by name.
842    pub fn export_func(&self, name: &str) -> Option<FuncIdx> {
843        self.exports.iter().find_map(|export| match export.desc {
844            crate::lower::RegExportDesc::Func(idx) if export.name == name => Some(idx),
845            _ => None,
846        })
847    }
848
849    /// An exported memory by name: its declared type and shared handle.
850    pub fn export_memory(
851        &self,
852        name: &str,
853    ) -> Option<(MemType, alloc::rc::Rc<core::cell::RefCell<Vec<u8>>>)> {
854        let idx = self.exports.iter().find_map(|export| match export.desc {
855            crate::lower::RegExportDesc::Mem(idx) if export.name == name => Some(idx),
856            _ => None,
857        })?;
858        Some((*self.memory_type(idx.0)?, self.shared_memory(idx.0)?))
859    }
860
861    /// An exported global by name: its declared type and shared cell.
862    pub fn export_global(
863        &self,
864        name: &str,
865    ) -> Option<(GlobalType, alloc::rc::Rc<core::cell::Cell<Value>>)> {
866        let idx = self.exports.iter().find_map(|export| match export.desc {
867            crate::lower::RegExportDesc::Global(idx) if export.name == name => Some(idx),
868            _ => None,
869        })?;
870        Some((*self.global_type(idx.0)?, self.shared_global(idx.0)?))
871    }
872
873    /// An exported table by name: its declared type and shared handle.
874    pub fn export_table(
875        &self,
876        name: &str,
877    ) -> Option<(
878        TableType,
879        alloc::rc::Rc<core::cell::RefCell<crate::runtime::Table>>,
880    )> {
881        let idx = self.exports.iter().find_map(|export| match export.desc {
882            crate::lower::RegExportDesc::Table(idx) if export.name == name => Some(idx),
883            _ => None,
884        })?;
885        Some((self.table_type(idx.0)?.clone(), self.shared_table(idx.0)?))
886    }
887
888    /// Read a retained element segment by index via closure.
889    pub(crate) fn with_elem<R>(
890        &self,
891        idx: u32,
892        f: impl FnOnce(Option<&Vec<Value>>) -> R,
893    ) -> Option<R> {
894        let elements = self.elements.borrow();
895        let slot = elements.get(idx as usize)?;
896        Some(f(slot.as_ref()))
897    }
898
899    /// Drop an element segment's storage.
900    pub(crate) fn drop_elem(&self, idx: u32) -> Option<()> {
901        let mut elements = self.elements.borrow_mut();
902        let slot = elements.get_mut(idx as usize)?;
903        *slot = None;
904        Some(())
905    }
906
907    /// Read a retained data segment by index via closure.
908    pub(crate) fn with_data<R>(
909        &self,
910        idx: u32,
911        f: impl FnOnce(Option<&Vec<u8>>) -> R,
912    ) -> Option<R> {
913        let data = self.data.borrow();
914        let slot = data.get(idx as usize)?;
915        Some(f(slot.as_ref()))
916    }
917
918    /// Drop a data segment's storage.
919    pub(crate) fn drop_data(&self, idx: u32) -> Option<()> {
920        let mut data = self.data.borrow_mut();
921        let slot = data.get_mut(idx as usize)?;
922        *slot = None;
923        Some(())
924    }
925
926    /// Read a global's current value by index-space index (embedding access).
927    pub fn get_global(&self, idx: u32) -> Option<Value> {
928        self.global(idx)
929    }
930
931    /// Read a memory's bytes by index-space index via closure (embedding
932    /// access; memories are shared handles, so direct slices are not
933    /// available).
934    pub fn with_memory<R>(&self, idx: u32, f: impl FnOnce(&[u8]) -> R) -> Option<R> {
935        let shared = self.shared_memory(idx)?;
936        let mem = shared.borrow();
937        Some(f(&mem))
938    }
939
940    /// Mutate a memory's bytes by index-space index via closure (embedding
941    /// access).
942    pub fn with_memory_mut<R>(&self, idx: u32, f: impl FnOnce(&mut [u8]) -> R) -> Option<R> {
943        let shared = self.shared_memory(idx)?;
944        let mut mem = shared.borrow_mut();
945        Some(f(&mut mem))
946    }
947
948    /// Install a GPU backend for bulk SIMD offload.
949    pub fn set_gpu(&mut self, backend: alloc::boxed::Box<dyn GpuBackend>) {
950        *self.gpu.borrow_mut() = Some(backend);
951    }
952
953    /// Remove the installed GPU backend, if any.
954    pub fn clear_gpu(&mut self) {
955        *self.gpu.borrow_mut() = None;
956    }
957
958    /// Whether a GPU backend is installed.
959    pub fn has_gpu(&self) -> bool {
960        self.gpu.borrow().is_some()
961    }
962
963    /// Mutable access to the installed GPU backend via closure.
964    pub fn with_gpu_mut<R>(&self, f: impl FnOnce(&mut dyn GpuBackend) -> R) -> Option<R> {
965        let mut gpu = self.gpu.borrow_mut();
966        let backend = gpu.as_mut()?;
967        Some(f(&mut **backend))
968    }
969
970    /// Register a host function for a function import `(module, name)`.
971    ///
972    /// Fails with [`RuntimeErrorKind::UnknownImport`] when the module does
973    /// not declare that import, or [`RuntimeErrorKind::ImportTypeMismatch`]
974    /// when the signatures differ.
975    pub fn register_host_func(
976        &mut self,
977        module: &str,
978        name: &str,
979        func: HostFunction,
980    ) -> Result<(), RuntimeError> {
981        let Some(pos) = self
982            .imported_funcs
983            .iter()
984            .position(|import| import.module == module && import.name == name)
985        else {
986            return Err(RuntimeError {
987                kind: RuntimeErrorKind::UnknownImport {
988                    module: module.into(),
989                    name: name.into(),
990                },
991            });
992        };
993        if func.ty() != &self.imported_funcs[pos].ty {
994            return Err(RuntimeError {
995                kind: RuntimeErrorKind::ImportTypeMismatch {
996                    module: module.into(),
997                    name: name.into(),
998                },
999            });
1000        }
1001        self.host_funcs[pos] = Some(core::cell::RefCell::new(func));
1002        Ok(())
1003    }
1004
1005    /// This instance's id within its link group (0 when unlinked).
1006    pub(crate) fn instance_id(&self) -> u32 {
1007        self.instance_id
1008    }
1009
1010    /// Set the instruction fuel budget (`None` = unlimited, the default).
1011    ///
1012    /// Fuel is charged per executed instruction (and per block dispatch)
1013    /// across all calls made from this store. Cross-instance calls charge
1014    /// the callee's store, not the caller's — each instance has its own
1015    /// budget.
1016    pub fn set_fuel(&self, fuel: Option<u64>) {
1017        self.fuel.set(fuel);
1018    }
1019
1020    /// Remaining fuel, or `None` when unlimited.
1021    pub fn fuel(&self) -> Option<u64> {
1022        self.fuel.get()
1023    }
1024
1025    /// Charge one unit of fuel; `false` when the budget is exhausted.
1026    pub(crate) fn charge_fuel(&self) -> bool {
1027        let Some(remaining) = self.fuel.get() else {
1028            return true;
1029        };
1030        if remaining == 0 {
1031            return false;
1032        }
1033        self.fuel.set(Some(remaining - 1));
1034        true
1035    }
1036
1037    /// The link group this instance belongs to, if any.
1038    pub(crate) fn link_group(&self) -> Option<&alloc::rc::Rc<core::cell::RefCell<LinkGroup>>> {
1039        self.link_group.as_ref()
1040    }
1041
1042    /// Invoke the registered host function for an imported function index,
1043    /// failing with [`RuntimeErrorKind::UnknownImport`] when unregistered.
1044    pub(crate) fn call_host(
1045        &self,
1046        func_idx: u32,
1047        args: &[Value],
1048    ) -> Result<Vec<Value>, RuntimeError> {
1049        if self
1050            .host_funcs
1051            .get(func_idx as usize)
1052            .and_then(Option::as_ref)
1053            .is_none()
1054        {
1055            let (module, name) = self
1056                .imported_func(func_idx)
1057                .map(|import| (import.module.clone(), import.name.clone()))
1058                .unwrap_or_default();
1059            return Err(RuntimeError {
1060                kind: RuntimeErrorKind::UnknownImport { module, name },
1061            });
1062        }
1063        self.host_funcs[func_idx as usize]
1064            .as_ref()
1065            .expect("registration checked above")
1066            .borrow_mut()
1067            .call(args)
1068    }
1069
1070    /// The import declaration for an imported function index.
1071    pub(crate) fn imported_func(&self, func_idx: u32) -> Option<&crate::lower::RegImport> {
1072        self.imported_funcs.get(func_idx as usize)
1073    }
1074
1075    /// Set the element count at or above which bulk SIMD work dispatches
1076    /// to GPU (per-platform tuning).
1077    pub fn set_offload_threshold(&mut self, threshold: usize) {
1078        self.offload_threshold = threshold;
1079    }
1080
1081    /// Bulk element-wise f32 addition over linear-memory regions:
1082    /// `out[i] = a[i] + b[i]` for `count` f32 elements (Borsalino Level 1).
1083    ///
1084    /// At or above the offload threshold with a GPU backend installed, the
1085    /// work dispatches to GPU; otherwise it executes on CPU. Memory 0.
1086    pub fn f32_add_region(
1087        &mut self,
1088        a_ptr: u32,
1089        b_ptr: u32,
1090        out_ptr: u32,
1091        count: usize,
1092    ) -> Result<(), RuntimeError> {
1093        let byte_len = count
1094            .checked_mul(4)
1095            .ok_or(trap(RuntimeTrap::OutOfBoundsMemoryAccess))?;
1096        let mem_len = self.with_memory(0, |mem| mem.len()).ok_or(RuntimeError {
1097            kind: RuntimeErrorKind::UnknownMemory { memory: 0 },
1098        })? as u64;
1099        for ptr in [a_ptr, b_ptr, out_ptr] {
1100            let end = ptr as u64 + byte_len as u64;
1101            if end > mem_len {
1102                return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1103            }
1104        }
1105
1106        if !self.has_gpu() || count < self.offload_threshold {
1107            // CPU path: element-wise add over the regions.
1108            self.with_memory_mut(0, |mem| {
1109                for i in 0..count {
1110                    let at = a_ptr as usize + i * 4;
1111                    let bt = b_ptr as usize + i * 4;
1112                    let ot = out_ptr as usize + i * 4;
1113                    let lhs =
1114                        f32::from_le_bytes(mem[at..at + 4].try_into().expect("width checked"));
1115                    let rhs =
1116                        f32::from_le_bytes(mem[bt..bt + 4].try_into().expect("width checked"));
1117                    mem[ot..ot + 4].copy_from_slice(&(lhs + rhs).to_le_bytes());
1118                }
1119            })
1120            .expect("memory 0 length checked above");
1121            return Ok(());
1122        }
1123
1124        // GPU path: compile (once), upload regions, dispatch, read back.
1125        let kernel = match self.offload_kernels.get("f32_add") {
1126            Some(&kernel) => kernel,
1127            None => {
1128                let kernel = self
1129                    .with_gpu_mut(|gpu| gpu.compile("vadd", F32_ADD_WGSL))
1130                    .expect("gpu checked above")
1131                    .map_err(runtime_gpu_error)?;
1132                self.offload_kernels.insert("f32_add", kernel);
1133                kernel
1134            }
1135        };
1136
1137        let mem = self.shared_memory(0).ok_or(RuntimeError {
1138            kind: RuntimeErrorKind::UnknownMemory { memory: 0 },
1139        })?;
1140        let (a_bytes, b_bytes) = {
1141            let mem = mem.borrow();
1142            (
1143                mem[a_ptr as usize..a_ptr as usize + byte_len].to_vec(),
1144                mem[b_ptr as usize..b_ptr as usize + byte_len].to_vec(),
1145            )
1146        };
1147        let buf_a = self
1148            .with_gpu_mut(|gpu| gpu.create_buffer(&a_bytes))
1149            .expect("gpu checked above")
1150            .map_err(runtime_gpu_error)?;
1151        let buf_b = self
1152            .with_gpu_mut(|gpu| gpu.create_buffer(&b_bytes))
1153            .expect("gpu checked above")
1154            .map_err(runtime_gpu_error)?;
1155        let buf_out = self
1156            .with_gpu_mut(|gpu| gpu.create_buffer_uninit(byte_len))
1157            .expect("gpu checked above")
1158            .map_err(runtime_gpu_error)?;
1159        let workgroups = [count.div_ceil(256) as u32, 1, 1];
1160        self.with_gpu_mut(|gpu| {
1161            gpu.dispatch_verified(kernel, &[buf_a, buf_b, buf_out], workgroups, [256, 1, 1])
1162        })
1163        .expect("gpu checked above")
1164        .map_err(runtime_gpu_error)?;
1165        let result = self
1166            .with_gpu_mut(|gpu| gpu.read_buffer(buf_out))
1167            .expect("gpu checked above")
1168            .map_err(runtime_gpu_error)?;
1169        mem.borrow_mut()[out_ptr as usize..out_ptr as usize + byte_len]
1170            .copy_from_slice(&result[..byte_len]);
1171        Ok(())
1172    }
1173}
1174
1175/// Map a GPU backend error into a runtime error.
1176fn runtime_gpu_error(error: GpuError) -> RuntimeError {
1177    RuntimeError {
1178        kind: RuntimeErrorKind::Gpu(error),
1179    }
1180}
1181
1182/// Evaluate a lowered const expression against the globals initialized so
1183/// far. Used for global initializers and data segment offsets.
1184fn eval_const(
1185    expr: &[RegConstInstr],
1186    globals: &[Value],
1187    imported_globals: &[Value],
1188    imported_global_count: u32,
1189    instance_id: u32,
1190) -> Result<Value, RuntimeError> {
1191    let mut stack: Vec<Value> = Vec::new();
1192    for instr in expr {
1193        match *instr {
1194            RegConstInstr::I32Const(value) => stack.push(Value::I32(value)),
1195            RegConstInstr::I64Const(value) => stack.push(Value::I64(value)),
1196            RegConstInstr::F32Const(bits) => stack.push(Value::F32(f32::from_bits(bits))),
1197            RegConstInstr::F64Const(bits) => stack.push(Value::F64(f64::from_bits(bits))),
1198            RegConstInstr::RefNull => stack.push(Value::FuncRef(None)),
1199            RegConstInstr::RefFunc(func) => {
1200                stack.push(Value::FuncRef(Some((instance_id, func.0))));
1201            }
1202            RegConstInstr::GlobalGet(global) => {
1203                let idx = global.0 as usize;
1204                let value = if idx < imported_global_count as usize {
1205                    imported_globals.get(idx).copied().ok_or(RuntimeError {
1206                        kind: RuntimeErrorKind::UnknownGlobal { global: global.0 },
1207                    })?
1208                } else {
1209                    globals
1210                        .get(idx - imported_global_count as usize)
1211                        .copied()
1212                        .ok_or(RuntimeError {
1213                            kind: RuntimeErrorKind::UnknownGlobal { global: global.0 },
1214                        })?
1215                };
1216                stack.push(value);
1217            }
1218            RegConstInstr::I32Add => {
1219                let (lhs, rhs) = pop_i32_pair(&mut stack)?;
1220                stack.push(Value::I32(lhs.wrapping_add(rhs)));
1221            }
1222            RegConstInstr::I32Sub => {
1223                let (lhs, rhs) = pop_i32_pair(&mut stack)?;
1224                stack.push(Value::I32(lhs.wrapping_sub(rhs)));
1225            }
1226            RegConstInstr::I32Mul => {
1227                let (lhs, rhs) = pop_i32_pair(&mut stack)?;
1228                stack.push(Value::I32(lhs.wrapping_mul(rhs)));
1229            }
1230            RegConstInstr::I64Add => {
1231                let (lhs, rhs) = pop_i64_pair(&mut stack)?;
1232                stack.push(Value::I64(lhs.wrapping_add(rhs)));
1233            }
1234            RegConstInstr::I64Sub => {
1235                let (lhs, rhs) = pop_i64_pair(&mut stack)?;
1236                stack.push(Value::I64(lhs.wrapping_sub(rhs)));
1237            }
1238            RegConstInstr::I64Mul => {
1239                let (lhs, rhs) = pop_i64_pair(&mut stack)?;
1240                stack.push(Value::I64(lhs.wrapping_mul(rhs)));
1241            }
1242        }
1243    }
1244
1245    if stack.len() != 1 {
1246        return Err(RuntimeError {
1247            kind: RuntimeErrorKind::InvalidConstExpr,
1248        });
1249    }
1250    Ok(stack[0])
1251}
1252
1253fn pop_i32_pair(stack: &mut Vec<Value>) -> Result<(i32, i32), RuntimeError> {
1254    let (Some(Value::I32(rhs)), Some(Value::I32(lhs))) = (stack.pop(), stack.pop()) else {
1255        return Err(RuntimeError {
1256            kind: RuntimeErrorKind::InvalidConstExpr,
1257        });
1258    };
1259    Ok((lhs, rhs))
1260}
1261
1262fn pop_i64_pair(stack: &mut Vec<Value>) -> Result<(i64, i64), RuntimeError> {
1263    let (Some(Value::I64(rhs)), Some(Value::I64(lhs))) = (stack.pop(), stack.pop()) else {
1264        return Err(RuntimeError {
1265            kind: RuntimeErrorKind::InvalidConstExpr,
1266        });
1267    };
1268    Ok((lhs, rhs))
1269}