submilli-wasm 0.2.0

A WebAssembly interpreter with a wasmtime-compatible API, built for fast compilation and startup
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
//! The compiled, immutable module representation (`ModuleInner`) and its
//! descriptor / segment / constant-expression types. Built by [`super::parse`],
//! shared behind an `Arc` inside [`Module`](super::Module), and consumed by
//! `instance::init` at instantiation time.

use crate::canon::{
    self, AggKind, ArrayLayout, CanonicalTypeId, GroupId, IrGlobalType, IrHeap, IrTableType,
    Layout, ModuleType, Slot, StructLayout,
};
use crate::engine::Engine;
use crate::module::op::CompiledFunc;
use crate::value::{ExternType, FuncType, GlobalType, MemoryType, TableType, TagType, ValType};

/// A function type's materialized `(params, results)`.
pub(crate) type FuncSig = (Vec<ValType>, Vec<ValType>);

/// A module decoded and compiled to internal bytecode. Immutable and shareable.
///
/// The type table (`types`) holds module-relative type defs (the serializable form). After
/// parse/decode, [`intern`](Self::intern) registers the rec groups in the engine — filling
/// `type_ids` (module type index → engine-canonical id) and `group_handles` (for release on
/// drop). Runtime type identity compares the canonical ids; the structure stays for func
/// signatures and (later) field layout.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub(crate) struct ModuleInner {
    pub types: Vec<ModuleType>,
    /// Type index for every function — imported functions first, then defined.
    pub func_types: Vec<u32>,
    pub num_imported_funcs: u32,
    /// Compiled bodies for *defined* functions (index = module func idx
    /// minus [`num_imported_funcs`](Self::num_imported_funcs)) — `Copy` span records into
    /// [`code`](Self::code).
    pub functions: Vec<CompiledFunc>,
    /// Module-wide arenas holding every function's op stream and side tables contiguously.
    pub code: crate::module::code::CodeArenas,
    pub imports: Vec<Import>,
    pub exports: Vec<Export>,
    /// *Defined* memory/table/global descriptors (imported ones live in `imports`).
    pub memories: Vec<MemoryType>,
    pub tables: Vec<TableDef>,
    pub globals: Vec<GlobalDef>,
    /// *Defined* tags (imported ones live in `imports`). Each references a func type index.
    pub tags: Vec<TagDef>,
    pub num_imported_tags: u32,
    pub datas: Vec<DataSegment>,
    pub elems: Vec<ElemSegment>,
    pub start: Option<u32>,
    /// Module type index → engine-canonical id (engine-specific; recomputed by `intern`).
    #[serde(skip)]
    pub type_ids: Vec<CanonicalTypeId>,
    /// Registered canonical group ids, released on drop (engine-specific).
    #[serde(skip)]
    pub group_handles: Vec<GroupId>,
    /// Per-module-type GC byte layout (`None` for function types). Rebuilt by `intern` from the
    /// IR; the interpreter reads it lock-free for `struct.*`/`array.*` field/element access.
    #[serde(skip)]
    pub layouts: Vec<Option<Layout>>,
    /// The owning engine (for releasing `group_handles` on drop). `None` until `intern`.
    #[serde(skip)]
    pub engine: Option<Engine>,
    /// Per-module-type materialized `(params, results)`, built on first use (see
    /// [`func_sig`](Self::func_sig)). Engine-specific; reset by `intern`.
    #[serde(skip)]
    pub func_sigs: Vec<std::sync::OnceLock<FuncSig>>,
    /// Retained DWARF/`name` custom sections + lazily-built line index (#29a). Empty unless debug
    /// retention was on at parse. Module-intrinsic (not engine-specific), so it serializes with the
    /// artifact — backtraces survive `serialize`/`deserialize`.
    pub debug: crate::module::debug::DebugSections,
}

impl ModuleInner {
    /// Registers this module's rec groups in `engine`'s canonical registry, baking the
    /// canonical ids. Called after parse (`Module::new`) and after decode (`deserialize`).
    pub(crate) fn intern(&mut self, engine: &Engine) {
        let (type_ids, group_handles) = engine.intern_types(&self.types);
        self.type_ids = type_ids;
        self.group_handles = group_handles;
        self.func_sigs = std::iter::repeat_with(std::sync::OnceLock::new)
            .take(self.types.len())
            .collect();
        self.layouts = self
            .types
            .iter()
            .map(|t| Layout::from_body(&t.body))
            .collect();
        self.engine = Some(engine.clone());
    }

    /// The engine-canonical id of a module type index — an error, not a panic, when out of range:
    /// op and const-expr immediates reach here, and only validation keeps those in range.
    pub(crate) fn canonical_type_id(&self, type_index: u32) -> crate::Result<CanonicalTypeId> {
        crate::error::ir_get(&self.type_ids, type_index).copied()
    }

    /// The canonical signature id of a module-space function index (imported or defined) — one a
    /// `FuncEntity::Wasm` records (see [`declared_type_id`](Self::declared_type_id) for range).
    #[allow(clippy::indexing_slicing)]
    pub(crate) fn func_type_id(&self, module_func_idx: u32) -> CanonicalTypeId {
        self.declared_type_id(self.func_types[module_func_idx as usize])
    }

    /// [`canonical_type_id`](Self::canonical_type_id) for a module-level type index (function,
    /// import, and tag signatures) on the infallible API paths. Validation guarantees these in
    /// range for a module compiled here; a crafted deserialized artifact may panic here, which the
    /// `Module::deserialize` contract accepts.
    #[allow(clippy::indexing_slicing)]
    fn declared_type_id(&self, type_index: u32) -> CanonicalTypeId {
        self.type_ids[type_index as usize]
    }

    /// The GC byte layout of a struct module type index; an error if the type is not a struct.
    pub(crate) fn struct_layout(&self, type_index: u32) -> crate::Result<&StructLayout> {
        self.layout(type_index)
            .and_then(Layout::as_struct)
            .ok_or_else(|| crate::Error::msg(format!("type {type_index} is not a struct type")))
    }

    /// The slot of field `field` of a struct module type index; an error if the type is not a
    /// struct or has no such field.
    pub(crate) fn struct_field(&self, type_index: u32, field: u32) -> crate::Result<Slot> {
        self.struct_layout(type_index)?
            .field(field as usize)
            .ok_or_else(|| {
                crate::Error::msg(format!("type {type_index} has no struct field {field}"))
            })
    }

    /// The GC byte layout of an array module type index; an error if the type is not an array.
    pub(crate) fn array_layout(&self, type_index: u32) -> crate::Result<ArrayLayout> {
        self.layout(type_index)
            .and_then(Layout::as_array)
            .ok_or_else(|| crate::Error::msg(format!("type {type_index} is not an array type")))
    }

    fn layout(&self, type_index: u32) -> Option<&Layout> {
        self.layouts.get(type_index as usize)?.as_ref()
    }

    /// The owning engine (set by `intern`).
    pub(crate) fn engine(&self) -> &Engine {
        self.engine.as_ref().expect("module not interned")
    }

    /// The per-type-index kind table (for resolving concrete references during conversion).
    pub(crate) fn type_kinds(&self) -> Vec<AggKind> {
        self.types.iter().map(ModuleType::kind).collect()
    }
}

impl Drop for ModuleInner {
    fn drop(&mut self) {
        if let Some(engine) = &self.engine {
            engine.release_types(&self.group_handles);
        }
    }
}

/// One module import, in declaration order.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct Import {
    pub module: String,
    pub name: String,
    pub kind: ImportKind,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum ImportKind {
    Func(u32),
    Table(IrTableType),
    Memory(MemoryType),
    Global(IrGlobalType),
    /// An imported tag, carrying its referenced func-type index (module-relative).
    Tag(u32),
}

/// One module export, in declaration order. Indices are module-space.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct Export {
    pub name: String,
    pub kind: ExportKind,
}

#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum ExportKind {
    Func(u32),
    Table(u32),
    Memory(u32),
    Global(u32),
    Tag(u32),
}

/// A defined table: its type plus an optional constant initializer expression
/// (function-references; `None` means default-null per the reference-types form).
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct TableDef {
    pub ty: IrTableType,
    pub init: Option<ConstExpr>,
}

/// A defined global: its type plus the constant initializer expression.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct GlobalDef {
    pub ty: IrGlobalType,
    pub init: ConstExpr,
}

/// A defined tag: the module-relative index of the func type it references.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct TagDef {
    pub type_index: u32,
}

/// A data segment. Active segments copy into a memory at instantiation; passive
/// ones are consumed by `memory.init`.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct DataSegment {
    pub mode: DataMode,
    pub bytes: Box<[u8]>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum DataMode {
    Passive,
    Active { memory: u32, offset: ConstExpr },
}

/// An element segment. Active segments write into a table at instantiation;
/// passive/declared ones are stored inert (table.init/elem.drop are deferred).
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct ElemSegment {
    pub mode: ElemMode,
    pub items: ElemItems,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum ElemMode {
    Passive,
    Declared,
    Active { table: u32, offset: ConstExpr },
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum ElemItems {
    Funcs(Box<[u32]>),
    Exprs(Box<[ConstExpr]>),
}

/// An owned constant expression: the operator sequence (decoupled from the input bytes),
/// evaluated by a small stack machine at instantiation. Only the constant forms the validator
/// admits — including the GC aggregate constructors, which allocate at init time.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct ConstExpr(pub Box<[ConstOp]>);

/// One operator of a [`ConstExpr`] (postfix; aggregate ops pop their operands from the
/// const-eval stack).
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum ConstOp {
    I32(i32),
    I64(i64),
    F32(u32),
    F64(u64),
    /// `v128.const` literal bits (#37).
    #[cfg(feature = "simd")]
    V128(u128),
    RefNull(IrHeap),
    RefFunc(u32),
    /// `global.get` of an imported or prior-defined immutable global (guaranteed by validation).
    GlobalGet(u32),
    /// Extended-const arithmetic (#40); each pops two operands off the const-eval stack.
    I32Add,
    I32Sub,
    I32Mul,
    I64Add,
    I64Sub,
    I64Mul,
    RefI31,
    StructNew(u32),
    StructNewDefault(u32),
    ArrayNew(u32),
    ArrayNewDefault(u32),
    ArrayNewFixed {
        ty: u32,
        n: u32,
    },
    ArrayNewData {
        ty: u32,
        data: u32,
    },
    ArrayNewElem {
        ty: u32,
        elem: u32,
    },
    AnyConvertExtern,
    ExternConvertAny,
}

impl ModuleInner {
    /// The signature handle of any module-space function index (imported or defined).
    pub(crate) fn func_type(&self, module_func_idx: u32) -> FuncType {
        canon::func_handle(self.engine(), self.func_type_id(module_func_idx))
    }

    /// The materialized `(params, results)` of a function's type, built once and shared:
    /// `Func::call` would otherwise re-materialize it (registry lock + allocations) per call.
    pub(crate) fn func_sig(&self, module_func_idx: u32) -> crate::Result<&FuncSig> {
        let slot = self
            .func_types
            .get(module_func_idx as usize)
            .and_then(|&type_index| self.func_sigs.get(type_index as usize))
            .ok_or_else(|| crate::Error::msg("function type index out of range"))?;
        Ok(slot.get_or_init(|| self.func_type(module_func_idx).signature()))
    }

    /// The external type of an import (for [`Module::imports`](super::Module::imports)).
    pub(crate) fn import_extern_type(&self, kind: &ImportKind) -> ExternType {
        let (engine, ids) = (self.engine(), &self.type_ids);
        match kind {
            ImportKind::Func(t) => {
                ExternType::Func(canon::func_handle(engine, self.declared_type_id(*t)))
            }
            ImportKind::Table(tt) => ExternType::Table(canon::materialize_table(engine, ids, tt)),
            ImportKind::Memory(mt) => ExternType::Memory(mt.clone()),
            ImportKind::Global(gt) => {
                ExternType::Global(canon::materialize_global(engine, ids, gt))
            }
            ImportKind::Tag(t) => ExternType::Tag(TagType::new(canon::func_handle(
                engine,
                self.declared_type_id(*t),
            ))),
        }
    }

    /// The external type of an export (for [`Module::exports`](super::Module::exports)),
    /// resolving the module-space index across imported + defined entities.
    pub(crate) fn export_extern_type(&self, kind: ExportKind) -> ExternType {
        match kind {
            ExportKind::Func(i) => ExternType::Func(self.func_type(i)),
            ExportKind::Table(i) => ExternType::Table(self.nth_table(i)),
            ExportKind::Memory(i) => ExternType::Memory(self.nth_memory(i)),
            ExportKind::Global(i) => ExternType::Global(self.nth_global(i)),
            ExportKind::Tag(i) => ExternType::Tag(self.nth_tag(i)),
        }
    }

    fn nth_memory(&self, idx: u32) -> MemoryType {
        let mut n = 0;
        for imp in &self.imports {
            if let ImportKind::Memory(mt) = &imp.kind {
                if n == idx {
                    return mt.clone();
                }
                n += 1;
            }
        }
        self.memories[(idx - n) as usize].clone()
    }

    fn nth_table(&self, idx: u32) -> TableType {
        let (engine, ids) = (self.engine(), &self.type_ids);
        let mut n = 0;
        for imp in &self.imports {
            if let ImportKind::Table(tt) = &imp.kind {
                if n == idx {
                    return canon::materialize_table(engine, ids, tt);
                }
                n += 1;
            }
        }
        canon::materialize_table(engine, ids, &self.tables[(idx - n) as usize].ty)
    }

    fn nth_global(&self, idx: u32) -> GlobalType {
        let (engine, ids) = (self.engine(), &self.type_ids);
        let mut n = 0;
        for imp in &self.imports {
            if let ImportKind::Global(gt) = &imp.kind {
                if n == idx {
                    return canon::materialize_global(engine, ids, gt);
                }
                n += 1;
            }
        }
        canon::materialize_global(engine, ids, &self.globals[(idx - n) as usize].ty)
    }

    fn nth_tag(&self, idx: u32) -> TagType {
        let mut n = 0;
        for imp in &self.imports {
            if let ImportKind::Tag(t) = &imp.kind {
                if n == idx {
                    return self.tag_type(*t);
                }
                n += 1;
            }
        }
        self.tag_type(self.tags[(idx - n) as usize].type_index)
    }

    /// The [`TagType`] for a tag referencing the given module-relative func-type index.
    pub(crate) fn tag_type(&self, type_index: u32) -> TagType {
        TagType::new(canon::func_handle(
            self.engine(),
            self.declared_type_id(type_index),
        ))
    }
}