Skip to main content

baedeker_core/lower/
mod.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Register-based lowering skeleton.
5//!
6//! Phase 2 starts by turning validated stack-machine functions into an
7//! inspectable register-oriented IR. This module is deliberately small: it
8//! establishes the execution-side vocabulary and lowers straight-line functions
9//! before broader control-flow/runtime semantics are added.
10
11use alloc::{string::String, vec::Vec};
12
13use crate::binary::instr::{DecodedInstr, Instr, decode_instr_sequence_with_offsets};
14use crate::binary::module::Module;
15use crate::error::{ByteOffset, DecodeContext, DecodeErrorKind};
16use crate::types::{
17    BlockType, CodeBody, DataMode, ElemIdx, ElementInit, ElementMode, ExportDesc, FuncIdx,
18    FuncType, GlobalIdx, ImportDesc, LabelIdx, LocalDecl, LocalIdx, MemArg, MemIdx, MemType,
19    Mutability, NumType, RefType, TableIdx, TableType, TypeIdx, ValType,
20};
21use crate::validate;
22use crate::validate::error::{ValidationError, ValidationErrorKind};
23
24/// A virtual register in lowered Baedeker IR.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Reg(pub u32);
28
29/// A typed value currently on the lowering stack.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct RegValue {
33    pub reg: Reg,
34    pub ty: ValType,
35}
36
37/// A validated module lowered into register IR.
38#[derive(Debug, Clone, PartialEq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct RegModule {
41    pub funcs: Vec<RegFunc>,
42    pub exports: Vec<RegExport>,
43    /// Number of imported functions: `FuncIdx` values below this are not
44    /// lowered and cannot be called by the interpreter yet.
45    pub imported_func_count: u32,
46    /// Function import declarations in index order (for host-function
47    /// registration and lazy import resolution).
48    pub imported_funcs: Vec<RegImport>,
49    /// Memory import declarations in index order.
50    pub imported_memories: Vec<RegMemoryImport>,
51    /// Global import declarations in index order.
52    pub imported_globals: Vec<RegGlobalImport>,
53    /// Table import declarations in index order.
54    pub imported_tables: Vec<RegTableImport>,
55    /// The start function, if the module declares one.
56    pub start: Option<FuncIdx>,
57    /// Defined memories (instantiated as zeroed linear memory).
58    pub memories: Vec<MemType>,
59    /// Defined globals, initialized in declaration order at instantiation.
60    pub globals: Vec<RegGlobal>,
61    /// Defined tables (instantiated as null-filled reference arrays).
62    pub tables: Vec<TableType>,
63    /// Element segments in index order; mode decides instantiation behavior.
64    pub elements: Vec<RegElement>,
65    /// All function types in the module's type section, for structural
66    /// `call_indirect` type checks.
67    pub types: Vec<FuncType>,
68    /// All data segments in index order; mode decides instantiation behavior.
69    pub data: Vec<RegDataSegment>,
70    /// Number of imported memories (runtime access is not yet supported).
71    pub imported_memory_count: u32,
72    /// Number of imported globals (runtime access is not yet supported).
73    pub imported_global_count: u32,
74    /// Number of imported tables (runtime access is not yet supported).
75    pub imported_table_count: u32,
76}
77
78/// An element segment in lowered register IR.
79#[derive(Debug, Clone, PartialEq, Eq)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct RegElement {
82    pub mode: RegElementMode,
83    /// Element values (funcref indices or nulls), in order.
84    pub values: Vec<RegElemValue>,
85}
86
87/// Instantiation behavior of an element segment.
88#[derive(Debug, Clone, PartialEq, Eq)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90pub enum RegElementMode {
91    /// Written into the table at instantiation.
92    Active {
93        table: TableIdx,
94        offset: Vec<RegConstInstr>,
95    },
96    /// Retained for `table.init` until dropped.
97    Passive,
98    /// Declarative: only declares functions for `ref.func`; never usable
99    /// at runtime.
100    Dropped,
101}
102
103/// One element value in a segment.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum RegElemValue {
107    FuncRef(FuncIdx),
108    /// A `global.get` of a funcref global, resolved at instantiation.
109    GlobalGet(GlobalIdx),
110    Null,
111}
112
113/// A defined global in lowered register IR.
114#[derive(Debug, Clone, PartialEq)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116pub struct RegGlobal {
117    pub ty: ValType,
118    pub mutable: bool,
119    /// Const initializer, evaluated at instantiation.
120    pub init: Vec<RegConstInstr>,
121}
122
123/// A data segment in lowered register IR.
124#[derive(Debug, Clone, PartialEq, Eq)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub struct RegDataSegment {
127    pub mode: RegDataMode,
128    pub bytes: Vec<u8>,
129}
130
131/// Instantiation behavior of a data segment.
132#[derive(Debug, Clone, PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134pub enum RegDataMode {
135    /// Written into memory at instantiation, then dropped.
136    Active {
137        memory: MemIdx,
138        offset: Vec<RegConstInstr>,
139    },
140    /// Retained for `memory.init` until dropped.
141    Passive,
142}
143
144/// An instruction in a lowered constant expression (global initializers,
145/// data segment offsets). Supports the const instrs plus the
146/// extended-const integer arithmetic the validator accepts.
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149pub enum RegConstInstr {
150    I32Const(i32),
151    I64Const(i64),
152    F32Const(u32),
153    F64Const(u64),
154    GlobalGet(GlobalIdx),
155    RefNull,
156    RefFunc(FuncIdx),
157    I32Add,
158    I32Sub,
159    I32Mul,
160    I64Add,
161    I64Sub,
162    I64Mul,
163}
164
165/// A function import declaration in lowered register IR.
166#[derive(Debug, Clone, PartialEq, Eq)]
167#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168pub struct RegImport {
169    pub module: String,
170    pub name: String,
171    pub ty: FuncType,
172}
173
174/// A memory import declaration in lowered register IR.
175#[derive(Debug, Clone, PartialEq, Eq)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177pub struct RegMemoryImport {
178    pub module: String,
179    pub name: String,
180    pub ty: MemType,
181}
182
183/// A global import declaration in lowered register IR.
184#[derive(Debug, Clone, PartialEq, Eq)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct RegGlobalImport {
187    pub module: String,
188    pub name: String,
189    pub ty: crate::types::GlobalType,
190}
191
192/// A table import declaration in lowered register IR.
193#[derive(Debug, Clone, PartialEq, Eq)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
195pub struct RegTableImport {
196    pub module: String,
197    pub name: String,
198    pub ty: TableType,
199}
200
201/// A function export in lowered register IR.
202#[derive(Debug, Clone, PartialEq, Eq)]
203#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
204pub struct RegExport {
205    pub name: String,
206    pub desc: RegExportDesc,
207}
208
209/// What kind of item an export refers to.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
212pub enum RegExportDesc {
213    Func(FuncIdx),
214    Table(TableIdx),
215    Mem(MemIdx),
216    Global(GlobalIdx),
217}
218
219/// A defined function lowered into register IR.
220#[derive(Debug, Clone, PartialEq)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
222pub struct RegFunc {
223    pub idx: FuncIdx,
224    pub type_idx: TypeIdx,
225    pub params: Vec<ValType>,
226    pub results: Vec<ValType>,
227    /// All locals in function-index order: parameters first, then code-section locals.
228    pub locals: Vec<ValType>,
229    /// Type of each virtual register allocated while lowering this function.
230    pub reg_types: Vec<ValType>,
231    /// Basic blocks in execution order.
232    pub blocks: Vec<RegBlock>,
233}
234
235/// A basic block in the lowered register IR.
236#[derive(Debug, Clone, PartialEq)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct RegBlock {
239    pub label: LabelIdx,
240    pub instrs: Vec<RegInstr>,
241    pub term: RegTerm,
242}
243
244/// A block terminator — how execution leaves this block.
245#[derive(Debug, Clone, PartialEq)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247pub enum RegTerm {
248    /// Fall through to the next block in sequence.
249    Fallthrough,
250    /// Branch to a target block, passing values.
251    Br { target_block: u32, values: Vec<Reg> },
252    /// Conditional branch: if cond is non-zero, branch; otherwise fall through.
253    BrIf {
254        cond: Reg,
255        target_block: u32,
256        values: Vec<Reg>,
257    },
258    /// `br_on_null`: branch when the reference value is null.
259    BrIfNull {
260        value: Reg,
261        target_block: u32,
262        values: Vec<Reg>,
263    },
264    /// `br_on_non_null`: branch when the reference value is non-null,
265    /// forwarding it (as the last branch value) to the target.
266    BrIfNonNull {
267        value: Reg,
268        target_block: u32,
269        values: Vec<Reg>,
270    },
271    /// Two-way fork: if cond is non-zero, go to then_block; else to else_block.
272    IfFork {
273        cond: Reg,
274        then_block: u32,
275        else_block: u32,
276    },
277    /// Multi-target dispatch (`br_table`): branch to `targets[i]` when the
278    /// index value equals i, or to `default` when out of range.
279    BrTable {
280        index: Reg,
281        targets: Vec<u32>,
282        default: u32,
283        values: Vec<Reg>,
284    },
285    /// Return from the function with values.
286    Return { values: Vec<Reg> },
287    /// Unconditional trap (`unreachable`).
288    Trap,
289}
290
291impl RegBlock {
292    pub fn new(label: LabelIdx, instrs: Vec<RegInstr>, term: RegTerm) -> Self {
293        Self {
294            label,
295            instrs,
296            term,
297        }
298    }
299}
300
301/// A lowered instruction with the source byte offset it came from.
302#[derive(Debug, Clone, PartialEq)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304pub struct RegInstr {
305    pub offset: ByteOffset,
306    pub op: RegOp,
307}
308
309/// Register-oriented operations.
310#[derive(Debug, Clone, PartialEq)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312pub enum RegOp {
313    LocalGet {
314        dst: Reg,
315        local: LocalIdx,
316    },
317    LocalSet {
318        local: LocalIdx,
319        value: Reg,
320    },
321    LocalTee {
322        local: LocalIdx,
323        value: Reg,
324    },
325    Drop {
326        value: Reg,
327    },
328    I32Const {
329        dst: Reg,
330        value: i32,
331    },
332    I64Const {
333        dst: Reg,
334        value: i64,
335    },
336    F32Const {
337        dst: Reg,
338        value: f32,
339    },
340    F64Const {
341        dst: Reg,
342        value: f64,
343    },
344    Unary {
345        op: UnaryOp,
346        dst: Reg,
347        value: Reg,
348    },
349    Binary {
350        op: BinaryOp,
351        dst: Reg,
352        lhs: Reg,
353        rhs: Reg,
354    },
355    /// Copy a register — used to deliver branch-carried values into the
356    /// registers a continuation block expects (phi lowering via copies in
357    /// predecessor blocks).
358    Copy {
359        dst: Reg,
360        src: Reg,
361    },
362    /// Direct call (`call`): invoke `func` with `args`, writing each result
363    /// register.
364    Call {
365        func: FuncIdx,
366        args: Vec<Reg>,
367        results: Vec<Reg>,
368    },
369    /// Conditional selection (`select`): dst = cond != 0 ? v1 : v2.
370    Select {
371        dst: Reg,
372        v1: Reg,
373        v2: Reg,
374        cond: Reg,
375    },
376    /// Linear-memory load: dst = mem[effective(addr)..+width] per `op`.
377    Load {
378        op: LoadOp,
379        dst: Reg,
380        addr: Reg,
381        memarg: MemArg,
382    },
383    /// v128 constant.
384    V128Const {
385        dst: Reg,
386        value: [u8; 16],
387    },
388    /// Broadcast a scalar into every lane.
389    V128Splat {
390        dst: Reg,
391        shape: LaneShape,
392        src: Reg,
393    },
394    /// Extract one lane as a scalar.
395    V128ExtractLane {
396        dst: Reg,
397        shape: LaneShape,
398        src: Reg,
399        lane: u8,
400    },
401    /// Replace one lane of a vector with a scalar.
402    V128ReplaceLane {
403        dst: Reg,
404        shape: LaneShape,
405        vec: Reg,
406        scalar: Reg,
407        lane: u8,
408    },
409    /// Lane-wise binary operation (add/sub/mul/div and bitwise and/or/xor).
410    V128Binary {
411        shape: LaneShape,
412        kind: V128BinaryKind,
413        dst: Reg,
414        lhs: Reg,
415        rhs: Reg,
416    },
417    /// Bitwise not over all 128 bits.
418    V128Not {
419        dst: Reg,
420        src: Reg,
421    },
422    /// Linear-memory store: mem[effective(addr)..+width] = value per `op`.
423    Store {
424        op: StoreOp,
425        addr: Reg,
426        value: Reg,
427        memarg: MemArg,
428    },
429    /// Read a global.
430    GlobalGet {
431        dst: Reg,
432        global: GlobalIdx,
433    },
434    /// Write a global.
435    GlobalSet {
436        global: GlobalIdx,
437        value: Reg,
438    },
439    /// Current memory size in pages (`memory.size`).
440    MemorySize {
441        dst: Reg,
442        memory: MemIdx,
443    },
444    /// Grow memory by `delta` pages (`memory.grow`): dst = previous size,
445    /// or -1 on failure.
446    MemoryGrow {
447        dst: Reg,
448        memory: MemIdx,
449        delta: Reg,
450    },
451    /// `memory.init`: mem[dst..] = data_segment[src..] over count bytes.
452    MemoryInit {
453        memory: MemIdx,
454        data: crate::types::DataIdx,
455        dst: Reg,
456        src: Reg,
457        count: Reg,
458    },
459    /// `data.drop`: drop the data segment's runtime storage.
460    DataDrop {
461        data: crate::types::DataIdx,
462    },
463    /// `memory.copy`: dst_mem[dst..] = src_mem[src..] over count bytes.
464    MemoryCopy {
465        dst_memory: MemIdx,
466        src_memory: MemIdx,
467        dst: Reg,
468        src: Reg,
469        count: Reg,
470    },
471    /// `memory.fill`: mem[dst..dst+count] = value (low byte).
472    MemoryFill {
473        memory: MemIdx,
474        dst: Reg,
475        value: Reg,
476        count: Reg,
477    },
478    /// Indirect call through a table (`call_indirect`).
479    CallIndirect {
480        type_idx: TypeIdx,
481        table: TableIdx,
482        index: Reg,
483        args: Vec<Reg>,
484        results: Vec<Reg>,
485    },
486    /// Direct call through a function reference (`call_ref`).
487    CallRef {
488        type_idx: TypeIdx,
489        func: Reg,
490        args: Vec<Reg>,
491        results: Vec<Reg>,
492    },
493    /// `table.get`: dst = `table[index]`.
494    TableGet {
495        dst: Reg,
496        table: TableIdx,
497        index: Reg,
498    },
499    /// `table.set`: `table[index]` = value.
500    TableSet {
501        table: TableIdx,
502        index: Reg,
503        value: Reg,
504    },
505    /// `table.size`.
506    TableSize {
507        dst: Reg,
508        table: TableIdx,
509    },
510    /// `table.grow`: grow by delta, filling with `value`; dst = previous
511    /// size or -1 on failure.
512    TableGrow {
513        dst: Reg,
514        table: TableIdx,
515        value: Reg,
516        delta: Reg,
517    },
518    /// `table.fill`: table[dst..dst+count] = value.
519    TableFill {
520        table: TableIdx,
521        dst: Reg,
522        value: Reg,
523        count: Reg,
524    },
525    /// `table.copy`: dst_table[dst..] = src_table[src..] over count.
526    TableCopy {
527        dst_table: TableIdx,
528        src_table: TableIdx,
529        dst: Reg,
530        src: Reg,
531        count: Reg,
532    },
533    /// `table.init`: table[dst..] = elem[src..] over count.
534    TableInit {
535        table: TableIdx,
536        elem: ElemIdx,
537        dst: Reg,
538        src: Reg,
539        count: Reg,
540    },
541    /// `elem.drop`: drop the element segment's runtime storage.
542    ElemDrop {
543        elem: ElemIdx,
544    },
545    /// `ref.null`: produce a null reference of the given reference type.
546    RefNull {
547        dst: Reg,
548        ref_type: RefType,
549    },
550    /// `ref.func`: produce a function reference.
551    RefFunc {
552        dst: Reg,
553        func: FuncIdx,
554    },
555    /// `ref.is_null`: dst = 1 when the reference is null, else 0.
556    RefIsNull {
557        dst: Reg,
558        value: Reg,
559    },
560    /// `ref.as_non_null`: trap on null, otherwise copy the reference.
561    RefAsNonNull {
562        dst: Reg,
563        value: Reg,
564    },
565}
566
567/// SIMD lane shapes.
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
570pub enum LaneShape {
571    I8x16,
572    I16x8,
573    I32x4,
574    I64x2,
575    F32x4,
576    F64x2,
577}
578
579impl LaneShape {
580    /// Bytes per lane.
581    pub fn lane_width(self) -> usize {
582        match self {
583            LaneShape::I8x16 => 1,
584            LaneShape::I16x8 => 2,
585            LaneShape::I32x4 | LaneShape::F32x4 => 4,
586            LaneShape::I64x2 | LaneShape::F64x2 => 8,
587        }
588    }
589
590    /// Scalar type of one lane.
591    pub fn scalar_type(self) -> ValType {
592        match self {
593            LaneShape::I8x16 | LaneShape::I16x8 | LaneShape::I32x4 => ValType::Num(NumType::I32),
594            LaneShape::I64x2 => ValType::Num(NumType::I64),
595            LaneShape::F32x4 => ValType::Num(NumType::F32),
596            LaneShape::F64x2 => ValType::Num(NumType::F64),
597        }
598    }
599}
600
601/// Lane-wise binary operation kinds. Bitwise kinds ignore lane shape.
602#[derive(Debug, Clone, Copy, PartialEq, Eq)]
603#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
604pub enum V128BinaryKind {
605    Add,
606    Sub,
607    Mul,
608    Div,
609    And,
610    Or,
611    Xor,
612}
613
614/// Linear-memory load operations.
615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
616#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
617pub enum LoadOp {
618    I32,
619    I64,
620    F32,
621    F64,
622    I32Load8S,
623    I32Load8U,
624    I32Load16S,
625    I32Load16U,
626    I64Load8S,
627    I64Load8U,
628    I64Load16S,
629    I64Load16U,
630    I64Load32S,
631    I64Load32U,
632    V128,
633}
634
635impl LoadOp {
636    /// Bytes read from memory.
637    pub fn byte_width(self) -> usize {
638        match self {
639            LoadOp::V128 => 16,
640            LoadOp::I32 | LoadOp::F32 | LoadOp::I64Load32S | LoadOp::I64Load32U => 4,
641            LoadOp::I64 | LoadOp::F64 => 8,
642            LoadOp::I32Load8S | LoadOp::I32Load8U | LoadOp::I64Load8S | LoadOp::I64Load8U => 1,
643            LoadOp::I32Load16S | LoadOp::I32Load16U | LoadOp::I64Load16S | LoadOp::I64Load16U => 2,
644        }
645    }
646
647    /// Value type produced by the load.
648    pub fn result_type(self) -> ValType {
649        match self {
650            LoadOp::V128 => ValType::Vec(crate::types::VecType::V128),
651            LoadOp::I32
652            | LoadOp::I32Load8S
653            | LoadOp::I32Load8U
654            | LoadOp::I32Load16S
655            | LoadOp::I32Load16U => ValType::Num(NumType::I32),
656            LoadOp::I64
657            | LoadOp::I64Load8S
658            | LoadOp::I64Load8U
659            | LoadOp::I64Load16S
660            | LoadOp::I64Load16U
661            | LoadOp::I64Load32S
662            | LoadOp::I64Load32U => ValType::Num(NumType::I64),
663            LoadOp::F32 => ValType::Num(NumType::F32),
664            LoadOp::F64 => ValType::Num(NumType::F64),
665        }
666    }
667
668    /// Whether the loaded value is sign-extended to the result type.
669    pub fn sign_extend(self) -> bool {
670        matches!(
671            self,
672            LoadOp::I32Load8S
673                | LoadOp::I32Load16S
674                | LoadOp::I64Load8S
675                | LoadOp::I64Load16S
676                | LoadOp::I64Load32S
677        )
678    }
679}
680
681/// Linear-memory store operations.
682#[derive(Debug, Clone, Copy, PartialEq, Eq)]
683#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
684pub enum StoreOp {
685    I32,
686    I64,
687    F32,
688    F64,
689    I32Store8,
690    I32Store16,
691    I64Store8,
692    I64Store16,
693    I64Store32,
694    V128,
695}
696
697impl StoreOp {
698    /// Bytes written to memory.
699    pub fn byte_width(self) -> usize {
700        match self {
701            StoreOp::V128 => 16,
702            StoreOp::I32 | StoreOp::F32 | StoreOp::I64Store32 => 4,
703            StoreOp::I64 | StoreOp::F64 => 8,
704            StoreOp::I32Store8 | StoreOp::I64Store8 => 1,
705            StoreOp::I32Store16 | StoreOp::I64Store16 => 2,
706        }
707    }
708
709    /// Value type consumed by the store.
710    pub fn value_type(self) -> ValType {
711        match self {
712            StoreOp::V128 => ValType::Vec(crate::types::VecType::V128),
713            StoreOp::I32 | StoreOp::I32Store8 | StoreOp::I32Store16 => ValType::Num(NumType::I32),
714            StoreOp::I64 | StoreOp::I64Store8 | StoreOp::I64Store16 | StoreOp::I64Store32 => {
715                ValType::Num(NumType::I64)
716            }
717            StoreOp::F32 => ValType::Num(NumType::F32),
718            StoreOp::F64 => ValType::Num(NumType::F64),
719        }
720    }
721}
722
723/// Unary numeric operation lowered into register IR.
724#[derive(Debug, Clone, Copy, PartialEq, Eq)]
725#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
726pub enum UnaryOp {
727    I32Clz,
728    I32Ctz,
729    I32Popcnt,
730    I32Eqz,
731    I32WrapI64,
732    I32Extend8S,
733    I32Extend16S,
734    I32TruncF32S,
735    I32TruncF32U,
736    I32TruncF64S,
737    I32TruncF64U,
738    F32ConvertI32S,
739    F32ConvertI32U,
740    F64ConvertI32S,
741    F64ConvertI32U,
742    F32Neg,
743    F32Abs,
744    F32Sqrt,
745    F32Ceil,
746    F32Floor,
747    F32Trunc,
748    F32Nearest,
749    I64Clz,
750    I64Ctz,
751    I64Popcnt,
752    I64Eqz,
753    I64ExtendI32S,
754    I64ExtendI32U,
755    I64Extend8S,
756    I64Extend16S,
757    I64Extend32S,
758    I64TruncF32S,
759    I64TruncF32U,
760    I64TruncF64S,
761    I64TruncF64U,
762    F32ConvertI64S,
763    F32ConvertI64U,
764    F64ConvertI64S,
765    F64ConvertI64U,
766    F64Neg,
767    F64Abs,
768    F64Sqrt,
769    F64Ceil,
770    F64Floor,
771    F64Trunc,
772    F64Nearest,
773    F32DemoteF64,
774    F64PromoteF32,
775    I32ReinterpretF32,
776    F32ReinterpretI32,
777    I64ReinterpretF64,
778    F64ReinterpretI64,
779    I32TruncSatF32S,
780    I32TruncSatF32U,
781    I32TruncSatF64S,
782    I32TruncSatF64U,
783    I64TruncSatF32S,
784    I64TruncSatF32U,
785    I64TruncSatF64S,
786    I64TruncSatF64U,
787}
788
789/// Binary numeric operation lowered into register IR.
790#[derive(Debug, Clone, Copy, PartialEq, Eq)]
791#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
792pub enum BinaryOp {
793    I32Add,
794    I32Sub,
795    I32Mul,
796    I32DivS,
797    I32DivU,
798    I32RemS,
799    I32RemU,
800    I32And,
801    I32Or,
802    I32Xor,
803    I32Shl,
804    I32ShrS,
805    I32ShrU,
806    I32Rotl,
807    I32Rotr,
808    I32Eq,
809    I32Ne,
810    I32LtS,
811    I32LtU,
812    I32GtS,
813    I32GtU,
814    I32LeS,
815    I32LeU,
816    I32GeS,
817    I32GeU,
818    I64Add,
819    I64Sub,
820    I64Mul,
821    I64DivS,
822    I64DivU,
823    I64RemS,
824    I64RemU,
825    I64And,
826    I64Or,
827    I64Xor,
828    I64Shl,
829    I64ShrS,
830    I64ShrU,
831    I64Rotl,
832    I64Rotr,
833    I64Eq,
834    I64Ne,
835    I64LtS,
836    I64LtU,
837    I64GtS,
838    I64GtU,
839    I64LeS,
840    I64LeU,
841    I64GeS,
842    I64GeU,
843    F32Add,
844    F32Sub,
845    F32Mul,
846    F32Div,
847    F32Copysign,
848    F64Copysign,
849    F32Min,
850    F32Max,
851    F64Add,
852    F64Sub,
853    F64Mul,
854    F64Div,
855    F64Min,
856    F64Max,
857    F32Eq,
858    F32Ne,
859    F32Lt,
860    F32Gt,
861    F32Le,
862    F32Ge,
863    F64Eq,
864    F64Ne,
865    F64Lt,
866    F64Gt,
867    F64Le,
868    F64Ge,
869}
870
871impl UnaryOp {
872    fn name(self) -> &'static str {
873        match self {
874            UnaryOp::I32Clz => "i32.clz",
875            UnaryOp::I32Ctz => "i32.ctz",
876            UnaryOp::I32Popcnt => "i32.popcnt",
877            UnaryOp::I32Eqz => "i32.eqz",
878            UnaryOp::I32WrapI64 => "i32.wrap_i64",
879            UnaryOp::I32Extend8S => "i32.extend8_s",
880            UnaryOp::I32Extend16S => "i32.extend16_s",
881            UnaryOp::I32TruncF32S => "i32.trunc_f32_s",
882            UnaryOp::I32TruncF32U => "i32.trunc_f32_u",
883            UnaryOp::I32TruncF64S => "i32.trunc_f64_s",
884            UnaryOp::I32TruncF64U => "i32.trunc_f64_u",
885            UnaryOp::F32ConvertI32S => "f32.convert_i32_s",
886            UnaryOp::F32ConvertI32U => "f32.convert_i32_u",
887            UnaryOp::F64ConvertI32S => "f64.convert_i32_s",
888            UnaryOp::F64ConvertI32U => "f64.convert_i32_u",
889            UnaryOp::F32Neg => "f32.neg",
890            UnaryOp::F32Abs => "f32.abs",
891            UnaryOp::F32Sqrt => "f32.sqrt",
892            UnaryOp::F32Ceil => "f32.ceil",
893            UnaryOp::F32Floor => "f32.floor",
894            UnaryOp::F32Trunc => "f32.trunc",
895            UnaryOp::F32Nearest => "f32.nearest",
896            UnaryOp::I64Clz => "i64.clz",
897            UnaryOp::I64Ctz => "i64.ctz",
898            UnaryOp::I64Popcnt => "i64.popcnt",
899            UnaryOp::I64Eqz => "i64.eqz",
900            UnaryOp::I64ExtendI32S => "i64.extend_i32_s",
901            UnaryOp::I64ExtendI32U => "i64.extend_i32_u",
902            UnaryOp::I64Extend8S => "i64.extend8_s",
903            UnaryOp::I64Extend16S => "i64.extend16_s",
904            UnaryOp::I64Extend32S => "i64.extend32_s",
905            UnaryOp::I64TruncF32S => "i64.trunc_f32_s",
906            UnaryOp::I64TruncF32U => "i64.trunc_f32_u",
907            UnaryOp::I64TruncF64S => "i64.trunc_f64_s",
908            UnaryOp::I64TruncF64U => "i64.trunc_f64_u",
909            UnaryOp::F32ConvertI64S => "f32.convert_i64_s",
910            UnaryOp::F32ConvertI64U => "f32.convert_i64_u",
911            UnaryOp::F64ConvertI64S => "f64.convert_i64_s",
912            UnaryOp::F64ConvertI64U => "f64.convert_i64_u",
913            UnaryOp::F64Neg => "f64.neg",
914            UnaryOp::F64Abs => "f64.abs",
915            UnaryOp::F64Sqrt => "f64.sqrt",
916            UnaryOp::F64Ceil => "f64.ceil",
917            UnaryOp::F64Floor => "f64.floor",
918            UnaryOp::F64Trunc => "f64.trunc",
919            UnaryOp::F64Nearest => "f64.nearest",
920            UnaryOp::F32DemoteF64 => "f32.demote_f64",
921            UnaryOp::F64PromoteF32 => "f64.promote_f32",
922            UnaryOp::I32ReinterpretF32 => "i32.reinterpret_f32",
923            UnaryOp::F32ReinterpretI32 => "f32.reinterpret_i32",
924            UnaryOp::I64ReinterpretF64 => "i64.reinterpret_f64",
925            UnaryOp::F64ReinterpretI64 => "f64.reinterpret_i64",
926            UnaryOp::I32TruncSatF32S => "i32.trunc_sat_f32_s",
927            UnaryOp::I32TruncSatF32U => "i32.trunc_sat_f32_u",
928            UnaryOp::I32TruncSatF64S => "i32.trunc_sat_f64_s",
929            UnaryOp::I32TruncSatF64U => "i32.trunc_sat_f64_u",
930            UnaryOp::I64TruncSatF32S => "i64.trunc_sat_f32_s",
931            UnaryOp::I64TruncSatF32U => "i64.trunc_sat_f32_u",
932            UnaryOp::I64TruncSatF64S => "i64.trunc_sat_f64_s",
933            UnaryOp::I64TruncSatF64U => "i64.trunc_sat_f64_u",
934        }
935    }
936
937    fn input_type(self) -> ValType {
938        match self {
939            UnaryOp::I32Clz
940            | UnaryOp::I32Ctz
941            | UnaryOp::I32Popcnt
942            | UnaryOp::I32Eqz
943            | UnaryOp::I32Extend8S
944            | UnaryOp::I32Extend16S
945            | UnaryOp::I64ExtendI32S
946            | UnaryOp::I64ExtendI32U
947            | UnaryOp::F32ConvertI32S
948            | UnaryOp::F32ConvertI32U
949            | UnaryOp::F64ConvertI32S
950            | UnaryOp::F64ConvertI32U
951            | UnaryOp::F32ReinterpretI32 => ValType::Num(NumType::I32),
952            UnaryOp::I64Clz
953            | UnaryOp::I64Ctz
954            | UnaryOp::I64Popcnt
955            | UnaryOp::I64Eqz
956            | UnaryOp::I32WrapI64
957            | UnaryOp::I64Extend8S
958            | UnaryOp::I64Extend16S
959            | UnaryOp::I64Extend32S
960            | UnaryOp::F32ConvertI64S
961            | UnaryOp::F32ConvertI64U
962            | UnaryOp::F64ConvertI64S
963            | UnaryOp::F64ConvertI64U
964            | UnaryOp::F64ReinterpretI64 => ValType::Num(NumType::I64),
965            UnaryOp::F32Neg
966            | UnaryOp::F32Abs
967            | UnaryOp::F32Sqrt
968            | UnaryOp::F32Ceil
969            | UnaryOp::F32Floor
970            | UnaryOp::F32Trunc
971            | UnaryOp::F32Nearest
972            | UnaryOp::I32TruncF32S
973            | UnaryOp::I32TruncF32U
974            | UnaryOp::I64TruncF32S
975            | UnaryOp::I64TruncF32U
976            | UnaryOp::F64PromoteF32
977            | UnaryOp::I32ReinterpretF32
978            | UnaryOp::I32TruncSatF32S
979            | UnaryOp::I32TruncSatF32U
980            | UnaryOp::I64TruncSatF32S
981            | UnaryOp::I64TruncSatF32U => ValType::Num(NumType::F32),
982            UnaryOp::F64Neg
983            | UnaryOp::F64Abs
984            | UnaryOp::F64Sqrt
985            | UnaryOp::F64Ceil
986            | UnaryOp::F64Floor
987            | UnaryOp::F64Trunc
988            | UnaryOp::F64Nearest
989            | UnaryOp::I32TruncF64S
990            | UnaryOp::I32TruncF64U
991            | UnaryOp::I64TruncF64S
992            | UnaryOp::I64TruncF64U
993            | UnaryOp::F32DemoteF64
994            | UnaryOp::I64ReinterpretF64
995            | UnaryOp::I32TruncSatF64S
996            | UnaryOp::I32TruncSatF64U
997            | UnaryOp::I64TruncSatF64S
998            | UnaryOp::I64TruncSatF64U => ValType::Num(NumType::F64),
999        }
1000    }
1001
1002    fn result_type(self) -> ValType {
1003        match self {
1004            UnaryOp::I32Clz
1005            | UnaryOp::I32Ctz
1006            | UnaryOp::I32Popcnt
1007            | UnaryOp::I32Eqz
1008            | UnaryOp::I32WrapI64
1009            | UnaryOp::I32Extend8S
1010            | UnaryOp::I32Extend16S
1011            | UnaryOp::I32TruncF32S
1012            | UnaryOp::I32TruncF32U
1013            | UnaryOp::I32TruncF64S
1014            | UnaryOp::I32TruncF64U
1015            | UnaryOp::I32TruncSatF32S
1016            | UnaryOp::I32TruncSatF32U
1017            | UnaryOp::I32TruncSatF64S
1018            | UnaryOp::I32TruncSatF64U
1019            | UnaryOp::I32ReinterpretF32 => ValType::Num(NumType::I32),
1020            UnaryOp::I64Clz
1021            | UnaryOp::I64Ctz
1022            | UnaryOp::I64Popcnt
1023            | UnaryOp::I64ExtendI32S
1024            | UnaryOp::I64ExtendI32U
1025            | UnaryOp::I64Extend8S
1026            | UnaryOp::I64Extend16S
1027            | UnaryOp::I64Extend32S
1028            | UnaryOp::I64TruncF32S
1029            | UnaryOp::I64TruncF32U
1030            | UnaryOp::I64TruncF64S
1031            | UnaryOp::I64TruncF64U
1032            | UnaryOp::I64TruncSatF32S
1033            | UnaryOp::I64TruncSatF32U
1034            | UnaryOp::I64TruncSatF64S
1035            | UnaryOp::I64TruncSatF64U
1036            | UnaryOp::I64ReinterpretF64 => ValType::Num(NumType::I64),
1037            UnaryOp::F32Neg
1038            | UnaryOp::F32Abs
1039            | UnaryOp::F32Sqrt
1040            | UnaryOp::F32Ceil
1041            | UnaryOp::F32Floor
1042            | UnaryOp::F32Trunc
1043            | UnaryOp::F32Nearest
1044            | UnaryOp::F32ConvertI32S
1045            | UnaryOp::F32ConvertI32U
1046            | UnaryOp::F32ConvertI64S
1047            | UnaryOp::F32ConvertI64U
1048            | UnaryOp::F32DemoteF64
1049            | UnaryOp::F32ReinterpretI32 => ValType::Num(NumType::F32),
1050            UnaryOp::F64Neg
1051            | UnaryOp::F64Abs
1052            | UnaryOp::F64Sqrt
1053            | UnaryOp::F64Ceil
1054            | UnaryOp::F64Floor
1055            | UnaryOp::F64Trunc
1056            | UnaryOp::F64Nearest
1057            | UnaryOp::F64ConvertI32S
1058            | UnaryOp::F64ConvertI32U
1059            | UnaryOp::F64ConvertI64S
1060            | UnaryOp::F64ConvertI64U
1061            | UnaryOp::F64PromoteF32
1062            | UnaryOp::F64ReinterpretI64 => ValType::Num(NumType::F64),
1063            UnaryOp::I64Eqz => ValType::Num(NumType::I32),
1064        }
1065    }
1066}
1067
1068impl BinaryOp {
1069    fn name(self) -> &'static str {
1070        match self {
1071            BinaryOp::I32Add => "i32.add",
1072            BinaryOp::I32Sub => "i32.sub",
1073            BinaryOp::I32Mul => "i32.mul",
1074            BinaryOp::I32DivS => "i32.div_s",
1075            BinaryOp::I32DivU => "i32.div_u",
1076            BinaryOp::I32RemS => "i32.rem_s",
1077            BinaryOp::I32RemU => "i32.rem_u",
1078            BinaryOp::I32And => "i32.and",
1079            BinaryOp::I32Or => "i32.or",
1080            BinaryOp::I32Xor => "i32.xor",
1081            BinaryOp::I32Shl => "i32.shl",
1082            BinaryOp::I32ShrS => "i32.shr_s",
1083            BinaryOp::I32ShrU => "i32.shr_u",
1084            BinaryOp::I32Rotl => "i32.rotl",
1085            BinaryOp::I32Rotr => "i32.rotr",
1086            BinaryOp::I32Eq => "i32.eq",
1087            BinaryOp::I32Ne => "i32.ne",
1088            BinaryOp::I32LtS => "i32.lt_s",
1089            BinaryOp::I32LtU => "i32.lt_u",
1090            BinaryOp::I32GtS => "i32.gt_s",
1091            BinaryOp::I32GtU => "i32.gt_u",
1092            BinaryOp::I32LeS => "i32.le_s",
1093            BinaryOp::I32LeU => "i32.le_u",
1094            BinaryOp::I32GeS => "i32.ge_s",
1095            BinaryOp::I32GeU => "i32.ge_u",
1096            BinaryOp::I64Add => "i64.add",
1097            BinaryOp::I64Sub => "i64.sub",
1098            BinaryOp::I64Mul => "i64.mul",
1099            BinaryOp::I64DivS => "i64.div_s",
1100            BinaryOp::I64DivU => "i64.div_u",
1101            BinaryOp::I64RemS => "i64.rem_s",
1102            BinaryOp::I64RemU => "i64.rem_u",
1103            BinaryOp::I64And => "i64.and",
1104            BinaryOp::I64Or => "i64.or",
1105            BinaryOp::I64Xor => "i64.xor",
1106            BinaryOp::I64Shl => "i64.shl",
1107            BinaryOp::I64ShrS => "i64.shr_s",
1108            BinaryOp::I64ShrU => "i64.shr_u",
1109            BinaryOp::I64Rotl => "i64.rotl",
1110            BinaryOp::I64Rotr => "i64.rotr",
1111            BinaryOp::I64Eq => "i64.eq",
1112            BinaryOp::I64Ne => "i64.ne",
1113            BinaryOp::I64LtS => "i64.lt_s",
1114            BinaryOp::I64LtU => "i64.lt_u",
1115            BinaryOp::I64GtS => "i64.gt_s",
1116            BinaryOp::I64GtU => "i64.gt_u",
1117            BinaryOp::I64LeS => "i64.le_s",
1118            BinaryOp::I64LeU => "i64.le_u",
1119            BinaryOp::I64GeS => "i64.ge_s",
1120            BinaryOp::I64GeU => "i64.ge_u",
1121            BinaryOp::F32Add => "f32.add",
1122            BinaryOp::F32Copysign => "f32.copysign",
1123            BinaryOp::F64Copysign => "f64.copysign",
1124            BinaryOp::F32Sub => "f32.sub",
1125            BinaryOp::F32Mul => "f32.mul",
1126            BinaryOp::F32Div => "f32.div",
1127            BinaryOp::F32Min => "f32.min",
1128            BinaryOp::F32Max => "f32.max",
1129            BinaryOp::F64Add => "f64.add",
1130            BinaryOp::F64Sub => "f64.sub",
1131            BinaryOp::F64Mul => "f64.mul",
1132            BinaryOp::F64Div => "f64.div",
1133            BinaryOp::F64Min => "f64.min",
1134            BinaryOp::F64Max => "f64.max",
1135            BinaryOp::F32Eq => "f32.eq",
1136            BinaryOp::F32Ne => "f32.ne",
1137            BinaryOp::F32Lt => "f32.lt",
1138            BinaryOp::F32Gt => "f32.gt",
1139            BinaryOp::F32Le => "f32.le",
1140            BinaryOp::F32Ge => "f32.ge",
1141            BinaryOp::F64Eq => "f64.eq",
1142            BinaryOp::F64Ne => "f64.ne",
1143            BinaryOp::F64Lt => "f64.lt",
1144            BinaryOp::F64Gt => "f64.gt",
1145            BinaryOp::F64Le => "f64.le",
1146            BinaryOp::F64Ge => "f64.ge",
1147        }
1148    }
1149
1150    fn input_type(self) -> ValType {
1151        match self {
1152            BinaryOp::I32Add
1153            | BinaryOp::I32Sub
1154            | BinaryOp::I32Mul
1155            | BinaryOp::I32DivS
1156            | BinaryOp::I32DivU
1157            | BinaryOp::I32RemS
1158            | BinaryOp::I32RemU
1159            | BinaryOp::I32And
1160            | BinaryOp::I32Or
1161            | BinaryOp::I32Xor
1162            | BinaryOp::I32Shl
1163            | BinaryOp::I32ShrS
1164            | BinaryOp::I32ShrU
1165            | BinaryOp::I32Rotl
1166            | BinaryOp::I32Rotr
1167            | BinaryOp::I32Eq
1168            | BinaryOp::I32Ne
1169            | BinaryOp::I32LtS
1170            | BinaryOp::I32LtU
1171            | BinaryOp::I32GtS
1172            | BinaryOp::I32GtU
1173            | BinaryOp::I32LeS
1174            | BinaryOp::I32LeU
1175            | BinaryOp::I32GeS
1176            | BinaryOp::I32GeU => ValType::Num(NumType::I32),
1177            BinaryOp::I64Add
1178            | BinaryOp::I64Sub
1179            | BinaryOp::I64Mul
1180            | BinaryOp::I64DivS
1181            | BinaryOp::I64DivU
1182            | BinaryOp::I64RemS
1183            | BinaryOp::I64RemU
1184            | BinaryOp::I64And
1185            | BinaryOp::I64Or
1186            | BinaryOp::I64Xor
1187            | BinaryOp::I64Shl
1188            | BinaryOp::I64ShrS
1189            | BinaryOp::I64ShrU
1190            | BinaryOp::I64Rotl
1191            | BinaryOp::I64Rotr
1192            | BinaryOp::I64Eq
1193            | BinaryOp::I64Ne
1194            | BinaryOp::I64LtS
1195            | BinaryOp::I64LtU
1196            | BinaryOp::I64GtS
1197            | BinaryOp::I64GtU
1198            | BinaryOp::I64LeS
1199            | BinaryOp::I64LeU
1200            | BinaryOp::I64GeS
1201            | BinaryOp::I64GeU => ValType::Num(NumType::I64),
1202            BinaryOp::F32Add
1203            | BinaryOp::F32Sub
1204            | BinaryOp::F32Mul
1205            | BinaryOp::F32Div
1206            | BinaryOp::F32Copysign
1207            | BinaryOp::F32Min
1208            | BinaryOp::F32Max
1209            | BinaryOp::F32Eq
1210            | BinaryOp::F32Ne
1211            | BinaryOp::F32Lt
1212            | BinaryOp::F32Gt
1213            | BinaryOp::F32Le
1214            | BinaryOp::F32Ge => ValType::Num(NumType::F32),
1215            BinaryOp::F64Add
1216            | BinaryOp::F64Sub
1217            | BinaryOp::F64Mul
1218            | BinaryOp::F64Div
1219            | BinaryOp::F64Copysign
1220            | BinaryOp::F64Min
1221            | BinaryOp::F64Max
1222            | BinaryOp::F64Eq
1223            | BinaryOp::F64Ne
1224            | BinaryOp::F64Lt
1225            | BinaryOp::F64Gt
1226            | BinaryOp::F64Le
1227            | BinaryOp::F64Ge => ValType::Num(NumType::F64),
1228        }
1229    }
1230
1231    fn result_type(self) -> ValType {
1232        match self {
1233            BinaryOp::I32Add
1234            | BinaryOp::I32Sub
1235            | BinaryOp::I32Mul
1236            | BinaryOp::I32DivS
1237            | BinaryOp::I32DivU
1238            | BinaryOp::I32RemS
1239            | BinaryOp::I32RemU
1240            | BinaryOp::I32And
1241            | BinaryOp::I32Or
1242            | BinaryOp::I32Xor
1243            | BinaryOp::I32Shl
1244            | BinaryOp::I32ShrS
1245            | BinaryOp::I32ShrU
1246            | BinaryOp::I32Rotl
1247            | BinaryOp::I32Rotr => ValType::Num(NumType::I32),
1248            BinaryOp::I64Add
1249            | BinaryOp::I64Sub
1250            | BinaryOp::I64Mul
1251            | BinaryOp::I64DivS
1252            | BinaryOp::I64DivU
1253            | BinaryOp::I64RemS
1254            | BinaryOp::I64RemU
1255            | BinaryOp::I64And
1256            | BinaryOp::I64Or
1257            | BinaryOp::I64Xor
1258            | BinaryOp::I64Shl
1259            | BinaryOp::I64ShrS
1260            | BinaryOp::I64ShrU
1261            | BinaryOp::I64Rotl
1262            | BinaryOp::I64Rotr => ValType::Num(NumType::I64),
1263            BinaryOp::I32Eq
1264            | BinaryOp::I32Ne
1265            | BinaryOp::I32LtS
1266            | BinaryOp::I32LtU
1267            | BinaryOp::I32GtS
1268            | BinaryOp::I32GtU
1269            | BinaryOp::I32LeS
1270            | BinaryOp::I32LeU
1271            | BinaryOp::I32GeS
1272            | BinaryOp::I32GeU
1273            | BinaryOp::I64Eq
1274            | BinaryOp::I64Ne
1275            | BinaryOp::I64LtS
1276            | BinaryOp::I64LtU
1277            | BinaryOp::I64GtS
1278            | BinaryOp::I64GtU
1279            | BinaryOp::I64LeS
1280            | BinaryOp::I64LeU
1281            | BinaryOp::I64GeS
1282            | BinaryOp::I64GeU => ValType::Num(NumType::I32),
1283            BinaryOp::F32Add
1284            | BinaryOp::F32Sub
1285            | BinaryOp::F32Mul
1286            | BinaryOp::F32Div
1287            | BinaryOp::F32Copysign
1288            | BinaryOp::F32Min
1289            | BinaryOp::F32Max => ValType::Num(NumType::F32),
1290            BinaryOp::F64Add
1291            | BinaryOp::F64Sub
1292            | BinaryOp::F64Mul
1293            | BinaryOp::F64Div
1294            | BinaryOp::F64Copysign
1295            | BinaryOp::F64Min
1296            | BinaryOp::F64Max => ValType::Num(NumType::F64),
1297            BinaryOp::F32Eq
1298            | BinaryOp::F32Ne
1299            | BinaryOp::F32Lt
1300            | BinaryOp::F32Gt
1301            | BinaryOp::F32Le
1302            | BinaryOp::F32Ge
1303            | BinaryOp::F64Eq
1304            | BinaryOp::F64Ne
1305            | BinaryOp::F64Lt
1306            | BinaryOp::F64Gt
1307            | BinaryOp::F64Le
1308            | BinaryOp::F64Ge => ValType::Num(NumType::I32),
1309        }
1310    }
1311}
1312
1313/// Lowering error with byte offset and optional function context.
1314#[derive(Debug, Clone, PartialEq, Eq)]
1315pub struct LowerError {
1316    pub offset: ByteOffset,
1317    pub function: Option<FuncIdx>,
1318    pub kind: LowerErrorKind,
1319}
1320
1321/// Specific lowering failures.
1322#[derive(Debug, Clone, PartialEq, Eq)]
1323pub enum LowerErrorKind {
1324    Validation(ValidationErrorKind),
1325    Decode {
1326        context: DecodeContext,
1327        kind: DecodeErrorKind,
1328    },
1329    UnsupportedInstr {
1330        op: &'static str,
1331    },
1332    StackUnderflow {
1333        op: &'static str,
1334        expected: ValType,
1335    },
1336    TypeMismatch {
1337        op: &'static str,
1338        expected: ValType,
1339        found: ValType,
1340    },
1341    InvalidLabel {
1342        label: u32,
1343    },
1344    InvalidFunction {
1345        func: u32,
1346    },
1347    InvalidGlobal {
1348        global: u32,
1349    },
1350    InvalidTable {
1351        table: u32,
1352    },
1353    InvalidType {
1354        type_idx: u32,
1355    },
1356    UnexpectedElse,
1357    MissingFunctionEnd,
1358}
1359
1360impl From<ValidationError> for LowerError {
1361    fn from(error: ValidationError) -> Self {
1362        Self {
1363            offset: error.offset,
1364            function: error.function,
1365            kind: LowerErrorKind::Validation(error.kind),
1366        }
1367    }
1368}
1369
1370impl<'a> Module<'a> {
1371    /// Validate and lower this module into Baedeker register IR.
1372    pub fn lower(&self) -> Result<RegModule, LowerError> {
1373        lower_module(self)
1374    }
1375}
1376
1377/// Validate and lower a decoded module into register IR.
1378pub fn lower_module(module: &Module<'_>) -> Result<RegModule, LowerError> {
1379    validate::validate_module(module)?;
1380
1381    let imported_func_count = module.imported_function_count() as u32;
1382    let func_types = func_type_table(module);
1383    let global_types = global_type_table(module);
1384    let table_elem_types: Vec<crate::types::RefType> = module
1385        .imports()
1386        .iter()
1387        .filter_map(|import| match &import.desc {
1388            ImportDesc::Table(table) => Some(table.elem),
1389            _ => None,
1390        })
1391        .chain(module.tables().iter().map(|table| table.elem))
1392        .collect();
1393    let tables = ModuleTables {
1394        func_types: &func_types,
1395        global_types: &global_types,
1396        types: module.types(),
1397        table_elem_types: &table_elem_types,
1398    };
1399    let mut funcs = Vec::new();
1400
1401    for (defined_idx, (type_idx, code)) in module.functions().iter().zip(module.codes()).enumerate()
1402    {
1403        let func_idx = FuncIdx(imported_func_count + defined_idx as u32);
1404        let ty = &module.types()[type_idx.0 as usize];
1405        funcs.push(lower_function(func_idx, *type_idx, ty, code, &tables)?);
1406    }
1407
1408    let imported_memory_count = module
1409        .imports()
1410        .iter()
1411        .filter(|import| matches!(import.desc, ImportDesc::Mem(_)))
1412        .count() as u32;
1413    let imported_global_count = module
1414        .imports()
1415        .iter()
1416        .filter(|import| matches!(import.desc, ImportDesc::Global(_)))
1417        .count() as u32;
1418
1419    let memories = module.memories().to_vec();
1420
1421    let mut globals = Vec::with_capacity(module.globals().len());
1422    for global in module.globals() {
1423        globals.push(RegGlobal {
1424            ty: global.global_type.val_type,
1425            mutable: global.global_type.mutability == Mutability::Var,
1426            init: lower_const_expr(global.init_expr, global.init_offset)?,
1427        });
1428    }
1429
1430    let mut data = Vec::new();
1431    for segment in module.data() {
1432        let mode = match &segment.mode {
1433            DataMode::Active {
1434                memory,
1435                offset_expr,
1436                offset_offset,
1437            } => RegDataMode::Active {
1438                memory: *memory,
1439                offset: lower_const_expr(offset_expr, *offset_offset)?,
1440            },
1441            DataMode::Passive => RegDataMode::Passive,
1442        };
1443        data.push(RegDataSegment {
1444            mode,
1445            bytes: segment.init.to_vec(),
1446        });
1447    }
1448
1449    let imported_table_count = module
1450        .imports()
1451        .iter()
1452        .filter(|import| matches!(import.desc, ImportDesc::Table(_)))
1453        .count() as u32;
1454
1455    let imported_funcs = module
1456        .imports()
1457        .iter()
1458        .filter_map(|import| match import.desc {
1459            ImportDesc::Func(type_idx) => Some(RegImport {
1460                module: import.module.clone(),
1461                name: import.name.clone(),
1462                ty: module.types()[type_idx.0 as usize].clone(),
1463            }),
1464            _ => None,
1465        })
1466        .collect();
1467
1468    let imported_memories = module
1469        .imports()
1470        .iter()
1471        .filter_map(|import| match import.desc {
1472            ImportDesc::Mem(ty) => Some(RegMemoryImport {
1473                module: import.module.clone(),
1474                name: import.name.clone(),
1475                ty,
1476            }),
1477            _ => None,
1478        })
1479        .collect();
1480
1481    let imported_globals = module
1482        .imports()
1483        .iter()
1484        .filter_map(|import| match import.desc {
1485            ImportDesc::Global(ty) => Some(RegGlobalImport {
1486                module: import.module.clone(),
1487                name: import.name.clone(),
1488                ty,
1489            }),
1490            _ => None,
1491        })
1492        .collect();
1493
1494    let imported_tables = module
1495        .imports()
1496        .iter()
1497        .filter_map(|import| match &import.desc {
1498            ImportDesc::Table(ty) => Some(RegTableImport {
1499                module: import.module.clone(),
1500                name: import.name.clone(),
1501                ty: ty.clone(),
1502            }),
1503            _ => None,
1504        })
1505        .collect();
1506
1507    let start = module.start();
1508
1509    let tables = module.tables().to_vec();
1510    let types = module.types().to_vec();
1511
1512    let mut elements = Vec::with_capacity(module.elements().len());
1513    for segment in module.elements() {
1514        let mut values = Vec::new();
1515        match &segment.init {
1516            ElementInit::FuncIndices(funcs) => {
1517                values.extend(funcs.iter().map(|func| RegElemValue::FuncRef(*func)));
1518            }
1519            ElementInit::Expressions(exprs) => {
1520                for expr in exprs {
1521                    values.push(lower_element_expr(expr)?);
1522                }
1523            }
1524        }
1525        let mode = match &segment.mode {
1526            ElementMode::Active {
1527                table,
1528                offset_expr,
1529                offset_offset,
1530            } => RegElementMode::Active {
1531                table: *table,
1532                offset: lower_const_expr(offset_expr, *offset_offset)?,
1533            },
1534            ElementMode::Passive => RegElementMode::Passive,
1535            ElementMode::Declarative => RegElementMode::Dropped,
1536        };
1537        elements.push(RegElement { mode, values });
1538    }
1539
1540    let exports = module
1541        .exports()
1542        .iter()
1543        .map(|export| RegExport {
1544            name: export.name.clone(),
1545            desc: match export.desc {
1546                ExportDesc::Func(idx) => RegExportDesc::Func(idx),
1547                ExportDesc::Table(idx) => RegExportDesc::Table(idx),
1548                ExportDesc::Mem(idx) => RegExportDesc::Mem(idx),
1549                ExportDesc::Global(idx) => RegExportDesc::Global(idx),
1550            },
1551        })
1552        .collect();
1553
1554    Ok(RegModule {
1555        funcs,
1556        exports,
1557        imported_func_count,
1558        imported_funcs,
1559        imported_memories,
1560        imported_globals,
1561        imported_tables,
1562        start,
1563        memories,
1564        globals,
1565        tables,
1566        elements,
1567        types,
1568        data,
1569        imported_memory_count,
1570        imported_global_count,
1571        imported_table_count,
1572    })
1573}
1574
1575/// Lower one element-segment initializer expression to a funcref value.
1576/// Only `ref.func`/`ref.null` are supported at runtime (other const
1577/// expressions require host-provided imports).
1578fn lower_element_expr(expr: &crate::types::ElementExpr<'_>) -> Result<RegElemValue, LowerError> {
1579    let instrs =
1580        decode_instr_sequence_with_offsets(expr.expr, expr.offset).map_err(|error| LowerError {
1581            offset: error.offset,
1582            function: None,
1583            kind: LowerErrorKind::Decode {
1584                context: error.context,
1585                kind: error.kind,
1586            },
1587        })?;
1588    match instrs.as_slice() {
1589        [first, last] if matches!(last.instr, Instr::End) => match first.instr {
1590            Instr::RefFunc(func) => Ok(RegElemValue::FuncRef(func)),
1591            Instr::RefNull(_) => Ok(RegElemValue::Null),
1592            Instr::GlobalGet(global) => Ok(RegElemValue::GlobalGet(global)),
1593            ref other => Err(LowerError {
1594                offset: first.offset,
1595                function: None,
1596                kind: LowerErrorKind::UnsupportedInstr {
1597                    op: instr_name(other),
1598                },
1599            }),
1600        },
1601        _ => Err(LowerError {
1602            offset: ByteOffset(expr.offset),
1603            function: None,
1604            kind: LowerErrorKind::UnsupportedInstr {
1605                op: "multi-instruction element expression",
1606            },
1607        }),
1608    }
1609}
1610
1611/// Lower a constant expression (global initializer, data segment offset)
1612/// into owned form. Supports the const instructions plus the
1613/// extended-const integer arithmetic the validator accepts.
1614pub(crate) fn lower_const_expr(
1615    expr: &[u8],
1616    offset: usize,
1617) -> Result<Vec<RegConstInstr>, LowerError> {
1618    let instrs = decode_instr_sequence_with_offsets(expr, offset).map_err(|error| LowerError {
1619        offset: error.offset,
1620        function: None,
1621        kind: LowerErrorKind::Decode {
1622            context: error.context,
1623            kind: error.kind,
1624        },
1625    })?;
1626
1627    let mut lowered = Vec::with_capacity(instrs.len());
1628    for decoded in instrs {
1629        let const_instr = match decoded.instr {
1630            Instr::I32Const(value) => RegConstInstr::I32Const(value),
1631            Instr::I64Const(value) => RegConstInstr::I64Const(value),
1632            Instr::F32Const(value) => RegConstInstr::F32Const(value.to_bits()),
1633            Instr::F64Const(value) => RegConstInstr::F64Const(value.to_bits()),
1634            Instr::GlobalGet(global) => RegConstInstr::GlobalGet(global),
1635            Instr::RefNull(_) => RegConstInstr::RefNull,
1636            Instr::RefFunc(func) => RegConstInstr::RefFunc(func),
1637            Instr::I32Add => RegConstInstr::I32Add,
1638            Instr::I32Sub => RegConstInstr::I32Sub,
1639            Instr::I32Mul => RegConstInstr::I32Mul,
1640            Instr::I64Add => RegConstInstr::I64Add,
1641            Instr::I64Sub => RegConstInstr::I64Sub,
1642            Instr::I64Mul => RegConstInstr::I64Mul,
1643            Instr::End => break,
1644            ref other => {
1645                return Err(LowerError {
1646                    offset: decoded.offset,
1647                    function: None,
1648                    kind: LowerErrorKind::UnsupportedInstr {
1649                        op: instr_name(other),
1650                    },
1651                });
1652            }
1653        };
1654        lowered.push(const_instr);
1655    }
1656    Ok(lowered)
1657}
1658
1659/// Resolve the `FuncType` for every function in the index space (imported
1660/// first, then defined), so `call` lowering can type its operands.
1661fn func_type_table<'a, 'm>(module: &'a Module<'m>) -> Vec<&'a FuncType> {
1662    let mut table = Vec::new();
1663    for import in module.imports() {
1664        if let ImportDesc::Func(type_idx) = import.desc {
1665            table.push(&module.types()[type_idx.0 as usize]);
1666        }
1667    }
1668    for type_idx in module.functions() {
1669        table.push(&module.types()[type_idx.0 as usize]);
1670    }
1671    table
1672}
1673
1674/// Resolve the value type of every global in the index space (imported
1675/// first, then defined), so `global.get`/`global.set` lowering can type
1676/// its operands.
1677fn global_type_table(module: &Module<'_>) -> Vec<ValType> {
1678    module
1679        .imports()
1680        .iter()
1681        .filter_map(|import| match import.desc {
1682            ImportDesc::Global(global) => Some(global.val_type),
1683            _ => None,
1684        })
1685        .chain(
1686            module
1687                .globals()
1688                .iter()
1689                .map(|global| global.global_type.val_type),
1690        )
1691        .collect()
1692}
1693
1694fn lower_function(
1695    func_idx: FuncIdx,
1696    type_idx: TypeIdx,
1697    ty: &FuncType,
1698    code: &CodeBody<'_>,
1699    tables: &ModuleTables<'_>,
1700) -> Result<RegFunc, LowerError> {
1701    let instrs = code
1702        .instructions_with_offsets()
1703        .map_err(|error| LowerError {
1704            offset: error.offset,
1705            function: Some(func_idx),
1706            kind: LowerErrorKind::Decode {
1707                context: error.context,
1708                kind: error.kind,
1709            },
1710        })?;
1711
1712    let locals = local_types(ty, code.locals.as_slice());
1713    let mut builder = FuncBuilder::new(func_idx, type_idx, ty, locals, tables);
1714
1715    for decoded in instrs {
1716        if builder.lower_instr(decoded)? {
1717            return Ok(builder.finish());
1718        }
1719    }
1720
1721    Err(LowerError {
1722        offset: ByteOffset(code.body_offset + code.body.len()),
1723        function: Some(func_idx),
1724        kind: LowerErrorKind::MissingFunctionEnd,
1725    })
1726}
1727
1728fn local_types(ty: &FuncType, locals: &[LocalDecl]) -> Vec<ValType> {
1729    let local_count = locals
1730        .iter()
1731        .map(|local| local.count as usize)
1732        .sum::<usize>();
1733    let mut types = Vec::with_capacity(ty.params.len() + local_count);
1734    types.extend_from_slice(ty.params.as_slice());
1735    for local in locals {
1736        for _ in 0..local.count {
1737            types.push(local.val_type);
1738        }
1739    }
1740    types
1741}
1742
1743struct FuncBuilder<'b> {
1744    func_idx: FuncIdx,
1745    type_idx: TypeIdx,
1746    params: Vec<ValType>,
1747    results: Vec<ValType>,
1748    locals: Vec<ValType>,
1749    stack: Vec<RegValue>,
1750    reg_types: Vec<ValType>,
1751    blocks: Vec<RegBlock>,
1752    current_instrs: Vec<RegInstr>,
1753    /// Shared module-level tables used while lowering function bodies.
1754    tables: &'b ModuleTables<'b>,
1755    /// Stack of active block/loop/if frames. Each entry records the label of
1756    /// the block that should follow the `end` of this control structure.
1757    label_stack: Vec<LabelFrame>,
1758    /// Branches whose target block index needs back-patching.
1759    pending_branches: Vec<PendingBranch>,
1760    /// Back-edge trampolines for conditional branches to loops with
1761    /// parameters, created at `finish`.
1762    pending_trampolines: Vec<TrampolineReq>,
1763}
1764
1765/// A branch whose target block index needs back-patching once the target
1766/// frame's continuation block is known.
1767struct PendingBranch {
1768    /// The block containing the branch terminator.
1769    block: usize,
1770    /// Position in `label_stack` of the targeted frame.
1771    frame_pos: usize,
1772    /// Branch-carried values (copy sources for the continuation).
1773    values: Vec<Reg>,
1774    /// Which terminator slot to patch.
1775    slot: BranchSlot,
1776}
1777
1778/// A loop back-edge target: the header block and the canonical parameter
1779/// registers branch values must be copied into.
1780struct LoopTarget {
1781    header: u32,
1782    param_regs: Vec<Reg>,
1783}
1784
1785/// Which conditional reference branch is being lowered.
1786enum RefBranchKind {
1787    /// `br_on_null`: taken when the reference is null.
1788    OnNull,
1789    /// `br_on_non_null`: taken when the reference is non-null (the ref is
1790    /// forwarded as the last branch value).
1791    OnNonNull,
1792}
1793
1794/// A request for a back-edge trampoline block: a conditional branch to a
1795/// loop with parameters cannot copy values into the loop's parameter
1796/// registers in its own block (the copies would clobber the registers on
1797/// the not-taken path), so the branch targets a trampoline that runs the
1798/// copies and then branches unconditionally.
1799struct TrampolineReq {
1800    /// The block containing the conditional branch terminator.
1801    block: usize,
1802    /// Which terminator slot jumps to the trampoline.
1803    slot: BranchSlot,
1804    /// Copy destinations (the loop's parameter registers).
1805    param_regs: Vec<Reg>,
1806    /// Copy sources (the branch's values).
1807    values: Vec<Reg>,
1808    /// The loop header block the trampoline branches to.
1809    header: u32,
1810    /// Byte offset of the originating branch instruction.
1811    offset: ByteOffset,
1812}
1813
1814/// Which target slot of a branch terminator a pending branch patches.
1815#[derive(Debug, Clone, Copy)]
1816enum BranchSlot {
1817    /// The single target of `br` / `br_if` / an if-then exit.
1818    Single,
1819    /// The i-th target of `br_table` (`i == targets.len()` patches the
1820    /// default target).
1821    Table(usize),
1822}
1823
1824struct LabelFrame {
1825    /// The label that `br` with this index targets.
1826    #[allow(dead_code)]
1827    label: LabelIdx,
1828    /// What kind of control structure this frame belongs to.
1829    kind: FrameKind,
1830    /// The result types expected at the `end` of this control structure.
1831    result_types: Vec<ValType>,
1832    /// The parameter types consumed at the start of this control structure.
1833    /// Branches to a `loop` label carry parameters (to the loop header);
1834    /// branches to any other label carry results (to the continuation).
1835    param_types: Vec<ValType>,
1836    /// The canonical registers holding the frame's parameters: the
1837    /// registers the body reads when it consumes params from the stack.
1838    /// Loop back-edges must deliver branch values into these registers.
1839    param_regs: Vec<Reg>,
1840    /// Operand stack height at frame entry (after consuming parameters).
1841    height: usize,
1842    /// Whether the code currently being lowered in this frame is
1843    /// unreachable (polymorphic stack, per the spec validation algorithm).
1844    unreachable: bool,
1845}
1846
1847/// Coarse reference compatibility for lowering's operand checks.
1848///
1849/// Lowering tracks types only for register allocation; the validator has
1850/// already proven precise ref types (nullability, concrete type indices,
1851/// subtyping). Here we only need to keep function references and external
1852/// references from crossing kinds.
1853fn ref_compatible(found: ValType, expected: ValType) -> bool {
1854    fn kind(ty: &crate::types::RefType) -> u8 {
1855        match ty {
1856            crate::types::RefType::FuncRef => 0,
1857            crate::types::RefType::ExternRef => 1,
1858            crate::types::RefType::Typed { heap, .. } => match heap {
1859                crate::types::HeapType::Func | crate::types::HeapType::Type(_) => 0,
1860                crate::types::HeapType::Extern => 1,
1861            },
1862        }
1863    }
1864    match (found, expected) {
1865        (ValType::Ref(found), ValType::Ref(expected)) => kind(&found) == kind(&expected),
1866        _ => false,
1867    }
1868}
1869
1870/// function types, global types, the type section, and table element
1871/// types (all imported-first where an index space applies).
1872/// Module-level index-space tables shared by every function lowering:
1873/// function types, global types, the type section, and table element
1874/// types (all imported-first where an index space applies).
1875struct ModuleTables<'a> {
1876    func_types: &'a [&'a FuncType],
1877    global_types: &'a [ValType],
1878    types: &'a [FuncType],
1879    table_elem_types: &'a [crate::types::RefType],
1880}
1881
1882/// The kind of control structure a label frame describes.
1883enum FrameKind {
1884    /// `block` (or the implicit function body frame).
1885    Block,
1886    /// `loop` — branches target the loop header block directly.
1887    Loop { header_block: u32 },
1888    /// `if` — `cond_block` is the IfFork block whose `else_block` field needs
1889    /// back-patching; `else_seen` records whether an `else` was lowered.
1890    If { cond_block: usize, else_seen: bool },
1891}
1892
1893impl<'b> FuncBuilder<'b> {
1894    fn new(
1895        func_idx: FuncIdx,
1896        type_idx: TypeIdx,
1897        ty: &FuncType,
1898        locals: Vec<ValType>,
1899        tables: &'b ModuleTables<'b>,
1900    ) -> Self {
1901        // The function body itself is label 0, targeting a block that will
1902        // receive function-end returns (created on demand).
1903        Self {
1904            func_idx,
1905            type_idx,
1906            params: ty.params.clone(),
1907            results: ty.results.clone(),
1908            locals,
1909            stack: Vec::new(),
1910            reg_types: Vec::new(),
1911            blocks: Vec::new(),
1912            current_instrs: Vec::new(),
1913            label_stack: alloc::vec![LabelFrame {
1914                label: LabelIdx(0),
1915                kind: FrameKind::Block,
1916                result_types: ty.results.clone(),
1917                param_types: Vec::new(),
1918                param_regs: Vec::new(),
1919                height: 0,
1920                unreachable: false,
1921            }],
1922            pending_branches: Vec::new(),
1923            pending_trampolines: Vec::new(),
1924            tables,
1925        }
1926    }
1927
1928    fn finish_block_with_label(&mut self, label: LabelIdx, term: RegTerm) {
1929        let instrs = core::mem::take(&mut self.current_instrs);
1930        self.blocks.push(RegBlock::new(label, instrs, term));
1931    }
1932
1933    fn finish_block(&mut self, term: RegTerm) {
1934        let label = LabelIdx(self.blocks.len() as u32);
1935        self.finish_block_with_label(label, term);
1936    }
1937
1938    /// Finish a conditional reference branch (`br_on_null` /
1939    /// `br_on_non_null`), mirroring the `br_if` discipline: loop back-edges
1940    /// get a copy trampoline; other targets are back-patched at frame `end`.
1941    fn finish_cond_ref_branch(
1942        &mut self,
1943        offset: ByteOffset,
1944        frame_pos: usize,
1945        loop_header: Option<LoopTarget>,
1946        values: Vec<Reg>,
1947        value_reg: Reg,
1948        kind: RefBranchKind,
1949    ) {
1950        let make_term = |target_block: u32, values: Vec<Reg>| match kind {
1951            RefBranchKind::OnNull => RegTerm::BrIfNull {
1952                value: value_reg,
1953                target_block,
1954                values,
1955            },
1956            RefBranchKind::OnNonNull => RegTerm::BrIfNonNull {
1957                value: value_reg,
1958                target_block,
1959                values,
1960            },
1961        };
1962        let br_block_idx = self.blocks.len();
1963        if let Some(loop_target) = loop_header {
1964            self.pending_trampolines.push(TrampolineReq {
1965                block: br_block_idx,
1966                slot: BranchSlot::Single,
1967                param_regs: loop_target.param_regs,
1968                values: values.clone(),
1969                header: loop_target.header,
1970                offset,
1971            });
1972            self.finish_block(make_term(0, values));
1973        } else {
1974            self.pending_branches.push(PendingBranch {
1975                block: br_block_idx,
1976                frame_pos,
1977                values: values.clone(),
1978                slot: BranchSlot::Single,
1979            });
1980            self.finish_block(make_term(0, values));
1981        }
1982    }
1983
1984    fn finish(mut self) -> RegFunc {
1985        // If there are pending instructions without a terminator, add Fallthrough
1986        if !self.current_instrs.is_empty() || self.blocks.is_empty() {
1987            self.finish_block(RegTerm::Fallthrough);
1988        }
1989        // Create back-edge trampolines for conditional branches to loops
1990        // with parameters: each runs the parameter copies, then branches
1991        // unconditionally to the header. Trampolines sit at the end of the
1992        // block list so no fallthrough can reach them.
1993        for req in core::mem::take(&mut self.pending_trampolines) {
1994            let trampoline_idx = self.blocks.len() as u32;
1995            let instrs = req
1996                .param_regs
1997                .iter()
1998                .zip(req.values.iter())
1999                .filter(|(dst, src)| dst != src)
2000                .map(|(dst, src)| RegInstr {
2001                    offset: req.offset,
2002                    op: RegOp::Copy {
2003                        dst: *dst,
2004                        src: *src,
2005                    },
2006                })
2007                .collect();
2008            self.blocks.push(RegBlock::new(
2009                LabelIdx(trampoline_idx),
2010                instrs,
2011                RegTerm::Br {
2012                    target_block: req.header,
2013                    values: Vec::new(),
2014                },
2015            ));
2016            match (req.slot, &mut self.blocks[req.block].term) {
2017                (
2018                    BranchSlot::Single,
2019                    RegTerm::BrIf { target_block, .. }
2020                    | RegTerm::BrIfNull { target_block, .. }
2021                    | RegTerm::BrIfNonNull { target_block, .. },
2022                ) => {
2023                    *target_block = trampoline_idx;
2024                }
2025                (
2026                    BranchSlot::Table(slot),
2027                    RegTerm::BrTable {
2028                        targets, default, ..
2029                    },
2030                ) => {
2031                    if slot < targets.len() {
2032                        targets[slot] = trampoline_idx;
2033                    } else {
2034                        *default = trampoline_idx;
2035                    }
2036                }
2037                (slot, term) => {
2038                    unreachable!("trampoline slot {slot:?} on non-branch terminator {term:?}")
2039                }
2040            }
2041        }
2042        RegFunc {
2043            idx: self.func_idx,
2044            type_idx: self.type_idx,
2045            params: self.params,
2046            results: self.results,
2047            locals: self.locals,
2048            reg_types: self.reg_types,
2049            blocks: self.blocks,
2050        }
2051    }
2052    /// Lower one decoded instruction. Returns `true` when the function body is complete.
2053    fn lower_instr(&mut self, decoded: DecodedInstr) -> Result<bool, LowerError> {
2054        let offset = decoded.offset;
2055        match decoded.instr {
2056            Instr::LocalGet(local) => {
2057                let ty = self.locals[local.0 as usize];
2058                let dst = self.alloc_reg(ty);
2059                self.stack.push(RegValue { reg: dst, ty });
2060                self.emit(offset, RegOp::LocalGet { dst, local });
2061            }
2062            Instr::LocalSet(local) => {
2063                let expected = self.locals[local.0 as usize];
2064                let value = self.pop_expect(offset, "local.set", expected)?;
2065                self.emit(
2066                    offset,
2067                    RegOp::LocalSet {
2068                        local,
2069                        value: value.reg,
2070                    },
2071                );
2072            }
2073            Instr::LocalTee(local) => {
2074                let expected = self.locals[local.0 as usize];
2075                let value = self.pop_expect(offset, "local.tee", expected)?;
2076                self.stack.push(value);
2077                self.emit(
2078                    offset,
2079                    RegOp::LocalTee {
2080                        local,
2081                        value: value.reg,
2082                    },
2083                );
2084            }
2085            Instr::Drop => {
2086                let value = self.pop_any(offset, "drop")?;
2087                self.emit(offset, RegOp::Drop { value: value.reg });
2088            }
2089            Instr::Select => {
2090                let cond = self.pop_expect(offset, "select", ValType::Num(NumType::I32))?;
2091                // Untyped select: both operands must share the same numeric
2092                // type; the second operand's type is discovered from the
2093                // stack (the validator has already proven they match).
2094                let v2 = self.pop_any(offset, "select")?;
2095                let v1 = self.pop_expect(offset, "select", v2.ty)?;
2096                let dst = self.alloc_reg(v1.ty);
2097                self.stack.push(RegValue {
2098                    reg: dst,
2099                    ty: v1.ty,
2100                });
2101                self.emit(
2102                    offset,
2103                    RegOp::Select {
2104                        dst,
2105                        v1: v1.reg,
2106                        v2: v2.reg,
2107                        cond: cond.reg,
2108                    },
2109                );
2110            }
2111            Instr::SelectTyped(types) => {
2112                let cond = self.pop_expect(offset, "select", ValType::Num(NumType::I32))?;
2113                // The validator guarantees exactly one result type.
2114                let Some(&ty) = types.first() else {
2115                    return Err(LowerError {
2116                        offset,
2117                        function: Some(self.func_idx),
2118                        kind: LowerErrorKind::UnsupportedInstr {
2119                            op: "select with empty type annotation",
2120                        },
2121                    });
2122                };
2123                let v2 = self.pop_expect(offset, "select", ty)?;
2124                let v1 = self.pop_expect(offset, "select", ty)?;
2125                let dst = self.alloc_reg(ty);
2126                self.stack.push(RegValue { reg: dst, ty });
2127                self.emit(
2128                    offset,
2129                    RegOp::Select {
2130                        dst,
2131                        v1: v1.reg,
2132                        v2: v2.reg,
2133                        cond: cond.reg,
2134                    },
2135                );
2136            }
2137            Instr::I32Const(value) => {
2138                let dst = self.alloc_reg(ValType::Num(NumType::I32));
2139                self.stack.push(RegValue {
2140                    reg: dst,
2141                    ty: ValType::Num(NumType::I32),
2142                });
2143                self.emit(offset, RegOp::I32Const { dst, value });
2144            }
2145            Instr::I64Const(value) => {
2146                let dst = self.alloc_reg(ValType::Num(NumType::I64));
2147                self.stack.push(RegValue {
2148                    reg: dst,
2149                    ty: ValType::Num(NumType::I64),
2150                });
2151                self.emit(offset, RegOp::I64Const { dst, value });
2152            }
2153            Instr::F32Const(value) => {
2154                let dst = self.alloc_reg(ValType::Num(NumType::F32));
2155                self.stack.push(RegValue {
2156                    reg: dst,
2157                    ty: ValType::Num(NumType::F32),
2158                });
2159                self.emit(offset, RegOp::F32Const { dst, value });
2160            }
2161            Instr::F64Const(value) => {
2162                let dst = self.alloc_reg(ValType::Num(NumType::F64));
2163                self.stack.push(RegValue {
2164                    reg: dst,
2165                    ty: ValType::Num(NumType::F64),
2166                });
2167                self.emit(offset, RegOp::F64Const { dst, value });
2168            }
2169            Instr::Unreachable => {
2170                self.finish_block(RegTerm::Trap);
2171                self.set_unreachable();
2172            }
2173            Instr::Nop => {}
2174            Instr::Block(block_type) => {
2175                self.finish_block(RegTerm::Fallthrough);
2176                self.enter_frame(offset, "block", FrameKind::Block, block_type)?;
2177            }
2178            Instr::Loop(block_type) => {
2179                self.finish_block(RegTerm::Fallthrough);
2180                // The loop body starts a fresh block; branches to the loop
2181                // label jump back to it (back-edge), so its index is known
2182                // immediately and needs no back-patching.
2183                let header_block = self.blocks.len() as u32;
2184                self.enter_frame(offset, "loop", FrameKind::Loop { header_block }, block_type)?;
2185            }
2186            Instr::If(block_type) => {
2187                let cond = self.pop_expect(offset, "if", ValType::Num(NumType::I32))?;
2188                let cond_block = self.blocks.len();
2189                self.finish_block(RegTerm::IfFork {
2190                    cond: cond.reg,
2191                    then_block: (cond_block + 1) as u32,
2192                    // Back-patched at `else` (else-body start) or at `end`
2193                    // (no else: the continuation).
2194                    else_block: 0,
2195                });
2196                self.enter_frame(
2197                    offset,
2198                    "if",
2199                    FrameKind::If {
2200                        cond_block,
2201                        else_seen: false,
2202                    },
2203                    block_type,
2204                )?;
2205            }
2206            Instr::Else => {
2207                let frame_pos = self.label_stack.len() - 1;
2208                let (cond_block, result_types, frame_height) = match self.label_stack.last() {
2209                    Some(LabelFrame {
2210                        kind:
2211                            FrameKind::If {
2212                                cond_block,
2213                                else_seen: false,
2214                            },
2215                        result_types,
2216                        height,
2217                        ..
2218                    }) => (*cond_block, result_types.clone(), *height),
2219                    _ => {
2220                        return Err(LowerError {
2221                            offset,
2222                            function: Some(self.func_idx),
2223                            kind: LowerErrorKind::UnexpectedElse,
2224                        });
2225                    }
2226                };
2227                // Pop the then-body's results; they become the values of a
2228                // synthetic branch from the then-body exit to the
2229                // continuation, back-patched at `end` like any other branch.
2230                let mut values = Vec::with_capacity(result_types.len());
2231                for &expected in result_types.iter().rev() {
2232                    let found = self.pop_expect(offset, "else", expected)?;
2233                    values.push(found.reg);
2234                }
2235                values.reverse();
2236                let br_block_idx = self.blocks.len();
2237                self.pending_branches.push(PendingBranch {
2238                    block: br_block_idx,
2239                    frame_pos,
2240                    values: values.clone(),
2241                    slot: BranchSlot::Single,
2242                });
2243                self.finish_block(RegTerm::Br {
2244                    target_block: 0,
2245                    values,
2246                });
2247                // The else-body starts at the next block.
2248                let else_start = self.blocks.len() as u32;
2249                if let RegTerm::IfFork { else_block, .. } = &mut self.blocks[cond_block].term {
2250                    *else_block = else_start;
2251                }
2252                // Reset to the frame entry state for the else-body: the
2253                // else path starts with the frame's parameters again.
2254                self.stack.truncate(frame_height);
2255                let (param_regs, param_types) = {
2256                    let frame = self.label_stack.last().expect("if frame checked above");
2257                    (frame.param_regs.clone(), frame.param_types.clone())
2258                };
2259                for (&reg, &ty) in param_regs.iter().zip(param_types.iter()) {
2260                    self.stack.push(RegValue { reg, ty });
2261                }
2262                let frame = self.label_stack.last_mut().expect("if frame checked above");
2263                frame.unreachable = false;
2264                if let FrameKind::If { else_seen, .. } = &mut frame.kind {
2265                    *else_seen = true;
2266                }
2267            }
2268            Instr::End => {
2269                // The outermost end is the function end. The frame must
2270                // still be on the stack while results are popped (pops are
2271                // frame-aware), so handle it before popping.
2272                if self.label_stack.len() == 1 {
2273                    let values = self.pop_results(offset)?;
2274                    self.label_stack.pop();
2275                    // Finish the body block, then patch branches targeting
2276                    // the function frame (`br 0`) to a continuation block
2277                    // that returns — delivering their values into the
2278                    // return registers via copies, like any other frame.
2279                    self.finish_block(RegTerm::Fallthrough);
2280                    let continuation_idx = self.blocks.len() as u32;
2281                    for pending in core::mem::take(&mut self.pending_branches) {
2282                        match &mut self.blocks[pending.block].term {
2283                            RegTerm::Br { target_block, .. }
2284                            | RegTerm::BrIf { target_block, .. }
2285                            | RegTerm::BrIfNull { target_block, .. }
2286                            | RegTerm::BrIfNonNull { target_block, .. } => {
2287                                *target_block = continuation_idx;
2288                            }
2289                            RegTerm::BrTable {
2290                                targets, default, ..
2291                            } => {
2292                                if let BranchSlot::Table(slot) = pending.slot {
2293                                    if slot < targets.len() {
2294                                        targets[slot] = continuation_idx;
2295                                    } else {
2296                                        *default = continuation_idx;
2297                                    }
2298                                }
2299                            }
2300                            other => unreachable!(
2301                                "pending branch block has non-branch terminator: {other:?}"
2302                            ),
2303                        }
2304                        for (dst, src) in values.iter().zip(pending.values.iter()) {
2305                            if dst != src {
2306                                self.blocks[pending.block].instrs.push(RegInstr {
2307                                    offset,
2308                                    op: RegOp::Copy {
2309                                        dst: *dst,
2310                                        src: *src,
2311                                    },
2312                                });
2313                            }
2314                        }
2315                    }
2316                    self.finish_block(RegTerm::Return { values });
2317                    return Ok(true);
2318                }
2319                // Pop results matching this control frame's expected types.
2320                // The frame must still be on the stack while they are
2321                // popped: pops are frame-aware (entry height and
2322                // polymorphic-stack state).
2323                let result_types = self
2324                    .label_stack
2325                    .last()
2326                    .ok_or(LowerError {
2327                        offset,
2328                        function: Some(self.func_idx),
2329                        kind: LowerErrorKind::MissingFunctionEnd,
2330                    })?
2331                    .result_types
2332                    .clone();
2333                let mut values = Vec::with_capacity(result_types.len());
2334                for &expected in result_types.iter().rev() {
2335                    let found = self.pop_expect(offset, "end", expected)?;
2336                    values.push(found.reg);
2337                }
2338                values.reverse();
2339                let frame = self
2340                    .label_stack
2341                    .pop()
2342                    .expect("frame presence checked above");
2343                // Reset the operand stack to the frame entry height, then push
2344                // the block's result values back onto the outer stack.
2345                self.stack.truncate(frame.height);
2346                for (&reg, &ty) in values.iter().zip(frame.result_types.iter()) {
2347                    self.stack.push(RegValue { reg, ty });
2348                }
2349                // Finish the body block.
2350                self.finish_block(RegTerm::Fallthrough);
2351                // The continuation block will be at self.blocks.len().
2352                // For an `if` without `else` that takes parameters, the
2353                // IfFork's else edge is the identity: deliver the params into
2354                // the continuation's registers via a trampoline inserted
2355                // before the continuation (the else path never computed the
2356                // result registers). Branches target the continuation after
2357                // the trampoline; the else edge targets the trampoline.
2358                let frame_pos = self.label_stack.len(); // position of the popped frame
2359                let mut else_trampoline: Option<(usize, u32, usize)> = None;
2360                if let FrameKind::If {
2361                    cond_block,
2362                    else_seen: false,
2363                } = frame.kind
2364                    && !frame.param_regs.is_empty()
2365                {
2366                    // The then-body must branch over the trampoline (it
2367                    // would otherwise fall through it).
2368                    let body_end_idx = self.blocks.len() - 1;
2369                    let trampoline_idx = self.blocks.len() as u32;
2370                    let instrs = values
2371                        .iter()
2372                        .zip(frame.param_regs.iter())
2373                        .filter(|(dst, src)| dst != src)
2374                        .map(|(dst, src)| RegInstr {
2375                            offset,
2376                            op: RegOp::Copy {
2377                                dst: *dst,
2378                                src: *src,
2379                            },
2380                        })
2381                        .collect();
2382                    self.blocks.push(RegBlock::new(
2383                        LabelIdx(trampoline_idx),
2384                        instrs,
2385                        RegTerm::Fallthrough,
2386                    ));
2387                    else_trampoline = Some((cond_block, trampoline_idx, body_end_idx));
2388                }
2389                // Back-patch all pending branches targeting this frame,
2390                // preserving each terminator's kind (Br vs BrIf), and append
2391                // copies delivering each branch's values into the registers
2392                // the continuation expects.
2393                let continuation_idx = self.blocks.len() as u32;
2394                let mut remaining = Vec::with_capacity(self.pending_branches.len());
2395                for pending in core::mem::take(&mut self.pending_branches) {
2396                    if pending.frame_pos != frame_pos {
2397                        remaining.push(pending);
2398                        continue;
2399                    }
2400                    let term = &mut self.blocks[pending.block].term;
2401                    match pending.slot {
2402                        BranchSlot::Single => match term {
2403                            RegTerm::Br { target_block, .. }
2404                            | RegTerm::BrIf { target_block, .. }
2405                            | RegTerm::BrIfNull { target_block, .. }
2406                            | RegTerm::BrIfNonNull { target_block, .. } => {
2407                                *target_block = continuation_idx;
2408                            }
2409                            other => unreachable!(
2410                                "pending branch block has non-branch terminator: {other:?}"
2411                            ),
2412                        },
2413                        BranchSlot::Table(slot) => match term {
2414                            RegTerm::BrTable {
2415                                targets, default, ..
2416                            } => {
2417                                if slot < targets.len() {
2418                                    targets[slot] = continuation_idx;
2419                                } else {
2420                                    *default = continuation_idx;
2421                                }
2422                            }
2423                            other => unreachable!(
2424                                "pending branch block has non-branch terminator: {other:?}"
2425                            ),
2426                        },
2427                    }
2428                    // Copies from every targeted frame write disjoint
2429                    // register sets from the same sources, so appending
2430                    // per-frame copies to one block is sound.
2431                    for (dst, src) in values.iter().zip(pending.values.iter()) {
2432                        if dst != src {
2433                            self.blocks[pending.block].instrs.push(RegInstr {
2434                                offset,
2435                                op: RegOp::Copy {
2436                                    dst: *dst,
2437                                    src: *src,
2438                                },
2439                            });
2440                        }
2441                    }
2442                }
2443                self.pending_branches = remaining;
2444                // For an `if` without `else`, patch the IfFork's else edge to
2445                // the identity trampoline (when params exist) and retarget
2446                // the then-body's end to branch over it to the continuation.
2447                if let FrameKind::If {
2448                    cond_block,
2449                    else_seen: false,
2450                } = frame.kind
2451                {
2452                    if let RegTerm::IfFork { else_block, .. } = &mut self.blocks[cond_block].term {
2453                        *else_block = match else_trampoline {
2454                            Some((_, trampoline_idx, _)) => trampoline_idx,
2455                            None => continuation_idx,
2456                        };
2457                    }
2458                    if let Some((_, _, body_end_idx)) = else_trampoline {
2459                        self.blocks[body_end_idx].term = RegTerm::Br {
2460                            target_block: continuation_idx,
2461                            values: Vec::new(),
2462                        };
2463                    }
2464                }
2465                // Start a new continuation block (content will be filled by
2466                // subsequent instructions).
2467                self.finish_block(RegTerm::Fallthrough);
2468            }
2469            Instr::Br(label) => {
2470                let frame_pos = self.label_position(offset, label)?;
2471                // Branches to a loop label jump to the header carrying the
2472                // loop's parameters; all other branches jump to the frame's
2473                // continuation carrying its results.
2474                let (branch_types, loop_header) = self.branch_types_at(frame_pos);
2475                let mut values = Vec::with_capacity(branch_types.len());
2476                for &expected in branch_types.iter().rev() {
2477                    let found = self.pop_expect(offset, "br", expected)?;
2478                    values.push(found.reg);
2479                }
2480                values.reverse();
2481                if let Some(loop_target) = loop_header {
2482                    // Unconditional back-edge: deliver values into the
2483                    // loop's parameter registers inline (always taken).
2484                    self.emit_copies(offset, &loop_target.param_regs, &values);
2485                    self.finish_block(RegTerm::Br {
2486                        target_block: loop_target.header,
2487                        values,
2488                    });
2489                } else {
2490                    let br_block_idx = self.blocks.len();
2491                    self.pending_branches.push(PendingBranch {
2492                        block: br_block_idx,
2493                        frame_pos,
2494                        values: values.clone(),
2495                        slot: BranchSlot::Single,
2496                    });
2497                    self.finish_block(RegTerm::Br {
2498                        target_block: 0,
2499                        values,
2500                    });
2501                }
2502                self.set_unreachable();
2503            }
2504            Instr::BrIf(label) => {
2505                let cond = self.pop_expect(offset, "br_if", ValType::Num(NumType::I32))?;
2506                let frame_pos = self.label_position(offset, label)?;
2507                let (branch_types, loop_header) = self.branch_types_at(frame_pos);
2508                let mut values = Vec::with_capacity(branch_types.len());
2509                for &expected in branch_types.iter().rev() {
2510                    let found = self.pop_expect(offset, "br_if", expected)?;
2511                    values.push(found.reg);
2512                }
2513                values.reverse();
2514                // The not-taken path keeps the branch values on the stack.
2515                for (&reg, &ty) in values.iter().zip(branch_types.iter()) {
2516                    self.stack.push(RegValue { reg, ty });
2517                }
2518                if let Some(loop_target) = loop_header {
2519                    // Conditional back-edge: a trampoline runs the parameter
2520                    // copies so the not-taken path keeps the loop's live
2521                    // parameter registers intact.
2522                    let br_block_idx = self.blocks.len();
2523                    self.pending_trampolines.push(TrampolineReq {
2524                        block: br_block_idx,
2525                        slot: BranchSlot::Single,
2526                        param_regs: loop_target.param_regs,
2527                        values: values.clone(),
2528                        header: loop_target.header,
2529                        offset,
2530                    });
2531                    self.finish_block(RegTerm::BrIf {
2532                        cond: cond.reg,
2533                        target_block: 0,
2534                        values,
2535                    });
2536                } else {
2537                    let br_block_idx = self.blocks.len();
2538                    self.pending_branches.push(PendingBranch {
2539                        block: br_block_idx,
2540                        frame_pos,
2541                        values: values.clone(),
2542                        slot: BranchSlot::Single,
2543                    });
2544                    self.finish_block(RegTerm::BrIf {
2545                        cond: cond.reg,
2546                        target_block: 0,
2547                        values,
2548                    });
2549                }
2550            }
2551            Instr::BrTable { targets, default } => {
2552                let index = self.pop_expect(offset, "br_table", ValType::Num(NumType::I32))?;
2553                // All targets must agree on branch arity and types (the
2554                // validator guarantees this); pop using the default target.
2555                let default_pos = self.label_position(offset, default)?;
2556                let (branch_types, _) = self.branch_types_at(default_pos);
2557                let mut values = Vec::with_capacity(branch_types.len());
2558                for &expected in branch_types.iter().rev() {
2559                    let found = self.pop_expect(offset, "br_table", expected)?;
2560                    values.push(found.reg);
2561                }
2562                values.reverse();
2563                // Resolve targets: loop headers are known immediately; other
2564                // frames get a pending entry per slot for back-patching at
2565                // their `end`.
2566                let br_block_idx = self.blocks.len();
2567                let mut target_blocks = Vec::with_capacity(targets.len() + 1);
2568                for (slot, target) in targets.iter().chain(core::iter::once(&default)).enumerate() {
2569                    let frame_pos = self.label_position(offset, *target)?;
2570                    let (_, loop_header) = self.branch_types_at(frame_pos);
2571                    match loop_header {
2572                        Some(loop_target) => {
2573                            target_blocks.push(0);
2574                            self.pending_trampolines.push(TrampolineReq {
2575                                block: br_block_idx,
2576                                slot: BranchSlot::Table(slot),
2577                                param_regs: loop_target.param_regs,
2578                                values: values.clone(),
2579                                header: loop_target.header,
2580                                offset,
2581                            });
2582                        }
2583                        None => {
2584                            target_blocks.push(0);
2585                            self.pending_branches.push(PendingBranch {
2586                                block: br_block_idx,
2587                                frame_pos,
2588                                values: values.clone(),
2589                                slot: BranchSlot::Table(slot),
2590                            });
2591                        }
2592                    }
2593                }
2594                let default_block = target_blocks.pop().expect("default included above");
2595                self.finish_block(RegTerm::BrTable {
2596                    index: index.reg,
2597                    targets: target_blocks,
2598                    default: default_block,
2599                    values,
2600                });
2601                self.set_unreachable();
2602            }
2603            Instr::BrOnNull(label) => {
2604                let value = self.pop_any(offset, "br_on_null")?;
2605                let frame_pos = self.label_position(offset, label)?;
2606                let (branch_types, loop_header) = self.branch_types_at(frame_pos);
2607                let mut values = Vec::with_capacity(branch_types.len());
2608                for &expected in branch_types.iter().rev() {
2609                    let found = self.pop_expect(offset, "br_on_null", expected)?;
2610                    values.push(found.reg);
2611                }
2612                values.reverse();
2613                // Not-taken path: branch values stay, plus the ref (non-null
2614                // on this path by definition).
2615                for (&reg, &ty) in values.iter().zip(branch_types.iter()) {
2616                    self.stack.push(RegValue { reg, ty });
2617                }
2618                self.stack.push(value);
2619                self.finish_cond_ref_branch(
2620                    offset,
2621                    frame_pos,
2622                    loop_header,
2623                    values,
2624                    value.reg,
2625                    RefBranchKind::OnNull,
2626                );
2627            }
2628            Instr::BrOnNonNull(label) => {
2629                let value = self.pop_any(offset, "br_on_non_null")?;
2630                let frame_pos = self.label_position(offset, label)?;
2631                let (branch_types, loop_header) = self.branch_types_at(frame_pos);
2632                // The label's last value is the forwarded non-null ref; only
2633                // the leading types come from the stack.
2634                let leading_types = match branch_types.split_last() {
2635                    Some((_, leading)) => leading,
2636                    None => &[][..],
2637                };
2638                let mut values = Vec::with_capacity(branch_types.len());
2639                for &expected in leading_types.iter().rev() {
2640                    let found = self.pop_expect(offset, "br_on_non_null", expected)?;
2641                    values.push(found.reg);
2642                }
2643                values.reverse();
2644                // Not-taken path: leading values stay; the ref is consumed.
2645                for (&reg, &ty) in values.iter().zip(leading_types.iter()) {
2646                    self.stack.push(RegValue { reg, ty });
2647                }
2648                // Taken path forwards the ref as the last branch value.
2649                values.push(value.reg);
2650                self.finish_cond_ref_branch(
2651                    offset,
2652                    frame_pos,
2653                    loop_header,
2654                    values,
2655                    value.reg,
2656                    RefBranchKind::OnNonNull,
2657                );
2658            }
2659            Instr::Return => {
2660                let values = self.pop_results(offset)?;
2661                self.finish_block(RegTerm::Return { values });
2662                // Code after `return` is unreachable, but lowering continues:
2663                // instructions up to the function's final `end` must still be
2664                // processed (under polymorphic stack discipline).
2665                self.set_unreachable();
2666            }
2667            Instr::CallRef(type_idx) => {
2668                let Some(ty) = self.tables.types.get(type_idx.0 as usize) else {
2669                    return Err(LowerError {
2670                        offset,
2671                        function: Some(self.func_idx),
2672                        kind: LowerErrorKind::InvalidType {
2673                            type_idx: type_idx.0,
2674                        },
2675                    });
2676                };
2677                // Stack order: [args..., funcref] — the reference is on top.
2678                let func = self.pop_any(offset, "call_ref")?;
2679                let mut args = Vec::with_capacity(ty.params.len());
2680                for &expected in ty.params.iter().rev() {
2681                    let found = self.pop_expect(offset, "call_ref", expected)?;
2682                    args.push(found.reg);
2683                }
2684                args.reverse();
2685                let mut results = Vec::with_capacity(ty.results.len());
2686                for &ty in ty.results.iter() {
2687                    let dst = self.alloc_reg(ty);
2688                    self.stack.push(RegValue { reg: dst, ty });
2689                    results.push(dst);
2690                }
2691                self.emit(
2692                    offset,
2693                    RegOp::CallRef {
2694                        type_idx,
2695                        func: func.reg,
2696                        args,
2697                        results,
2698                    },
2699                );
2700            }
2701            Instr::Call(func) => {
2702                let Some(callee_ty) = self.tables.func_types.get(func.0 as usize) else {
2703                    return Err(LowerError {
2704                        offset,
2705                        function: Some(self.func_idx),
2706                        kind: LowerErrorKind::InvalidFunction { func: func.0 },
2707                    });
2708                };
2709                let mut args = Vec::with_capacity(callee_ty.params.len());
2710                for &expected in callee_ty.params.iter().rev() {
2711                    let found = self.pop_expect(offset, "call", expected)?;
2712                    args.push(found.reg);
2713                }
2714                args.reverse();
2715                let mut results = Vec::with_capacity(callee_ty.results.len());
2716                for &ty in callee_ty.results.iter() {
2717                    let dst = self.alloc_reg(ty);
2718                    self.stack.push(RegValue { reg: dst, ty });
2719                    results.push(dst);
2720                }
2721                self.emit(
2722                    offset,
2723                    RegOp::Call {
2724                        func,
2725                        args,
2726                        results,
2727                    },
2728                );
2729            }
2730            Instr::GlobalGet(global) => {
2731                let ty = self.global_type(offset, global)?;
2732                let dst = self.alloc_reg(ty);
2733                self.stack.push(RegValue { reg: dst, ty });
2734                self.emit(offset, RegOp::GlobalGet { dst, global });
2735            }
2736            Instr::GlobalSet(global) => {
2737                let ty = self.global_type(offset, global)?;
2738                let value = self.pop_expect(offset, "global.set", ty)?;
2739                self.emit(
2740                    offset,
2741                    RegOp::GlobalSet {
2742                        global,
2743                        value: value.reg,
2744                    },
2745                );
2746            }
2747            Instr::MemorySize(memory) => {
2748                let ty = ValType::Num(NumType::I32);
2749                let dst = self.alloc_reg(ty);
2750                self.stack.push(RegValue { reg: dst, ty });
2751                self.emit(offset, RegOp::MemorySize { dst, memory });
2752            }
2753            Instr::MemoryGrow(memory) => {
2754                let delta = self.pop_expect(offset, "memory.grow", ValType::Num(NumType::I32))?;
2755                let ty = ValType::Num(NumType::I32);
2756                let dst = self.alloc_reg(ty);
2757                self.stack.push(RegValue { reg: dst, ty });
2758                self.emit(
2759                    offset,
2760                    RegOp::MemoryGrow {
2761                        dst,
2762                        memory,
2763                        delta: delta.reg,
2764                    },
2765                );
2766            }
2767            Instr::MemoryInit(data_idx, mem_idx) => {
2768                let count = self.pop_expect(offset, "memory.init", ValType::Num(NumType::I32))?;
2769                let src = self.pop_expect(offset, "memory.init", ValType::Num(NumType::I32))?;
2770                let dst = self.pop_expect(offset, "memory.init", ValType::Num(NumType::I32))?;
2771                self.emit(
2772                    offset,
2773                    RegOp::MemoryInit {
2774                        memory: mem_idx,
2775                        data: data_idx,
2776                        dst: dst.reg,
2777                        src: src.reg,
2778                        count: count.reg,
2779                    },
2780                );
2781            }
2782            Instr::DataDrop(data_idx) => {
2783                self.emit(offset, RegOp::DataDrop { data: data_idx });
2784            }
2785            Instr::MemoryCopy { dst, src } => {
2786                let count = self.pop_expect(offset, "memory.copy", ValType::Num(NumType::I32))?;
2787                let src_idx = self.pop_expect(offset, "memory.copy", ValType::Num(NumType::I32))?;
2788                let dst_idx = self.pop_expect(offset, "memory.copy", ValType::Num(NumType::I32))?;
2789                self.emit(
2790                    offset,
2791                    RegOp::MemoryCopy {
2792                        dst_memory: dst,
2793                        src_memory: src,
2794                        dst: dst_idx.reg,
2795                        src: src_idx.reg,
2796                        count: count.reg,
2797                    },
2798                );
2799            }
2800            Instr::MemoryFill(memory) => {
2801                let count = self.pop_expect(offset, "memory.fill", ValType::Num(NumType::I32))?;
2802                let value = self.pop_expect(offset, "memory.fill", ValType::Num(NumType::I32))?;
2803                let dst = self.pop_expect(offset, "memory.fill", ValType::Num(NumType::I32))?;
2804                self.emit(
2805                    offset,
2806                    RegOp::MemoryFill {
2807                        memory,
2808                        dst: dst.reg,
2809                        value: value.reg,
2810                        count: count.reg,
2811                    },
2812                );
2813            }
2814            Instr::CallIndirect {
2815                type_idx,
2816                table_idx,
2817            } => {
2818                let Some(ty) = self.tables.types.get(type_idx.0 as usize) else {
2819                    return Err(LowerError {
2820                        offset,
2821                        function: Some(self.func_idx),
2822                        kind: LowerErrorKind::InvalidType {
2823                            type_idx: type_idx.0,
2824                        },
2825                    });
2826                };
2827                let index = self.pop_expect(offset, "call_indirect", ValType::Num(NumType::I32))?;
2828                let mut args = Vec::with_capacity(ty.params.len());
2829                for &expected in ty.params.iter().rev() {
2830                    let found = self.pop_expect(offset, "call_indirect", expected)?;
2831                    args.push(found.reg);
2832                }
2833                args.reverse();
2834                let mut results = Vec::with_capacity(ty.results.len());
2835                for &result_ty in ty.results.iter() {
2836                    let dst = self.alloc_reg(result_ty);
2837                    self.stack.push(RegValue {
2838                        reg: dst,
2839                        ty: result_ty,
2840                    });
2841                    results.push(dst);
2842                }
2843                self.emit(
2844                    offset,
2845                    RegOp::CallIndirect {
2846                        type_idx,
2847                        table: table_idx,
2848                        index: index.reg,
2849                        args,
2850                        results,
2851                    },
2852                );
2853            }
2854            Instr::TableGet(table) => {
2855                let index = self.pop_expect(offset, "table.get", ValType::Num(NumType::I32))?;
2856                let ty = ValType::Ref(self.table_elem_type(offset, table)?);
2857                let dst = self.alloc_reg(ty);
2858                self.stack.push(RegValue { reg: dst, ty });
2859                self.emit(
2860                    offset,
2861                    RegOp::TableGet {
2862                        dst,
2863                        table,
2864                        index: index.reg,
2865                    },
2866                );
2867            }
2868            Instr::TableSet(table) => {
2869                let ty = ValType::Ref(self.table_elem_type(offset, table)?);
2870                let value = self.pop_expect(offset, "table.set", ty)?;
2871                let index = self.pop_expect(offset, "table.set", ValType::Num(NumType::I32))?;
2872                self.emit(
2873                    offset,
2874                    RegOp::TableSet {
2875                        table,
2876                        index: index.reg,
2877                        value: value.reg,
2878                    },
2879                );
2880            }
2881            Instr::TableSize(table) => {
2882                let ty = ValType::Num(NumType::I32);
2883                let dst = self.alloc_reg(ty);
2884                self.stack.push(RegValue { reg: dst, ty });
2885                self.emit(offset, RegOp::TableSize { dst, table });
2886            }
2887            Instr::TableGrow(table) => {
2888                let delta = self.pop_expect(offset, "table.grow", ValType::Num(NumType::I32))?;
2889                let elem_ty = ValType::Ref(self.table_elem_type(offset, table)?);
2890                let value = self.pop_expect(offset, "table.grow", elem_ty)?;
2891                let ty = ValType::Num(NumType::I32);
2892                let dst = self.alloc_reg(ty);
2893                self.stack.push(RegValue { reg: dst, ty });
2894                self.emit(
2895                    offset,
2896                    RegOp::TableGrow {
2897                        dst,
2898                        table,
2899                        value: value.reg,
2900                        delta: delta.reg,
2901                    },
2902                );
2903            }
2904            Instr::TableFill(table) => {
2905                let count = self.pop_expect(offset, "table.fill", ValType::Num(NumType::I32))?;
2906                let elem_ty = ValType::Ref(self.table_elem_type(offset, table)?);
2907                let value = self.pop_expect(offset, "table.fill", elem_ty)?;
2908                let dst_idx = self.pop_expect(offset, "table.fill", ValType::Num(NumType::I32))?;
2909                self.emit(
2910                    offset,
2911                    RegOp::TableFill {
2912                        table,
2913                        dst: dst_idx.reg,
2914                        value: value.reg,
2915                        count: count.reg,
2916                    },
2917                );
2918            }
2919            Instr::TableCopy { dst, src } => {
2920                let count = self.pop_expect(offset, "table.copy", ValType::Num(NumType::I32))?;
2921                let src_idx = self.pop_expect(offset, "table.copy", ValType::Num(NumType::I32))?;
2922                let dst_idx = self.pop_expect(offset, "table.copy", ValType::Num(NumType::I32))?;
2923                self.emit(
2924                    offset,
2925                    RegOp::TableCopy {
2926                        dst_table: dst,
2927                        src_table: src,
2928                        dst: dst_idx.reg,
2929                        src: src_idx.reg,
2930                        count: count.reg,
2931                    },
2932                );
2933            }
2934            Instr::TableInit {
2935                elem_idx,
2936                table_idx,
2937            } => {
2938                let count = self.pop_expect(offset, "table.init", ValType::Num(NumType::I32))?;
2939                let src = self.pop_expect(offset, "table.init", ValType::Num(NumType::I32))?;
2940                let dst = self.pop_expect(offset, "table.init", ValType::Num(NumType::I32))?;
2941                self.emit(
2942                    offset,
2943                    RegOp::TableInit {
2944                        table: table_idx,
2945                        elem: elem_idx,
2946                        dst: dst.reg,
2947                        src: src.reg,
2948                        count: count.reg,
2949                    },
2950                );
2951            }
2952            Instr::ElemDrop(elem) => {
2953                self.emit(offset, RegOp::ElemDrop { elem });
2954            }
2955            Instr::RefNull(ref_type) => {
2956                let ty = ValType::Ref(ref_type);
2957                let dst = self.alloc_reg(ty);
2958                self.stack.push(RegValue { reg: dst, ty });
2959                self.emit(offset, RegOp::RefNull { dst, ref_type });
2960            }
2961            Instr::RefFunc(func) => {
2962                // Lowered as the nullable funcref type; the validator has
2963                // already proven the precise (non-null) type.
2964                let ty = ValType::Ref(crate::types::RefType::FuncRef);
2965                let dst = self.alloc_reg(ty);
2966                self.stack.push(RegValue { reg: dst, ty });
2967                self.emit(offset, RegOp::RefFunc { dst, func });
2968            }
2969            Instr::RefIsNull => {
2970                let value = self.pop_any(offset, "ref.is_null")?;
2971                let ty = ValType::Num(NumType::I32);
2972                let dst = self.alloc_reg(ty);
2973                self.stack.push(RegValue { reg: dst, ty });
2974                self.emit(
2975                    offset,
2976                    RegOp::RefIsNull {
2977                        dst,
2978                        value: value.reg,
2979                    },
2980                );
2981            }
2982            Instr::RefAsNonNull => {
2983                let value = self.pop_any(offset, "ref.as_non_null")?;
2984                // In unreachable code the synthesized operand has a default
2985                // type; the result must still be a reference for downstream
2986                // consumers.
2987                let ty = match value.ty {
2988                    ValType::Ref(_) => value.ty,
2989                    _ => ValType::Ref(crate::types::RefType::FuncRef),
2990                };
2991                let dst = self.alloc_reg(ty);
2992                self.stack.push(RegValue { reg: dst, ty });
2993                self.emit(
2994                    offset,
2995                    RegOp::RefAsNonNull {
2996                        dst,
2997                        value: value.reg,
2998                    },
2999                );
3000            }
3001            Instr::V128Const(value) => {
3002                let ty = ValType::Vec(crate::types::VecType::V128);
3003                let dst = self.alloc_reg(ty);
3004                self.stack.push(RegValue { reg: dst, ty });
3005                self.emit(offset, RegOp::V128Const { dst, value });
3006            }
3007            Instr::I8x16Splat => self.lower_splat(offset, LaneShape::I8x16)?,
3008            Instr::I16x8Splat => self.lower_splat(offset, LaneShape::I16x8)?,
3009            Instr::I32x4Splat => self.lower_splat(offset, LaneShape::I32x4)?,
3010            Instr::I64x2Splat => self.lower_splat(offset, LaneShape::I64x2)?,
3011            Instr::F32x4Splat => self.lower_splat(offset, LaneShape::F32x4)?,
3012            Instr::F64x2Splat => self.lower_splat(offset, LaneShape::F64x2)?,
3013            Instr::I32x4ExtractLane(lane) => {
3014                self.lower_extract_lane(offset, LaneShape::I32x4, lane)?
3015            }
3016            Instr::F32x4ExtractLane(lane) => {
3017                self.lower_extract_lane(offset, LaneShape::F32x4, lane)?
3018            }
3019            Instr::I32x4ReplaceLane(lane) => {
3020                self.lower_replace_lane(offset, LaneShape::I32x4, lane)?
3021            }
3022            Instr::F32x4ReplaceLane(lane) => {
3023                self.lower_replace_lane(offset, LaneShape::F32x4, lane)?
3024            }
3025            Instr::V128Not => {
3026                let src = self.pop_expect(
3027                    offset,
3028                    "v128.not",
3029                    ValType::Vec(crate::types::VecType::V128),
3030                )?;
3031                let ty = ValType::Vec(crate::types::VecType::V128);
3032                let dst = self.alloc_reg(ty);
3033                self.stack.push(RegValue { reg: dst, ty });
3034                self.emit(offset, RegOp::V128Not { dst, src: src.reg });
3035            }
3036            Instr::V128And => {
3037                self.lower_v128_binary(offset, LaneShape::I8x16, V128BinaryKind::And)?
3038            }
3039            Instr::V128Or => {
3040                self.lower_v128_binary(offset, LaneShape::I8x16, V128BinaryKind::Or)?
3041            }
3042            Instr::V128Xor => {
3043                self.lower_v128_binary(offset, LaneShape::I8x16, V128BinaryKind::Xor)?
3044            }
3045            Instr::I8x16Add => {
3046                self.lower_v128_binary(offset, LaneShape::I8x16, V128BinaryKind::Add)?
3047            }
3048            Instr::I8x16Sub => {
3049                self.lower_v128_binary(offset, LaneShape::I8x16, V128BinaryKind::Sub)?
3050            }
3051            Instr::I16x8Add => {
3052                self.lower_v128_binary(offset, LaneShape::I16x8, V128BinaryKind::Add)?
3053            }
3054            Instr::I16x8Sub => {
3055                self.lower_v128_binary(offset, LaneShape::I16x8, V128BinaryKind::Sub)?
3056            }
3057            Instr::I32x4Add => {
3058                self.lower_v128_binary(offset, LaneShape::I32x4, V128BinaryKind::Add)?
3059            }
3060            Instr::I32x4Sub => {
3061                self.lower_v128_binary(offset, LaneShape::I32x4, V128BinaryKind::Sub)?
3062            }
3063            Instr::I32x4Mul => {
3064                self.lower_v128_binary(offset, LaneShape::I32x4, V128BinaryKind::Mul)?
3065            }
3066            Instr::I64x2Add => {
3067                self.lower_v128_binary(offset, LaneShape::I64x2, V128BinaryKind::Add)?
3068            }
3069            Instr::I64x2Sub => {
3070                self.lower_v128_binary(offset, LaneShape::I64x2, V128BinaryKind::Sub)?
3071            }
3072            Instr::F32x4Add => {
3073                self.lower_v128_binary(offset, LaneShape::F32x4, V128BinaryKind::Add)?
3074            }
3075            Instr::F32x4Sub => {
3076                self.lower_v128_binary(offset, LaneShape::F32x4, V128BinaryKind::Sub)?
3077            }
3078            Instr::F32x4Mul => {
3079                self.lower_v128_binary(offset, LaneShape::F32x4, V128BinaryKind::Mul)?
3080            }
3081            Instr::F32x4Div => {
3082                self.lower_v128_binary(offset, LaneShape::F32x4, V128BinaryKind::Div)?
3083            }
3084            Instr::F64x2Add => {
3085                self.lower_v128_binary(offset, LaneShape::F64x2, V128BinaryKind::Add)?
3086            }
3087            Instr::F64x2Sub => {
3088                self.lower_v128_binary(offset, LaneShape::F64x2, V128BinaryKind::Sub)?
3089            }
3090            Instr::F64x2Mul => {
3091                self.lower_v128_binary(offset, LaneShape::F64x2, V128BinaryKind::Mul)?
3092            }
3093            Instr::F64x2Div => {
3094                self.lower_v128_binary(offset, LaneShape::F64x2, V128BinaryKind::Div)?
3095            }
3096            instr => {
3097                if let Some(op) = unary_op(&instr) {
3098                    self.lower_unary_op(offset, op)?;
3099                } else if let Some(op) = binary_op(&instr) {
3100                    self.lower_binary_op(offset, op)?;
3101                } else if let Some((op, memarg)) = load_op(&instr) {
3102                    self.lower_load(offset, op, memarg)?;
3103                } else if let Some((op, memarg)) = store_op(&instr) {
3104                    self.lower_store(offset, op, memarg)?;
3105                } else {
3106                    return Err(LowerError {
3107                        offset,
3108                        function: Some(self.func_idx),
3109                        kind: LowerErrorKind::UnsupportedInstr {
3110                            op: instr_name(&instr),
3111                        },
3112                    });
3113                }
3114            }
3115        }
3116
3117        Ok(false)
3118    }
3119
3120    fn alloc_reg(&mut self, ty: ValType) -> Reg {
3121        let reg = Reg(self.reg_types.len() as u32);
3122        self.reg_types.push(ty);
3123        reg
3124    }
3125
3126    fn emit(&mut self, offset: ByteOffset, op: RegOp) {
3127        self.current_instrs.push(RegInstr { offset, op });
3128    }
3129
3130    fn pop_any(&mut self, offset: ByteOffset, op: &'static str) -> Result<RegValue, LowerError> {
3131        if self.at_frame_boundary() {
3132            if self.current_frame_unreachable() {
3133                // Polymorphic stack: synthesize an undefined register. The
3134                // surrounding code is unreachable, so the register is never
3135                // read at runtime.
3136                let ty = ValType::Num(NumType::I32);
3137                let reg = self.alloc_reg(ty);
3138                return Ok(RegValue { reg, ty });
3139            }
3140            return Err(LowerError {
3141                offset,
3142                function: Some(self.func_idx),
3143                kind: LowerErrorKind::StackUnderflow {
3144                    op,
3145                    expected: ValType::Num(NumType::I32),
3146                },
3147            });
3148        }
3149        Ok(self.stack.pop().expect("stack height checked"))
3150    }
3151
3152    /// Whether the operand stack is exactly at the current frame's entry
3153    /// height — pops below this point are frame-boundary pops.
3154    fn at_frame_boundary(&self) -> bool {
3155        let frame = self
3156            .label_stack
3157            .last()
3158            .expect("function frame is always present");
3159        self.stack.len() == frame.height
3160    }
3161
3162    fn current_frame_unreachable(&self) -> bool {
3163        self.label_stack
3164            .last()
3165            .expect("function frame is always present")
3166            .unreachable
3167    }
3168
3169    /// Resolve a branch label to a position in `label_stack`, or fail on an
3170    /// out-of-range label.
3171    fn label_position(&self, offset: ByteOffset, label: LabelIdx) -> Result<usize, LowerError> {
3172        let label_idx = label.0 as usize;
3173        if label_idx >= self.label_stack.len() {
3174            return Err(LowerError {
3175                offset,
3176                function: Some(self.func_idx),
3177                kind: LowerErrorKind::InvalidLabel { label: label.0 },
3178            });
3179        }
3180        Ok(self.label_stack.len() - 1 - label_idx)
3181    }
3182
3183    /// Enter a new control frame: consume the block type's parameters from
3184    /// the operand stack (recording their canonical registers), push the
3185    /// frame with its base height below the params, then push the params
3186    /// back as the frame's initial working stack.
3187    fn enter_frame(
3188        &mut self,
3189        offset: ByteOffset,
3190        op: &'static str,
3191        kind: FrameKind,
3192        block_type: BlockType,
3193    ) -> Result<(), LowerError> {
3194        let (param_types, result_types) = self.block_type_sig(offset, block_type)?;
3195        let mut param_regs = Vec::with_capacity(param_types.len());
3196        for &expected in param_types.iter().rev() {
3197            let found = self.pop_expect(offset, op, expected)?;
3198            param_regs.push(found.reg);
3199        }
3200        param_regs.reverse();
3201        let height = self.stack.len();
3202        for (&reg, &ty) in param_regs.iter().zip(param_types.iter()) {
3203            self.stack.push(RegValue { reg, ty });
3204        }
3205        self.label_stack.push(LabelFrame {
3206            label: LabelIdx(self.label_stack.len() as u32),
3207            kind,
3208            result_types,
3209            param_types,
3210            param_regs,
3211            height,
3212            unreachable: false,
3213        });
3214        Ok(())
3215    }
3216
3217    /// Resolve a block type to its (params, results) signature.
3218    fn block_type_sig(
3219        &self,
3220        offset: ByteOffset,
3221        block_type: BlockType,
3222    ) -> Result<(Vec<ValType>, Vec<ValType>), LowerError> {
3223        match block_type {
3224            BlockType::Empty => Ok((Vec::new(), Vec::new())),
3225            BlockType::Val(ty) => Ok((Vec::new(), alloc::vec![ty])),
3226            BlockType::TypeIdx(idx) => {
3227                let ty = self.tables.types.get(idx as usize).ok_or(LowerError {
3228                    offset,
3229                    function: Some(self.func_idx),
3230                    kind: LowerErrorKind::InvalidType { type_idx: idx },
3231                })?;
3232                Ok((ty.params.clone(), ty.results.clone()))
3233            }
3234        }
3235    }
3236
3237    /// Emit copy instructions delivering `srcs` into `dsts` (used for
3238    /// unconditional loop back-edges; conditional branches use trampolines).
3239    fn emit_copies(&mut self, offset: ByteOffset, dsts: &[Reg], srcs: &[Reg]) {
3240        for (&dst, &src) in dsts.iter().zip(srcs.iter()) {
3241            if dst != src {
3242                self.emit(offset, RegOp::Copy { dst, src });
3243            }
3244        }
3245    }
3246
3247    /// The value types a branch to the frame at `frame_pos` must carry, and
3248    /// the loop target details when the frame is a loop.
3249    fn branch_types_at(&self, frame_pos: usize) -> (Vec<ValType>, Option<LoopTarget>) {
3250        let frame = &self.label_stack[frame_pos];
3251        match frame.kind {
3252            FrameKind::Loop { header_block } => (
3253                frame.param_types.clone(),
3254                Some(LoopTarget {
3255                    header: header_block,
3256                    param_regs: frame.param_regs.clone(),
3257                }),
3258            ),
3259            _ => (frame.result_types.clone(), None),
3260        }
3261    }
3262
3263    /// Mark the current control frame unreachable: the stack is truncated to
3264    /// the frame's entry height and further pops become polymorphic.
3265    fn set_unreachable(&mut self) {
3266        let frame = self
3267            .label_stack
3268            .last_mut()
3269            .expect("function frame is always present");
3270        self.stack.truncate(frame.height);
3271        frame.unreachable = true;
3272    }
3273
3274    fn lower_binary_op(&mut self, offset: ByteOffset, op: BinaryOp) -> Result<(), LowerError> {
3275        let input = op.input_type();
3276        let output = op.result_type();
3277        let rhs = self.pop_expect(offset, op.name(), input)?;
3278        let lhs = self.pop_expect(offset, op.name(), input)?;
3279        let dst = self.alloc_reg(output);
3280        self.stack.push(RegValue {
3281            reg: dst,
3282            ty: output,
3283        });
3284        self.emit(
3285            offset,
3286            RegOp::Binary {
3287                op,
3288                dst,
3289                lhs: lhs.reg,
3290                rhs: rhs.reg,
3291            },
3292        );
3293        Ok(())
3294    }
3295
3296    fn lower_splat(&mut self, offset: ByteOffset, shape: LaneShape) -> Result<(), LowerError> {
3297        let src = self.pop_expect(offset, "splat", shape.scalar_type())?;
3298        let ty = ValType::Vec(crate::types::VecType::V128);
3299        let dst = self.alloc_reg(ty);
3300        self.stack.push(RegValue { reg: dst, ty });
3301        self.emit(
3302            offset,
3303            RegOp::V128Splat {
3304                dst,
3305                shape,
3306                src: src.reg,
3307            },
3308        );
3309        Ok(())
3310    }
3311
3312    fn lower_extract_lane(
3313        &mut self,
3314        offset: ByteOffset,
3315        shape: LaneShape,
3316        lane: u8,
3317    ) -> Result<(), LowerError> {
3318        let src = self.pop_expect(
3319            offset,
3320            "extract_lane",
3321            ValType::Vec(crate::types::VecType::V128),
3322        )?;
3323        let ty = shape.scalar_type();
3324        let dst = self.alloc_reg(ty);
3325        self.stack.push(RegValue { reg: dst, ty });
3326        self.emit(
3327            offset,
3328            RegOp::V128ExtractLane {
3329                dst,
3330                shape,
3331                src: src.reg,
3332                lane,
3333            },
3334        );
3335        Ok(())
3336    }
3337
3338    fn lower_replace_lane(
3339        &mut self,
3340        offset: ByteOffset,
3341        shape: LaneShape,
3342        lane: u8,
3343    ) -> Result<(), LowerError> {
3344        let scalar = self.pop_expect(offset, "replace_lane", shape.scalar_type())?;
3345        let vec = self.pop_expect(
3346            offset,
3347            "replace_lane",
3348            ValType::Vec(crate::types::VecType::V128),
3349        )?;
3350        let ty = ValType::Vec(crate::types::VecType::V128);
3351        let dst = self.alloc_reg(ty);
3352        self.stack.push(RegValue { reg: dst, ty });
3353        self.emit(
3354            offset,
3355            RegOp::V128ReplaceLane {
3356                dst,
3357                shape,
3358                vec: vec.reg,
3359                scalar: scalar.reg,
3360                lane,
3361            },
3362        );
3363        Ok(())
3364    }
3365
3366    fn lower_v128_binary(
3367        &mut self,
3368        offset: ByteOffset,
3369        shape: LaneShape,
3370        kind: V128BinaryKind,
3371    ) -> Result<(), LowerError> {
3372        let v128 = ValType::Vec(crate::types::VecType::V128);
3373        let rhs = self.pop_expect(offset, "simd.binary", v128)?;
3374        let lhs = self.pop_expect(offset, "simd.binary", v128)?;
3375        let dst = self.alloc_reg(v128);
3376        self.stack.push(RegValue { reg: dst, ty: v128 });
3377        self.emit(
3378            offset,
3379            RegOp::V128Binary {
3380                shape,
3381                kind,
3382                dst,
3383                lhs: lhs.reg,
3384                rhs: rhs.reg,
3385            },
3386        );
3387        Ok(())
3388    }
3389
3390    fn global_type(&self, offset: ByteOffset, global: GlobalIdx) -> Result<ValType, LowerError> {
3391        self.tables
3392            .global_types
3393            .get(global.0 as usize)
3394            .copied()
3395            .ok_or(LowerError {
3396                offset,
3397                function: Some(self.func_idx),
3398                kind: LowerErrorKind::InvalidGlobal { global: global.0 },
3399            })
3400    }
3401
3402    fn table_elem_type(
3403        &self,
3404        offset: ByteOffset,
3405        table: TableIdx,
3406    ) -> Result<crate::types::RefType, LowerError> {
3407        self.tables
3408            .table_elem_types
3409            .get(table.0 as usize)
3410            .copied()
3411            .ok_or(LowerError {
3412                offset,
3413                function: Some(self.func_idx),
3414                kind: LowerErrorKind::InvalidTable { table: table.0 },
3415            })
3416    }
3417
3418    fn lower_load(
3419        &mut self,
3420        offset: ByteOffset,
3421        op: LoadOp,
3422        memarg: MemArg,
3423    ) -> Result<(), LowerError> {
3424        let addr = self.pop_expect(offset, "load", ValType::Num(NumType::I32))?;
3425        let ty = op.result_type();
3426        let dst = self.alloc_reg(ty);
3427        self.stack.push(RegValue { reg: dst, ty });
3428        self.emit(
3429            offset,
3430            RegOp::Load {
3431                op,
3432                dst,
3433                addr: addr.reg,
3434                memarg,
3435            },
3436        );
3437        Ok(())
3438    }
3439
3440    fn lower_store(
3441        &mut self,
3442        offset: ByteOffset,
3443        op: StoreOp,
3444        memarg: MemArg,
3445    ) -> Result<(), LowerError> {
3446        let value = self.pop_expect(offset, "store", op.value_type())?;
3447        let addr = self.pop_expect(offset, "store", ValType::Num(NumType::I32))?;
3448        self.emit(
3449            offset,
3450            RegOp::Store {
3451                op,
3452                addr: addr.reg,
3453                value: value.reg,
3454                memarg,
3455            },
3456        );
3457        Ok(())
3458    }
3459
3460    fn lower_unary_op(&mut self, offset: ByteOffset, op: UnaryOp) -> Result<(), LowerError> {
3461        let input = op.input_type();
3462        let output = op.result_type();
3463        let value = self.pop_expect(offset, op.name(), input)?;
3464        let dst = self.alloc_reg(output);
3465        self.stack.push(RegValue {
3466            reg: dst,
3467            ty: output,
3468        });
3469        self.emit(
3470            offset,
3471            RegOp::Unary {
3472                op,
3473                dst,
3474                value: value.reg,
3475            },
3476        );
3477        Ok(())
3478    }
3479
3480    fn pop_expect(
3481        &mut self,
3482        offset: ByteOffset,
3483        op: &'static str,
3484        expected: ValType,
3485    ) -> Result<RegValue, LowerError> {
3486        if self.at_frame_boundary() {
3487            if self.current_frame_unreachable() {
3488                // Polymorphic stack: synthesize an undefined register of the
3489                // expected type.
3490                let reg = self.alloc_reg(expected);
3491                return Ok(RegValue { reg, ty: expected });
3492            }
3493            return Err(LowerError {
3494                offset,
3495                function: Some(self.func_idx),
3496                kind: LowerErrorKind::StackUnderflow { op, expected },
3497            });
3498        }
3499
3500        let found = self.stack.pop().expect("stack height checked");
3501
3502        // In unreachable code every value is a polymorphic undef; precise
3503        // types were proven by the validator, and the undef registers
3504        // lowering synthesizes cannot represent bottom.
3505        if found.ty != expected
3506            && !ref_compatible(found.ty, expected)
3507            && !self.current_frame_unreachable()
3508        {
3509            return Err(LowerError {
3510                offset,
3511                function: Some(self.func_idx),
3512                kind: LowerErrorKind::TypeMismatch {
3513                    op,
3514                    expected,
3515                    found: found.ty,
3516                },
3517            });
3518        }
3519
3520        Ok(found)
3521    }
3522
3523    fn pop_results(&mut self, offset: ByteOffset) -> Result<Vec<Reg>, LowerError> {
3524        let results = self.results.clone();
3525        let mut values = Vec::with_capacity(results.len());
3526        for &expected in results.iter().rev() {
3527            let found = self.pop_expect(offset, "function end", expected)?;
3528            values.push(found.reg);
3529        }
3530        values.reverse();
3531        Ok(values)
3532    }
3533}
3534
3535fn load_op(instr: &Instr) -> Option<(LoadOp, MemArg)> {
3536    let (op, memarg) = match *instr {
3537        Instr::I32Load(memarg) => (LoadOp::I32, memarg),
3538        Instr::I64Load(memarg) => (LoadOp::I64, memarg),
3539        Instr::F32Load(memarg) => (LoadOp::F32, memarg),
3540        Instr::F64Load(memarg) => (LoadOp::F64, memarg),
3541        Instr::I32Load8S(memarg) => (LoadOp::I32Load8S, memarg),
3542        Instr::I32Load8U(memarg) => (LoadOp::I32Load8U, memarg),
3543        Instr::I32Load16S(memarg) => (LoadOp::I32Load16S, memarg),
3544        Instr::I32Load16U(memarg) => (LoadOp::I32Load16U, memarg),
3545        Instr::I64Load8S(memarg) => (LoadOp::I64Load8S, memarg),
3546        Instr::I64Load8U(memarg) => (LoadOp::I64Load8U, memarg),
3547        Instr::I64Load16S(memarg) => (LoadOp::I64Load16S, memarg),
3548        Instr::I64Load16U(memarg) => (LoadOp::I64Load16U, memarg),
3549        Instr::I64Load32S(memarg) => (LoadOp::I64Load32S, memarg),
3550        Instr::I64Load32U(memarg) => (LoadOp::I64Load32U, memarg),
3551        Instr::V128Load(memarg) => (LoadOp::V128, memarg),
3552        _ => return None,
3553    };
3554    Some((op, memarg))
3555}
3556
3557fn store_op(instr: &Instr) -> Option<(StoreOp, MemArg)> {
3558    let (op, memarg) = match *instr {
3559        Instr::I32Store(memarg) => (StoreOp::I32, memarg),
3560        Instr::I64Store(memarg) => (StoreOp::I64, memarg),
3561        Instr::F32Store(memarg) => (StoreOp::F32, memarg),
3562        Instr::F64Store(memarg) => (StoreOp::F64, memarg),
3563        Instr::I32Store8(memarg) => (StoreOp::I32Store8, memarg),
3564        Instr::I32Store16(memarg) => (StoreOp::I32Store16, memarg),
3565        Instr::I64Store8(memarg) => (StoreOp::I64Store8, memarg),
3566        Instr::I64Store16(memarg) => (StoreOp::I64Store16, memarg),
3567        Instr::I64Store32(memarg) => (StoreOp::I64Store32, memarg),
3568        Instr::V128Store(memarg) => (StoreOp::V128, memarg),
3569        _ => return None,
3570    };
3571    Some((op, memarg))
3572}
3573
3574fn unary_op(instr: &Instr) -> Option<UnaryOp> {
3575    match instr {
3576        Instr::I32Clz => Some(UnaryOp::I32Clz),
3577        Instr::I32Ctz => Some(UnaryOp::I32Ctz),
3578        Instr::I32Popcnt => Some(UnaryOp::I32Popcnt),
3579        Instr::I32Eqz => Some(UnaryOp::I32Eqz),
3580        Instr::I32WrapI64 => Some(UnaryOp::I32WrapI64),
3581        Instr::I32Extend8S => Some(UnaryOp::I32Extend8S),
3582        Instr::I32Extend16S => Some(UnaryOp::I32Extend16S),
3583        Instr::I32TruncF32S => Some(UnaryOp::I32TruncF32S),
3584        Instr::I32TruncF32U => Some(UnaryOp::I32TruncF32U),
3585        Instr::I32TruncF64S => Some(UnaryOp::I32TruncF64S),
3586        Instr::I32TruncF64U => Some(UnaryOp::I32TruncF64U),
3587        Instr::F32ConvertI32S => Some(UnaryOp::F32ConvertI32S),
3588        Instr::F32ConvertI32U => Some(UnaryOp::F32ConvertI32U),
3589        Instr::F64ConvertI32S => Some(UnaryOp::F64ConvertI32S),
3590        Instr::F64ConvertI32U => Some(UnaryOp::F64ConvertI32U),
3591        Instr::F32Neg => Some(UnaryOp::F32Neg),
3592        Instr::F32Abs => Some(UnaryOp::F32Abs),
3593        Instr::F32Sqrt => Some(UnaryOp::F32Sqrt),
3594        Instr::F32Ceil => Some(UnaryOp::F32Ceil),
3595        Instr::F32Floor => Some(UnaryOp::F32Floor),
3596        Instr::F32Trunc => Some(UnaryOp::F32Trunc),
3597        Instr::F32Nearest => Some(UnaryOp::F32Nearest),
3598        Instr::I64Clz => Some(UnaryOp::I64Clz),
3599        Instr::I64Ctz => Some(UnaryOp::I64Ctz),
3600        Instr::I64Popcnt => Some(UnaryOp::I64Popcnt),
3601        Instr::I64Eqz => Some(UnaryOp::I64Eqz),
3602        Instr::I64ExtendI32S => Some(UnaryOp::I64ExtendI32S),
3603        Instr::I64ExtendI32U => Some(UnaryOp::I64ExtendI32U),
3604        Instr::I64Extend8S => Some(UnaryOp::I64Extend8S),
3605        Instr::I64Extend16S => Some(UnaryOp::I64Extend16S),
3606        Instr::I64Extend32S => Some(UnaryOp::I64Extend32S),
3607        Instr::I64TruncF32S => Some(UnaryOp::I64TruncF32S),
3608        Instr::I64TruncF32U => Some(UnaryOp::I64TruncF32U),
3609        Instr::I64TruncF64S => Some(UnaryOp::I64TruncF64S),
3610        Instr::I64TruncF64U => Some(UnaryOp::I64TruncF64U),
3611        Instr::F32ConvertI64S => Some(UnaryOp::F32ConvertI64S),
3612        Instr::F32ConvertI64U => Some(UnaryOp::F32ConvertI64U),
3613        Instr::F64ConvertI64S => Some(UnaryOp::F64ConvertI64S),
3614        Instr::F64ConvertI64U => Some(UnaryOp::F64ConvertI64U),
3615        Instr::F64Neg => Some(UnaryOp::F64Neg),
3616        Instr::F64Abs => Some(UnaryOp::F64Abs),
3617        Instr::F64Sqrt => Some(UnaryOp::F64Sqrt),
3618        Instr::F64Ceil => Some(UnaryOp::F64Ceil),
3619        Instr::F64Floor => Some(UnaryOp::F64Floor),
3620        Instr::F64Trunc => Some(UnaryOp::F64Trunc),
3621        Instr::F64Nearest => Some(UnaryOp::F64Nearest),
3622        Instr::F32DemoteF64 => Some(UnaryOp::F32DemoteF64),
3623        Instr::F64PromoteF32 => Some(UnaryOp::F64PromoteF32),
3624        Instr::I32ReinterpretF32 => Some(UnaryOp::I32ReinterpretF32),
3625        Instr::F32ReinterpretI32 => Some(UnaryOp::F32ReinterpretI32),
3626        Instr::I64ReinterpretF64 => Some(UnaryOp::I64ReinterpretF64),
3627        Instr::F64ReinterpretI64 => Some(UnaryOp::F64ReinterpretI64),
3628        Instr::I32TruncSatF32S => Some(UnaryOp::I32TruncSatF32S),
3629        Instr::I32TruncSatF32U => Some(UnaryOp::I32TruncSatF32U),
3630        Instr::I32TruncSatF64S => Some(UnaryOp::I32TruncSatF64S),
3631        Instr::I32TruncSatF64U => Some(UnaryOp::I32TruncSatF64U),
3632        Instr::I64TruncSatF32S => Some(UnaryOp::I64TruncSatF32S),
3633        Instr::I64TruncSatF32U => Some(UnaryOp::I64TruncSatF32U),
3634        Instr::I64TruncSatF64S => Some(UnaryOp::I64TruncSatF64S),
3635        Instr::I64TruncSatF64U => Some(UnaryOp::I64TruncSatF64U),
3636        _ => None,
3637    }
3638}
3639
3640fn binary_op(instr: &Instr) -> Option<BinaryOp> {
3641    match instr {
3642        Instr::I32Add => Some(BinaryOp::I32Add),
3643        Instr::I32Sub => Some(BinaryOp::I32Sub),
3644        Instr::I32Mul => Some(BinaryOp::I32Mul),
3645        Instr::I32DivS => Some(BinaryOp::I32DivS),
3646        Instr::I32DivU => Some(BinaryOp::I32DivU),
3647        Instr::I32RemS => Some(BinaryOp::I32RemS),
3648        Instr::I32RemU => Some(BinaryOp::I32RemU),
3649        Instr::I32And => Some(BinaryOp::I32And),
3650        Instr::I32Or => Some(BinaryOp::I32Or),
3651        Instr::I32Xor => Some(BinaryOp::I32Xor),
3652        Instr::I32Shl => Some(BinaryOp::I32Shl),
3653        Instr::I32ShrS => Some(BinaryOp::I32ShrS),
3654        Instr::I32ShrU => Some(BinaryOp::I32ShrU),
3655        Instr::I32Rotl => Some(BinaryOp::I32Rotl),
3656        Instr::I32Rotr => Some(BinaryOp::I32Rotr),
3657        Instr::I32Eq => Some(BinaryOp::I32Eq),
3658        Instr::I32Ne => Some(BinaryOp::I32Ne),
3659        Instr::I32LtS => Some(BinaryOp::I32LtS),
3660        Instr::I32LtU => Some(BinaryOp::I32LtU),
3661        Instr::I32GtS => Some(BinaryOp::I32GtS),
3662        Instr::I32GtU => Some(BinaryOp::I32GtU),
3663        Instr::I32LeS => Some(BinaryOp::I32LeS),
3664        Instr::I32LeU => Some(BinaryOp::I32LeU),
3665        Instr::I32GeS => Some(BinaryOp::I32GeS),
3666        Instr::I32GeU => Some(BinaryOp::I32GeU),
3667        Instr::I64Add => Some(BinaryOp::I64Add),
3668        Instr::I64Sub => Some(BinaryOp::I64Sub),
3669        Instr::I64Mul => Some(BinaryOp::I64Mul),
3670        Instr::I64DivS => Some(BinaryOp::I64DivS),
3671        Instr::I64DivU => Some(BinaryOp::I64DivU),
3672        Instr::I64RemS => Some(BinaryOp::I64RemS),
3673        Instr::I64RemU => Some(BinaryOp::I64RemU),
3674        Instr::I64And => Some(BinaryOp::I64And),
3675        Instr::I64Or => Some(BinaryOp::I64Or),
3676        Instr::I64Xor => Some(BinaryOp::I64Xor),
3677        Instr::I64Shl => Some(BinaryOp::I64Shl),
3678        Instr::I64ShrS => Some(BinaryOp::I64ShrS),
3679        Instr::I64ShrU => Some(BinaryOp::I64ShrU),
3680        Instr::I64Rotl => Some(BinaryOp::I64Rotl),
3681        Instr::I64Rotr => Some(BinaryOp::I64Rotr),
3682        Instr::I64Eq => Some(BinaryOp::I64Eq),
3683        Instr::I64Ne => Some(BinaryOp::I64Ne),
3684        Instr::I64LtS => Some(BinaryOp::I64LtS),
3685        Instr::I64LtU => Some(BinaryOp::I64LtU),
3686        Instr::I64GtS => Some(BinaryOp::I64GtS),
3687        Instr::I64GtU => Some(BinaryOp::I64GtU),
3688        Instr::I64LeS => Some(BinaryOp::I64LeS),
3689        Instr::I64LeU => Some(BinaryOp::I64LeU),
3690        Instr::I64GeS => Some(BinaryOp::I64GeS),
3691        Instr::I64GeU => Some(BinaryOp::I64GeU),
3692        Instr::F32Add => Some(BinaryOp::F32Add),
3693        Instr::F32Copysign => Some(BinaryOp::F32Copysign),
3694        Instr::F64Copysign => Some(BinaryOp::F64Copysign),
3695        Instr::F32Sub => Some(BinaryOp::F32Sub),
3696        Instr::F32Mul => Some(BinaryOp::F32Mul),
3697        Instr::F32Div => Some(BinaryOp::F32Div),
3698        Instr::F32Min => Some(BinaryOp::F32Min),
3699        Instr::F32Max => Some(BinaryOp::F32Max),
3700        Instr::F64Add => Some(BinaryOp::F64Add),
3701        Instr::F64Sub => Some(BinaryOp::F64Sub),
3702        Instr::F64Mul => Some(BinaryOp::F64Mul),
3703        Instr::F64Div => Some(BinaryOp::F64Div),
3704        Instr::F64Min => Some(BinaryOp::F64Min),
3705        Instr::F64Max => Some(BinaryOp::F64Max),
3706        Instr::F32Eq => Some(BinaryOp::F32Eq),
3707        Instr::F32Ne => Some(BinaryOp::F32Ne),
3708        Instr::F32Lt => Some(BinaryOp::F32Lt),
3709        Instr::F32Gt => Some(BinaryOp::F32Gt),
3710        Instr::F32Le => Some(BinaryOp::F32Le),
3711        Instr::F32Ge => Some(BinaryOp::F32Ge),
3712        Instr::F64Eq => Some(BinaryOp::F64Eq),
3713        Instr::F64Ne => Some(BinaryOp::F64Ne),
3714        Instr::F64Lt => Some(BinaryOp::F64Lt),
3715        Instr::F64Gt => Some(BinaryOp::F64Gt),
3716        Instr::F64Le => Some(BinaryOp::F64Le),
3717        Instr::F64Ge => Some(BinaryOp::F64Ge),
3718        _ => None,
3719    }
3720}
3721
3722fn instr_name(instr: &Instr) -> &'static str {
3723    match instr {
3724        Instr::Unreachable => "unreachable",
3725        Instr::Nop => "nop",
3726        Instr::Block(_) => "block",
3727        Instr::Loop(_) => "loop",
3728        Instr::If(_) => "if",
3729        Instr::Else => "else",
3730        Instr::Br(_) => "br",
3731        Instr::BrIf(_) => "br_if",
3732        Instr::BrTable { .. } => "br_table",
3733        Instr::Return => "return",
3734        Instr::Call(_) => "call",
3735        Instr::CallIndirect { .. } => "call_indirect",
3736        Instr::LocalSet(_) => "local.set",
3737        Instr::LocalTee(_) => "local.tee",
3738        Instr::GlobalGet(_) => "global.get",
3739        Instr::GlobalSet(_) => "global.set",
3740        Instr::TableGet(_) => "table.get",
3741        Instr::TableSet(_) => "table.set",
3742        Instr::I64Add => "i64.add",
3743        _ => "instruction",
3744    }
3745}
3746
3747#[cfg(test)]
3748mod tests {
3749    use alloc::vec;
3750
3751    use super::*;
3752    use crate::binary::module::Module;
3753
3754    #[test]
3755    fn lower_simple_add_fixture_to_register_ir() {
3756        let bytes = baedeker_testdata::fixture_bytes("add");
3757        let module = Module::decode(&bytes).unwrap();
3758        let reg_module = module.lower().unwrap();
3759
3760        assert_eq!(reg_module.funcs.len(), 1);
3761        let func = &reg_module.funcs[0];
3762        assert_eq!(
3763            func.params,
3764            vec![ValType::Num(NumType::I32), ValType::Num(NumType::I32)]
3765        );
3766        assert_eq!(func.results, vec![ValType::Num(NumType::I32)]);
3767        assert_eq!(func.reg_types, vec![ValType::Num(NumType::I32); 3]);
3768        assert_eq!(
3769            func.blocks[0]
3770                .instrs
3771                .iter()
3772                .map(|instr| &instr.op)
3773                .collect::<Vec<_>>(),
3774            vec![
3775                &RegOp::LocalGet {
3776                    dst: Reg(0),
3777                    local: LocalIdx(1),
3778                },
3779                &RegOp::LocalGet {
3780                    dst: Reg(1),
3781                    local: LocalIdx(0),
3782                },
3783                &RegOp::Binary {
3784                    op: BinaryOp::I32Add,
3785                    dst: Reg(2),
3786                    lhs: Reg(0),
3787                    rhs: Reg(1),
3788                },
3789                // Return checked via blocks[0].term
3790            ]
3791        );
3792    }
3793
3794    #[test]
3795    fn lower_block_and_return() {
3796        let bytes = [
3797            0x00, 0x61, 0x73, 0x6d, // magic
3798            0x01, 0x00, 0x00, 0x00, // version
3799            0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // type: [] -> []
3800            0x03, 0x02, 0x01, 0x00, // function type 0
3801            0x0a, 0x07, 0x01, 0x05, 0x00, // code body header
3802            0x02, 0x40, 0x0b, 0x0b, // block end end
3803        ];
3804        let module = Module::decode(&bytes).unwrap();
3805        let reg_module = module.lower().unwrap();
3806        let func = &reg_module.funcs[0];
3807        // Body-end fallthrough into a continuation block ending in Return.
3808        assert_eq!(func.blocks.len(), 5);
3809        assert!(matches!(func.blocks[0].term, RegTerm::Fallthrough));
3810        assert!(matches!(
3811            func.blocks[func.blocks.len() - 2].term,
3812            RegTerm::Fallthrough
3813        ));
3814        assert!(matches!(
3815            func.blocks.last().unwrap().term,
3816            RegTerm::Return { .. }
3817        ));
3818    }
3819
3820    #[test]
3821    fn execute_lowered_add_fixture() {
3822        let bytes = baedeker_testdata::fixture_bytes("add");
3823        let module = Module::decode(&bytes).unwrap();
3824        let reg_module = module.lower().unwrap();
3825
3826        let result = crate::runtime::execute_func(
3827            &reg_module.funcs[0],
3828            &[
3829                crate::runtime::Value::I32(20),
3830                crate::runtime::Value::I32(22),
3831            ],
3832        )
3833        .unwrap();
3834
3835        assert_eq!(result, vec![crate::runtime::Value::I32(42)]);
3836    }
3837
3838    #[test]
3839    fn lower_local_set_temp_storage() {
3840        let module = Module::decode(local_set_temp_module()).unwrap();
3841        let reg_module = module.lower().unwrap();
3842        let func = &reg_module.funcs[0];
3843
3844        assert_eq!(func.reg_types, vec![ValType::Num(NumType::I32); 4]);
3845        assert_eq!(
3846            func.blocks[0]
3847                .instrs
3848                .iter()
3849                .map(|instr| &instr.op)
3850                .collect::<Vec<_>>(),
3851            vec![
3852                &RegOp::I32Const {
3853                    dst: Reg(0),
3854                    value: 40,
3855                },
3856                &RegOp::LocalSet {
3857                    local: LocalIdx(0),
3858                    value: Reg(0),
3859                },
3860                &RegOp::LocalGet {
3861                    dst: Reg(1),
3862                    local: LocalIdx(0),
3863                },
3864                &RegOp::I32Const {
3865                    dst: Reg(2),
3866                    value: 2,
3867                },
3868                &RegOp::Binary {
3869                    op: BinaryOp::I32Add,
3870                    dst: Reg(3),
3871                    lhs: Reg(1),
3872                    rhs: Reg(2),
3873                },
3874                // Return checked via blocks[0].term
3875            ]
3876        );
3877    }
3878
3879    #[test]
3880    fn execute_local_set_temp_storage() {
3881        let module = Module::decode(local_set_temp_module()).unwrap();
3882        let reg_module = module.lower().unwrap();
3883
3884        let result = crate::runtime::execute_func(&reg_module.funcs[0], &[]).unwrap();
3885
3886        assert_eq!(result, vec![crate::runtime::Value::I32(42)]);
3887    }
3888
3889    #[test]
3890    fn lower_local_tee_keeps_value_on_stack() {
3891        let module = Module::decode(local_tee_stack_module()).unwrap();
3892        let reg_module = module.lower().unwrap();
3893        let func = &reg_module.funcs[0];
3894
3895        assert_eq!(func.reg_types, vec![ValType::Num(NumType::I32); 3]);
3896        assert_eq!(
3897            func.blocks[0]
3898                .instrs
3899                .iter()
3900                .map(|instr| &instr.op)
3901                .collect::<Vec<_>>(),
3902            vec![
3903                &RegOp::I32Const {
3904                    dst: Reg(0),
3905                    value: 40,
3906                },
3907                &RegOp::LocalTee {
3908                    local: LocalIdx(0),
3909                    value: Reg(0),
3910                },
3911                &RegOp::I32Const {
3912                    dst: Reg(1),
3913                    value: 2,
3914                },
3915                &RegOp::Binary {
3916                    op: BinaryOp::I32Add,
3917                    dst: Reg(2),
3918                    lhs: Reg(0),
3919                    rhs: Reg(1),
3920                },
3921                // Return checked via blocks[0].term
3922            ]
3923        );
3924    }
3925
3926    #[test]
3927    fn execute_local_tee_stack_value() {
3928        let module = Module::decode(local_tee_stack_module()).unwrap();
3929        let reg_module = module.lower().unwrap();
3930
3931        let result = crate::runtime::execute_func(&reg_module.funcs[0], &[]).unwrap();
3932
3933        assert_eq!(result, vec![crate::runtime::Value::I32(42)]);
3934    }
3935
3936    fn local_set_temp_module() -> &'static [u8] {
3937        &[
3938            0x00, 0x61, 0x73, 0x6d, // magic
3939            0x01, 0x00, 0x00, 0x00, // version
3940            0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, // type: [] -> [i32]
3941            0x03, 0x02, 0x01, 0x00, // function type 0
3942            0x0a, 0x0f, 0x01, 0x0d, 0x01, 0x01, 0x7f, // one i32 local
3943            0x41, 0x28, // i32.const 40
3944            0x21, 0x00, // local.set 0
3945            0x20, 0x00, // local.get 0
3946            0x41, 0x02, // i32.const 2
3947            0x6a, // i32.add
3948            0x0b, // end
3949        ]
3950    }
3951
3952    fn local_tee_stack_module() -> &'static [u8] {
3953        &[
3954            0x00, 0x61, 0x73, 0x6d, // magic
3955            0x01, 0x00, 0x00, 0x00, // version
3956            0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, // type: [] -> [i32]
3957            0x03, 0x02, 0x01, 0x00, // function type 0
3958            0x0a, 0x0d, 0x01, 0x0b, 0x01, 0x01, 0x7f, // one i32 local
3959            0x41, 0x28, // i32.const 40
3960            0x22, 0x00, // local.tee 0
3961            0x41, 0x02, // i32.const 2
3962            0x6a, // i32.add
3963            0x0b, // end
3964        ]
3965    }
3966
3967    #[test]
3968    fn lower_i32_sub_and_mul_cohort() {
3969        let module = Module::decode(i32_sub_mul_module()).unwrap();
3970        let reg_module = module.lower().unwrap();
3971        let func = &reg_module.funcs[0];
3972
3973        assert_eq!(func.reg_types, vec![ValType::Num(NumType::I32); 5]);
3974        assert_eq!(
3975            func.blocks[0]
3976                .instrs
3977                .iter()
3978                .map(|instr| &instr.op)
3979                .collect::<Vec<_>>(),
3980            vec![
3981                &RegOp::I32Const {
3982                    dst: Reg(0),
3983                    value: 50,
3984                },
3985                &RegOp::I32Const {
3986                    dst: Reg(1),
3987                    value: 8,
3988                },
3989                &RegOp::Binary {
3990                    op: BinaryOp::I32Sub,
3991                    dst: Reg(2),
3992                    lhs: Reg(0),
3993                    rhs: Reg(1),
3994                },
3995                &RegOp::I32Const {
3996                    dst: Reg(3),
3997                    value: 3,
3998                },
3999                &RegOp::Binary {
4000                    op: BinaryOp::I32Mul,
4001                    dst: Reg(4),
4002                    lhs: Reg(2),
4003                    rhs: Reg(3),
4004                },
4005                // Return checked via blocks[0].term
4006            ]
4007        );
4008    }
4009
4010    #[test]
4011    fn execute_i32_sub_and_mul_cohort() {
4012        let module = Module::decode(i32_sub_mul_module()).unwrap();
4013        let reg_module = module.lower().unwrap();
4014
4015        let result = crate::runtime::execute_func(&reg_module.funcs[0], &[]).unwrap();
4016
4017        assert_eq!(result, vec![crate::runtime::Value::I32(126)]);
4018    }
4019
4020    #[test]
4021    fn lower_i64_add_cohort() {
4022        let module = Module::decode(i64_add_module()).unwrap();
4023        let reg_module = module.lower().unwrap();
4024        let func = &reg_module.funcs[0];
4025
4026        assert_eq!(func.reg_types, vec![ValType::Num(NumType::I64); 3]);
4027        assert_eq!(
4028            func.blocks[0]
4029                .instrs
4030                .iter()
4031                .map(|instr| &instr.op)
4032                .collect::<Vec<_>>(),
4033            vec![
4034                &RegOp::I64Const {
4035                    dst: Reg(0),
4036                    value: 20,
4037                },
4038                &RegOp::I64Const {
4039                    dst: Reg(1),
4040                    value: 22,
4041                },
4042                &RegOp::Binary {
4043                    op: BinaryOp::I64Add,
4044                    dst: Reg(2),
4045                    lhs: Reg(0),
4046                    rhs: Reg(1),
4047                },
4048                // Return checked via blocks[0].term
4049            ]
4050        );
4051    }
4052
4053    #[test]
4054    fn execute_i64_add_cohort() {
4055        let module = Module::decode(i64_add_module()).unwrap();
4056        let reg_module = module.lower().unwrap();
4057
4058        let result = crate::runtime::execute_func(&reg_module.funcs[0], &[]).unwrap();
4059
4060        assert_eq!(result, vec![crate::runtime::Value::I64(42)]);
4061    }
4062
4063    fn i32_sub_mul_module() -> &'static [u8] {
4064        &[
4065            0x00, 0x61, 0x73, 0x6d, // magic
4066            0x01, 0x00, 0x00, 0x00, // version
4067            0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, // type: [] -> [i32]
4068            0x03, 0x02, 0x01, 0x00, // function type 0
4069            0x0a, 0x0c, 0x01, 0x0a, 0x00, // one body, no locals
4070            0x41, 0x32, // i32.const 50
4071            0x41, 0x08, // i32.const 8
4072            0x6b, // i32.sub
4073            0x41, 0x03, // i32.const 3
4074            0x6c, // i32.mul
4075            0x0b, // end
4076        ]
4077    }
4078
4079    fn i64_add_module() -> &'static [u8] {
4080        &[
4081            0x00, 0x61, 0x73, 0x6d, // magic
4082            0x01, 0x00, 0x00, 0x00, // version
4083            0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7e, // type: [] -> [i64]
4084            0x03, 0x02, 0x01, 0x00, // function type 0
4085            0x0a, 0x09, 0x01, 0x07, 0x00, // one body, no locals
4086            0x42, 0x14, // i64.const 20
4087            0x42, 0x16, // i64.const 22
4088            0x7c, // i64.add
4089            0x0b, // end
4090        ]
4091    }
4092
4093    #[test]
4094    fn lower_i32_eqz_cohort() {
4095        let module = Module::decode(i32_eqz_module()).unwrap();
4096        let reg_module = module.lower().unwrap();
4097        let func = &reg_module.funcs[0];
4098
4099        assert_eq!(func.reg_types, vec![ValType::Num(NumType::I32); 2]);
4100        assert_eq!(
4101            func.blocks[0]
4102                .instrs
4103                .iter()
4104                .map(|instr| &instr.op)
4105                .collect::<Vec<_>>(),
4106            vec![
4107                &RegOp::LocalGet {
4108                    dst: Reg(0),
4109                    local: LocalIdx(0),
4110                },
4111                &RegOp::Unary {
4112                    op: UnaryOp::I32Eqz,
4113                    dst: Reg(1),
4114                    value: Reg(0),
4115                },
4116                // Return checked via blocks[0].term
4117            ]
4118        );
4119    }
4120
4121    #[test]
4122    fn execute_i32_eqz_cohort() {
4123        let module = Module::decode(i32_eqz_module()).unwrap();
4124        let reg_module = module.lower().unwrap();
4125
4126        let zero =
4127            crate::runtime::execute_func(&reg_module.funcs[0], &[crate::runtime::Value::I32(0)])
4128                .unwrap();
4129        let nonzero =
4130            crate::runtime::execute_func(&reg_module.funcs[0], &[crate::runtime::Value::I32(7)])
4131                .unwrap();
4132
4133        assert_eq!(zero, vec![crate::runtime::Value::I32(1)]);
4134        assert_eq!(nonzero, vec![crate::runtime::Value::I32(0)]);
4135    }
4136
4137    fn i32_eqz_module() -> &'static [u8] {
4138        &[
4139            0x00, 0x61, 0x73, 0x6d, // magic
4140            0x01, 0x00, 0x00, 0x00, // version
4141            0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f, // type: [i32] -> [i32]
4142            0x03, 0x02, 0x01, 0x00, // function type 0
4143            0x0a, 0x07, 0x01, 0x05, 0x00, // one body, no locals
4144            0x20, 0x00, // local.get 0
4145            0x45, // i32.eqz
4146            0x0b, // end
4147        ]
4148    }
4149
4150    #[test]
4151    fn lower_explicit_return_instruction() {
4152        let module = Module::decode(explicit_return_module()).unwrap();
4153        let reg_module = module.lower().unwrap();
4154        let func = &reg_module.funcs[0];
4155
4156        assert_eq!(
4157            func.blocks[0]
4158                .instrs
4159                .iter()
4160                .map(|instr| &instr.op)
4161                .collect::<Vec<_>>(),
4162            vec![
4163                &RegOp::I32Const {
4164                    dst: Reg(0),
4165                    value: 42,
4166                },
4167                // Return checked via blocks[0].term
4168            ]
4169        );
4170    }
4171
4172    #[test]
4173    fn execute_explicit_return_instruction() {
4174        let module = Module::decode(explicit_return_module()).unwrap();
4175        let reg_module = module.lower().unwrap();
4176
4177        let result = crate::runtime::execute_func(&reg_module.funcs[0], &[]).unwrap();
4178
4179        assert_eq!(result, vec![crate::runtime::Value::I32(42)]);
4180    }
4181
4182    fn explicit_return_module() -> &'static [u8] {
4183        &[
4184            0x00, 0x61, 0x73, 0x6d, // magic
4185            0x01, 0x00, 0x00, 0x00, // version
4186            0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, // type: [] -> [i32]
4187            0x03, 0x02, 0x01, 0x00, // function type 0
4188            0x0a, 0x07, 0x01, 0x05, 0x00, // one body, no locals
4189            0x41, 0x2a, // i32.const 42
4190            0x0f, // return
4191            0x0b, // end
4192        ]
4193    }
4194
4195    /// Lower a WAT module for control-flow shape tests.
4196    fn lower_wat(source: &str) -> RegModule {
4197        let buf = wast::parser::ParseBuffer::new(source).unwrap();
4198        let mut wat = wast::parser::parse::<wast::Wat<'_>>(&buf).unwrap();
4199        let bytes = wat.encode().unwrap();
4200        let module = Module::decode(&bytes).unwrap();
4201        module.lower().unwrap()
4202    }
4203
4204    fn run_wat(source: &str, args: &[crate::runtime::Value]) -> Vec<crate::runtime::Value> {
4205        let reg_module = lower_wat(source);
4206        crate::runtime::execute_func(&reg_module.funcs[0], args).unwrap()
4207    }
4208
4209    #[test]
4210    fn lower_if_else_shape() {
4211        let reg_module = lower_wat(
4212            "(module (func (param i32) (result i32)
4213               local.get 0
4214               if (result i32)
4215                 i32.const 1
4216               else
4217                 i32.const 2
4218               end))",
4219        );
4220        let func = &reg_module.funcs[0];
4221
4222        // Block 0 must end in an IfFork whose then/else targets were
4223        // back-patched to real block indices.
4224        let RegTerm::IfFork {
4225            then_block,
4226            else_block,
4227            ..
4228        } = func.blocks[0].term
4229        else {
4230            panic!("expected IfFork, got {:?}", func.blocks[0].term);
4231        };
4232        assert_eq!(then_block, 1);
4233        assert_ne!(else_block, 0);
4234        // The then-body must exit via a branch over the else-body.
4235        assert!(matches!(
4236            func.blocks[then_block as usize].term,
4237            RegTerm::Br { .. }
4238        ));
4239    }
4240
4241    #[test]
4242    fn execute_if_else_paths() {
4243        let source = "(module (func (param i32) (result i32)
4244            local.get 0
4245            if (result i32)
4246              i32.const 1
4247            else
4248              i32.const 2
4249            end))";
4250        assert_eq!(
4251            run_wat(source, &[crate::runtime::Value::I32(1)]),
4252            vec![crate::runtime::Value::I32(1)]
4253        );
4254        assert_eq!(
4255            run_wat(source, &[crate::runtime::Value::I32(0)]),
4256            vec![crate::runtime::Value::I32(2)]
4257        );
4258    }
4259
4260    #[test]
4261    fn lower_if_without_else_patches_else_to_continuation() {
4262        let reg_module = lower_wat(
4263            "(module (func (param i32) (result i32)
4264               local.get 0
4265               if
4266                 i32.const 42
4267                 drop
4268               end
4269               i32.const 7))",
4270        );
4271        let func = &reg_module.funcs[0];
4272        let RegTerm::IfFork { else_block, .. } = func.blocks[0].term else {
4273            panic!("expected IfFork, got {:?}", func.blocks[0].term);
4274        };
4275        // With no else, the else edge must reach the continuation whose
4276        // fallthrough chain leads to the final Return.
4277        let mut idx = else_block;
4278        loop {
4279            match func.blocks[idx as usize].term {
4280                RegTerm::Fallthrough => idx += 1,
4281                RegTerm::Return { .. } => break,
4282                ref other => panic!("unexpected terminator on else path: {other:?}"),
4283            }
4284        }
4285    }
4286
4287    #[test]
4288    fn lower_loop_back_edge_targets_header() {
4289        let reg_module = lower_wat(
4290            "(module (func (param i32) (result i32)
4291               (local i32)
4292               block
4293                 loop
4294                   local.get 0
4295                   i32.eqz
4296                   br_if 1
4297                   local.get 0
4298                   i32.const 1
4299                   i32.sub
4300                   local.set 0
4301                   br 0
4302                 end
4303               end
4304               local.get 1))",
4305        );
4306        let func = &reg_module.funcs[0];
4307        // The loop body contains a `br 0` back-edge: a Br terminator whose
4308        // target is an earlier (header) block.
4309        let back_edge = func
4310            .blocks
4311            .iter()
4312            .enumerate()
4313            .find_map(|(idx, block)| match block.term {
4314                RegTerm::Br { target_block, .. } if (target_block as usize) < idx => {
4315                    Some(target_block)
4316                }
4317                _ => None,
4318            })
4319            .expect("expected a loop back-edge Br");
4320        // The header block is where the pre-loop block falls through to
4321        // (block 0 = pre-block, block 1 = pre-loop, block 2 = loop header).
4322        assert_eq!(back_edge, 2);
4323    }
4324
4325    #[test]
4326    fn execute_loop_sum() {
4327        let source = "(module (func (param i32) (result i32)
4328            (local i32)
4329            block
4330              loop
4331                local.get 0
4332                i32.eqz
4333                br_if 1
4334                local.get 1
4335                local.get 0
4336                i32.add
4337                local.set 1
4338                local.get 0
4339                i32.const 1
4340                i32.sub
4341                local.set 0
4342                br 0
4343              end
4344            end
4345            local.get 1))";
4346        assert_eq!(
4347            run_wat(source, &[crate::runtime::Value::I32(5)]),
4348            vec![crate::runtime::Value::I32(15)]
4349        );
4350        assert_eq!(
4351            run_wat(source, &[crate::runtime::Value::I32(0)]),
4352            vec![crate::runtime::Value::I32(0)]
4353        );
4354    }
4355
4356    #[test]
4357    fn lower_br_value_appends_copy_to_branch_block() {
4358        let reg_module = lower_wat(
4359            "(module (func (result i32)
4360               block (result i32)
4361                 i32.const 1
4362                 br 0
4363                 i32.const 2
4364               end))",
4365        );
4366        let func = &reg_module.funcs[0];
4367        // The block containing `br 0` must deliver its value into the
4368        // continuation's expected register via a Copy before branching.
4369        let br_block = func
4370            .blocks
4371            .iter()
4372            .find(|block| matches!(block.term, RegTerm::Br { .. }))
4373            .expect("expected a Br block");
4374        assert!(
4375            br_block
4376                .instrs
4377                .iter()
4378                .any(|instr| matches!(instr.op, RegOp::Copy { .. })),
4379            "expected a Copy instruction in the branch block: {br_block:?}"
4380        );
4381    }
4382
4383    #[test]
4384    fn execute_br_value_delivers_branch_site_value() {
4385        let source = "(module (func (result i32)
4386            block (result i32)
4387              i32.const 1
4388              br 0
4389              i32.const 2
4390            end))";
4391        assert_eq!(run_wat(source, &[]), vec![crate::runtime::Value::I32(1)]);
4392    }
4393
4394    #[test]
4395    fn execute_return_does_not_stop_lowering() {
4396        // Regression: `return` used to halt lowering, dropping the code
4397        // after the block's `end`.
4398        let source = "(module (func (param i32) (result i32)
4399            block
4400              local.get 0
4401              br_if 0
4402              i32.const 10
4403              return
4404            end
4405            i32.const 20))";
4406        assert_eq!(
4407            run_wat(source, &[crate::runtime::Value::I32(0)]),
4408            vec![crate::runtime::Value::I32(10)]
4409        );
4410        assert_eq!(
4411            run_wat(source, &[crate::runtime::Value::I32(1)]),
4412            vec![crate::runtime::Value::I32(20)]
4413        );
4414    }
4415
4416    #[test]
4417    fn execute_unreachable_traps() {
4418        let reg_module = lower_wat("(module (func (result i32) unreachable))");
4419        let error = crate::runtime::execute_func(&reg_module.funcs[0], &[]).unwrap_err();
4420        assert_eq!(
4421            error.kind,
4422            crate::runtime::RuntimeErrorKind::Trap(crate::runtime::RuntimeTrap::Unreachable)
4423        );
4424    }
4425
4426    #[test]
4427    fn fuel_exhaustion_stops_runaway_and_bounded_finishes() {
4428        // An infinite loop burns any budget.
4429        let reg_module = lower_wat("(module (func (export \"spin\") (loop br 0)))");
4430        let store = crate::runtime::Store::instantiate(&reg_module).unwrap();
4431        store.set_fuel(Some(1_000));
4432        let error = crate::runtime::execute_export(&reg_module, &store, "spin", &[]).unwrap_err();
4433        assert_eq!(error.kind, crate::runtime::RuntimeErrorKind::FuelExhausted);
4434        assert_eq!(store.fuel(), Some(0));
4435
4436        // A bounded loop fits inside a sufficient budget.
4437        let reg_module = lower_wat(
4438            "(module (func (export \"sum\") (result i32) (local i32 i32) \
4439             (block (loop \
4440             local.get 0 i32.const 10 i32.ge_s br_if 1 \
4441             local.get 1 local.get 0 i32.add local.set 1 \
4442             local.get 0 i32.const 1 i32.add local.set 0 \
4443             br 0)) \
4444             local.get 1))",
4445        );
4446        let store = crate::runtime::Store::instantiate(&reg_module).unwrap();
4447        store.set_fuel(Some(10_000));
4448        let result = crate::runtime::execute_export(&reg_module, &store, "sum", &[]).unwrap();
4449        assert_eq!(result, vec![crate::runtime::Value::I32(45)]);
4450        assert!(store.fuel().unwrap() < 10_000);
4451
4452        // Unlimited by default: no budget, no exhaustion.
4453        let reg_module = lower_wat("(module (func (export \"one\") (result i32) i32.const 1))");
4454        let store = crate::runtime::Store::instantiate(&reg_module).unwrap();
4455        assert_eq!(store.fuel(), None);
4456        let result = crate::runtime::execute_export(&reg_module, &store, "one", &[]).unwrap();
4457        assert_eq!(result, vec![crate::runtime::Value::I32(1)]);
4458    }
4459
4460    /// Execute an exported function with full module context (required for
4461    /// `call` instructions).
4462    fn run_wat_export(
4463        source: &str,
4464        name: &str,
4465        args: &[crate::runtime::Value],
4466    ) -> Result<Vec<crate::runtime::Value>, crate::runtime::RuntimeError> {
4467        let reg_module = lower_wat(source);
4468        let store = crate::runtime::Store::instantiate(&reg_module).unwrap();
4469        crate::runtime::execute_export(&reg_module, &store, name, args)
4470    }
4471
4472    #[test]
4473    fn lower_call_shape() {
4474        let reg_module = lower_wat(
4475            "(module
4476               (func $add (param i32 i32) (result i32)
4477                 local.get 0 local.get 1 i32.add)
4478               (func (export \"main\") (param i32 i32) (result i32)
4479                 local.get 0 local.get 1 call $add))",
4480        );
4481        let func = &reg_module.funcs[1];
4482        let call = func.blocks[0]
4483            .instrs
4484            .iter()
4485            .find_map(|instr| match &instr.op {
4486                RegOp::Call {
4487                    func,
4488                    args,
4489                    results,
4490                } => Some((func, args, results)),
4491                _ => None,
4492            })
4493            .expect("expected a Call op");
4494        assert_eq!(*call.0, FuncIdx(0));
4495        assert_eq!(call.1.as_slice(), &[Reg(0), Reg(1)]);
4496        assert_eq!(call.2.as_slice(), &[Reg(2)]);
4497    }
4498
4499    #[test]
4500    fn execute_call_recursion_and_multi_result() {
4501        let fac = run_wat_export(
4502            "(module
4503               (func $fac (param i32) (result i32)
4504                 local.get 0
4505                 i32.const 2
4506                 i32.lt_s
4507                 if (result i32)
4508                   i32.const 1
4509                 else
4510                   local.get 0
4511                   local.get 0
4512                   i32.const 1
4513                   i32.sub
4514                   call $fac
4515                   i32.mul
4516                 end)
4517               (func (export \"fac\") (param i32) (result i32)
4518                 local.get 0
4519                 call $fac))",
4520            "fac",
4521            &[crate::runtime::Value::I32(5)],
4522        );
4523        assert_eq!(fac, Ok(vec![crate::runtime::Value::I32(120)]));
4524
4525        let rem = run_wat_export(
4526            "(module
4527               (func $divmod (param i32 i32) (result i32 i32)
4528                 local.get 0 local.get 1 i32.div_u
4529                 local.get 0 local.get 1 i32.rem_u)
4530               (func (export \"rem\") (param i32 i32) (result i32)
4531                 (local i32)
4532                 local.get 0 local.get 1 call $divmod
4533                 local.set 2
4534                 drop
4535                 local.get 2))",
4536            "rem",
4537            &[
4538                crate::runtime::Value::I32(17),
4539                crate::runtime::Value::I32(5),
4540            ],
4541        );
4542        assert_eq!(rem, Ok(vec![crate::runtime::Value::I32(2)]));
4543    }
4544
4545    #[test]
4546    fn execute_call_ref_and_null_trap() {
4547        // Direct call through a funcref value.
4548        let result = run_wat_export(
4549            "(module (type $ii (func (param i32) (result i32)))\
4550             (elem declare func $dbl)\
4551             (func $dbl (type $ii) local.get 0 i32.const 2 i32.mul)\
4552             (func (export \"go\") (param i32) (result i32)\
4553             local.get 0 ref.func $dbl call_ref $ii))",
4554            "go",
4555            &[crate::runtime::Value::I32(21)],
4556        )
4557        .unwrap();
4558        assert_eq!(result, vec![crate::runtime::Value::I32(42)]);
4559
4560        // Null funcref traps with the canonical message.
4561        let error = run_wat_export(
4562            "(module (type $ii (func (param i32) (result i32)))\
4563             (func (export \"go\") (param i32) (result i32)\
4564             local.get 0 ref.null $ii call_ref $ii))",
4565            "go",
4566            &[crate::runtime::Value::I32(1)],
4567        )
4568        .unwrap_err();
4569        assert_eq!(
4570            error.kind,
4571            crate::runtime::RuntimeErrorKind::Trap(
4572                crate::runtime::RuntimeTrap::NullFunctionReference
4573            )
4574        );
4575    }
4576
4577    #[test]
4578    fn execute_br_on_null_paths() {
4579        // Null ref takes the branch; non-null falls through keeping the ref.
4580        let source = "(module (type $ii (func (result i32)))\
4581             (elem declare func $f)\
4582             (func $f (type $ii) i32.const 7)\
4583             (func (export \"null\") (result i32)\
4584             (block ref.null $ii br_on_null 0 call_ref $ii return)\
4585             i32.const 99)\
4586             (func (export \"nonnull\") (result i32)\
4587             (block ref.func $f br_on_null 0 call_ref $ii return)\
4588             i32.const 99))";
4589        let result = run_wat_export(source, "null", &[]).unwrap();
4590        assert_eq!(result, vec![crate::runtime::Value::I32(99)]);
4591        let result = run_wat_export(source, "nonnull", &[]).unwrap();
4592        assert_eq!(result, vec![crate::runtime::Value::I32(7)]);
4593    }
4594
4595    #[test]
4596    fn execute_br_on_non_null_forwards_ref() {
4597        // Non-null ref branches and is forwarded to the label; null falls
4598        // through with the ref consumed.
4599        let result = run_wat_export(
4600            "(module (type $ii (func (result i32)))\
4601             (elem declare func $f)\
4602             (func $f (type $ii) i32.const 7)\
4603             (func (export \"nonnull\") (result i32)\
4604             (block (result (ref $ii)) ref.func $f br_on_non_null 0 unreachable)\
4605             call_ref $ii)\
4606             (func (export \"null\") (result i32)\
4607             (block (result (ref $ii)) ref.null $ii br_on_non_null 0 unreachable)\
4608             call_ref $ii))",
4609            "nonnull",
4610            &[],
4611        )
4612        .unwrap();
4613        assert_eq!(result, vec![crate::runtime::Value::I32(7)]);
4614    }
4615
4616    #[test]
4617    fn execute_ref_as_non_null_trap_and_passthrough() {
4618        let error = run_wat_export(
4619            "(module (type $ii (func))\
4620             (func (export \"go\") (result i32)\
4621             ref.null $ii ref.as_non_null drop i32.const 0))",
4622            "go",
4623            &[],
4624        )
4625        .unwrap_err();
4626        assert_eq!(
4627            error.kind,
4628            crate::runtime::RuntimeErrorKind::Trap(crate::runtime::RuntimeTrap::NullReference)
4629        );
4630
4631        let result = run_wat_export(
4632            "(module (type $ii (func (result i32)))\
4633             (elem declare func $f)\
4634             (func $f (type $ii) i32.const 7)\
4635             (func (export \"go\") (result i32)\
4636             ref.func $f ref.as_non_null call_ref $ii))",
4637            "go",
4638            &[],
4639        )
4640        .unwrap();
4641        assert_eq!(result, vec![crate::runtime::Value::I32(7)]);
4642    }
4643
4644    #[test]
4645    fn execute_call_exhaustion_traps() {
4646        // Deep recursion needs a larger host stack than the default test
4647        // thread provides at this call depth.
4648        let error = std::thread::Builder::new()
4649            .stack_size(32 * 1024 * 1024)
4650            .spawn(|| {
4651                run_wat_export(
4652                    "(module (func $boom (export \"boom\") call $boom))",
4653                    "boom",
4654                    &[],
4655                )
4656            })
4657            .expect("spawn exhaustion test thread")
4658            .join()
4659            .expect("exhaustion test thread panicked")
4660            .unwrap_err();
4661        assert_eq!(
4662            error.kind,
4663            crate::runtime::RuntimeErrorKind::Trap(crate::runtime::RuntimeTrap::CallStackExhausted)
4664        );
4665    }
4666
4667    #[test]
4668    fn execute_select_variants() {
4669        let source = "(module (func (param i32 i32 i32) (result i32)
4670            local.get 0
4671            local.get 1
4672            local.get 2
4673            select))";
4674        assert_eq!(
4675            run_wat(
4676                source,
4677                &[
4678                    crate::runtime::Value::I32(10),
4679                    crate::runtime::Value::I32(20),
4680                    crate::runtime::Value::I32(1),
4681                ],
4682            ),
4683            vec![crate::runtime::Value::I32(10)]
4684        );
4685        assert_eq!(
4686            run_wat(
4687                source,
4688                &[
4689                    crate::runtime::Value::I32(10),
4690                    crate::runtime::Value::I32(20),
4691                    crate::runtime::Value::I32(0),
4692                ],
4693            ),
4694            vec![crate::runtime::Value::I32(20)]
4695        );
4696    }
4697
4698    #[test]
4699    fn lower_select_shape() {
4700        let reg_module = lower_wat(
4701            "(module (func (param i32 i32 i32) (result i32)
4702               local.get 0
4703               local.get 1
4704               local.get 2
4705               select))",
4706        );
4707        let func = &reg_module.funcs[0];
4708        assert!(func.blocks[0].instrs.iter().any(|instr| matches!(
4709            instr.op,
4710            RegOp::Select {
4711                dst: Reg(3),
4712                v1: Reg(0),
4713                v2: Reg(1),
4714                cond: Reg(2),
4715            }
4716        )));
4717    }
4718
4719    #[test]
4720    fn lower_br_table_shape_and_patching() {
4721        let reg_module = lower_wat(
4722            "(module (func (param i32) (result i32)
4723               block (result i32)
4724                 block (result i32)
4725                   i32.const 0
4726                   local.get 0
4727                   br_table 0 1
4728                 end
4729                 i32.const 10
4730                 i32.add
4731                 br 0
4732               end))",
4733        );
4734        let func = &reg_module.funcs[0];
4735        let br_table_block = func
4736            .blocks
4737            .iter()
4738            .find(|block| matches!(block.term, RegTerm::BrTable { .. }))
4739            .expect("expected a BrTable block");
4740        let RegTerm::BrTable {
4741            targets, default, ..
4742        } = &br_table_block.term
4743        else {
4744            unreachable!()
4745        };
4746        // Both slots back-patched to real continuation blocks.
4747        assert_eq!(targets.len(), 1);
4748        assert_ne!(targets[0], 0);
4749        assert_ne!(*default, 0);
4750        assert_ne!(targets[0], *default);
4751        // The block carries copies delivering the branch value.
4752        assert!(
4753            br_table_block
4754                .instrs
4755                .iter()
4756                .any(|instr| matches!(instr.op, RegOp::Copy { .. }))
4757        );
4758    }
4759
4760    #[test]
4761    fn execute_br_table_dispatch() {
4762        let source = "(module (func (param i32) (result i32)
4763            block (result i32)
4764              block (result i32)
4765                i32.const 0
4766                local.get 0
4767                br_table 0 1
4768              end
4769              i32.const 10
4770              i32.add
4771              br 0
4772            end))";
4773        assert_eq!(
4774            run_wat(source, &[crate::runtime::Value::I32(0)]),
4775            vec![crate::runtime::Value::I32(10)]
4776        );
4777        assert_eq!(
4778            run_wat(source, &[crate::runtime::Value::I32(1)]),
4779            vec![crate::runtime::Value::I32(0)]
4780        );
4781        // Out-of-range and negative indices take the default.
4782        assert_eq!(
4783            run_wat(source, &[crate::runtime::Value::I32(9)]),
4784            vec![crate::runtime::Value::I32(0)]
4785        );
4786        assert_eq!(
4787            run_wat(source, &[crate::runtime::Value::I32(-1)]),
4788            vec![crate::runtime::Value::I32(0)]
4789        );
4790    }
4791
4792    #[test]
4793    fn lower_load_store_shape() {
4794        let reg_module = lower_wat(
4795            "(module
4796               (memory 1)
4797               (func (export \"f\") (param i32) (result i32)
4798                 local.get 0
4799                 local.get 0
4800                 i32.load offset=4
4801                 i32.store
4802                 i32.const 0))",
4803        );
4804        let func = &reg_module.funcs[0];
4805        let ops: Vec<&RegOp> = func.blocks[0]
4806            .instrs
4807            .iter()
4808            .map(|instr| &instr.op)
4809            .collect();
4810        assert!(matches!(
4811            ops[2],
4812            RegOp::Load {
4813                op: LoadOp::I32,
4814                memarg: MemArg { offset: 4, .. },
4815                ..
4816            }
4817        ));
4818        assert!(matches!(
4819            ops[3],
4820            RegOp::Store {
4821                op: StoreOp::I32,
4822                ..
4823            }
4824        ));
4825    }
4826
4827    #[test]
4828    fn execute_memory_roundtrip_and_oob_trap() {
4829        let source = "(module
4830            (memory 1)
4831            (func (export \"roundtrip\") (param i32 i32) (result i32)
4832              local.get 0
4833              local.get 1
4834              i32.store
4835              local.get 0
4836              i32.load)
4837            (func (export \"load\") (param i32) (result i32)
4838              local.get 0
4839              i32.load))";
4840        assert_eq!(
4841            run_wat_export(
4842                source,
4843                "roundtrip",
4844                &[
4845                    crate::runtime::Value::I32(8),
4846                    crate::runtime::Value::I32(-3)
4847                ],
4848            ),
4849            Ok(vec![crate::runtime::Value::I32(-3)])
4850        );
4851        // One page: address 65533 + 4-byte load is out of bounds.
4852        let error = run_wat_export(source, "load", &[crate::runtime::Value::I32(65533)])
4853            .expect_err("expected OOB trap");
4854        assert_eq!(
4855            error.kind,
4856            crate::runtime::RuntimeErrorKind::Trap(
4857                crate::runtime::RuntimeTrap::OutOfBoundsMemoryAccess
4858            )
4859        );
4860    }
4861
4862    #[test]
4863    fn execute_globals_and_data_segments() {
4864        let source = "(module
4865            (memory 1)
4866            (global $g (mut i32) (i32.const 10))
4867            (data (i32.const 4) \"\\2a\\00\\00\\00\")
4868            (func (export \"bump\") (result i32)
4869              global.get $g
4870              i32.const 1
4871              i32.add
4872              global.set $g
4873              global.get $g)
4874            (func (export \"load4\") (result i32)
4875              i32.const 4
4876              i32.load))";
4877        // Data segment wrote 42 at address 4 during instantiation.
4878        assert_eq!(
4879            run_wat_export(source, "load4", &[]),
4880            Ok(vec![crate::runtime::Value::I32(42)])
4881        );
4882        assert_eq!(
4883            run_wat_export(source, "bump", &[]),
4884            Ok(vec![crate::runtime::Value::I32(11)])
4885        );
4886    }
4887
4888    #[test]
4889    fn lower_call_indirect_shape() {
4890        let reg_module = lower_wat(
4891            "(module
4892               (type $t (func (param i32) (result i32)))
4893               (table 1 funcref)
4894               (func (export \"apply\") (param i32 i32) (result i32)
4895                 local.get 1
4896                 local.get 0
4897                 call_indirect (type $t)))",
4898        );
4899        let func = &reg_module.funcs[0];
4900        let call = func.blocks[0]
4901            .instrs
4902            .iter()
4903            .find_map(|instr| match &instr.op {
4904                RegOp::CallIndirect {
4905                    type_idx,
4906                    table,
4907                    args,
4908                    results,
4909                    ..
4910                } => Some((type_idx, table, args, results)),
4911                _ => None,
4912            })
4913            .expect("expected a CallIndirect op");
4914        assert_eq!(*call.0, TypeIdx(0));
4915        assert_eq!(*call.1, TableIdx(0));
4916        assert_eq!(call.2.as_slice(), &[Reg(0)]);
4917        assert_eq!(call.3.as_slice(), &[Reg(2)]);
4918    }
4919
4920    #[test]
4921    fn execute_call_indirect_and_traps() {
4922        let source = "(module
4923            (type $t (func (result i32)))
4924            (table 2 funcref)
4925            (func $f (type $t) i32.const 42)
4926            (elem (i32.const 0) $f)
4927            (func (export \"go\") (param i32) (result i32)
4928              local.get 0
4929              call_indirect (type $t)))";
4930        assert_eq!(
4931            run_wat_export(source, "go", &[crate::runtime::Value::I32(0)]),
4932            Ok(vec![crate::runtime::Value::I32(42)])
4933        );
4934        // Null slot.
4935        let error = run_wat_export(source, "go", &[crate::runtime::Value::I32(1)])
4936            .expect_err("expected uninitialized element trap");
4937        assert_eq!(
4938            error.kind,
4939            crate::runtime::RuntimeErrorKind::Trap(
4940                crate::runtime::RuntimeTrap::UninitializedElement
4941            )
4942        );
4943        // Out of bounds.
4944        let error = run_wat_export(source, "go", &[crate::runtime::Value::I32(7)])
4945            .expect_err("expected undefined element trap");
4946        assert_eq!(
4947            error.kind,
4948            crate::runtime::RuntimeErrorKind::Trap(crate::runtime::RuntimeTrap::UndefinedElement)
4949        );
4950    }
4951
4952    #[test]
4953    fn execute_table_grow_fill_copy() {
4954        let source = "(module
4955            (type $t (func (result i32)))
4956            (table 1 funcref)
4957            (func $f (type $t) i32.const 9)
4958            (elem declare func $f)
4959            (func (export \"go\") (param i32) (result i32)
4960              local.get 0
4961              call_indirect (type $t))
4962            (func (export \"setup\") (result i32)
4963              ref.func $f
4964              i32.const 2
4965              table.grow
4966              drop
4967              i32.const 1
4968              ref.func $f
4969              i32.const 2
4970              table.fill
4971              i32.const 1
4972              i32.const 2
4973              i32.const 1
4974              table.copy
4975              table.size))";
4976        // After setup: [null, f, f] (grow 2, fill 2 from 1, copy [1..2] to 2).
4977        let reg_module = lower_wat(source);
4978        let store = crate::runtime::Store::instantiate(&reg_module).unwrap();
4979        assert_eq!(
4980            crate::runtime::execute_export(&reg_module, &store, "setup", &[]),
4981            Ok(vec![crate::runtime::Value::I32(3)])
4982        );
4983        assert_eq!(
4984            crate::runtime::execute_export(
4985                &reg_module,
4986                &store,
4987                "go",
4988                &[crate::runtime::Value::I32(2)],
4989            ),
4990            Ok(vec![crate::runtime::Value::I32(9)])
4991        );
4992    }
4993
4994    #[test]
4995    fn lower_loop_with_params_consumes_them_into_frame() {
4996        let reg_module = lower_wat(
4997            "(module (func (param i32 i32) (result i32)
4998               local.get 0
4999               local.get 1
5000               block (param i32 i32) (result i32)
5001                 i32.add
5002               end))",
5003        );
5004        let func = &reg_module.funcs[0];
5005        // The block body must be able to pop both params (they live in the
5006        // frame, not below it): i32.add lowers without underflow, and the
5007        // function returns the sum.
5008        assert_eq!(
5009            crate::runtime::execute_func(
5010                &func.clone(),
5011                &[
5012                    crate::runtime::Value::I32(30),
5013                    crate::runtime::Value::I32(12)
5014                ],
5015            ),
5016            Ok(vec![crate::runtime::Value::I32(42)])
5017        );
5018    }
5019
5020    #[test]
5021    fn lower_br_if_to_loop_with_params_uses_trampoline() {
5022        let reg_module = lower_wat(
5023            "(module (func (param i32) (result i32)
5024               (local $n i32)
5025               i32.const 0
5026               local.get 0
5027               loop (param i32 i32) (result i32)
5028                 local.set $n
5029                 local.get $n
5030                 i32.add
5031                 local.get $n
5032                 i32.const 1
5033                 i32.sub
5034                 local.tee $n
5035                 local.get $n
5036                 i32.const 1
5037                 i32.ge_s
5038                 br_if 0
5039                 drop
5040               end))",
5041        );
5042        let func = &reg_module.funcs[0];
5043        // The conditional back-edge must target a trampoline (not the loop
5044        // header directly); the trampoline carries the param copies.
5045        let trampoline = func.blocks.iter().find(|block| {
5046            block
5047                .instrs
5048                .iter()
5049                .any(|instr| matches!(instr.op, RegOp::Copy { .. }))
5050                && matches!(block.term, RegTerm::Br { .. })
5051        });
5052        assert!(
5053            trampoline.is_some(),
5054            "expected a trampoline block with param copies"
5055        );
5056        // The BrIf terminator must point at that trampoline.
5057        let br_if = func
5058            .blocks
5059            .iter()
5060            .find_map(|block| match &block.term {
5061                RegTerm::BrIf { target_block, .. } => Some(*target_block),
5062                _ => None,
5063            })
5064            .expect("expected a BrIf terminator");
5065        let trampoline_idx = func
5066            .blocks
5067            .iter()
5068            .position(|block| core::ptr::eq(block, trampoline.unwrap()))
5069            .unwrap() as u32;
5070        assert_eq!(br_if, trampoline_idx);
5071    }
5072
5073    #[test]
5074    fn lower_v128_binary_shape() {
5075        let reg_module = lower_wat(
5076            "(module (func (result v128)
5077               v128.const i32x4 1 2 3 4
5078               v128.const i32x4 5 6 7 8
5079               i32x4.add))",
5080        );
5081        let func = &reg_module.funcs[0];
5082        let op = func.blocks[0]
5083            .instrs
5084            .iter()
5085            .find_map(|instr| match &instr.op {
5086                RegOp::V128Binary {
5087                    shape, kind, dst, ..
5088                } => Some((shape, kind, dst)),
5089                _ => None,
5090            })
5091            .expect("expected a V128Binary op");
5092        assert_eq!(*op.0, LaneShape::I32x4);
5093        assert_eq!(*op.1, V128BinaryKind::Add);
5094        assert_eq!(*op.2, Reg(2));
5095    }
5096
5097    #[test]
5098    fn execute_v128_arithmetic_and_memory() {
5099        let source = "(module
5100            (memory 1)
5101            (func (export \"add_store\") (param i32) (result i32)
5102              local.get 0
5103              v128.const i32x4 1 2 3 4
5104              v128.const i32x4 10 20 30 40
5105              i32x4.add
5106              v128.store
5107              local.get 0
5108              i32.load))";
5109        // First lane of (1+10) stored to memory and read back as a scalar.
5110        assert_eq!(
5111            run_wat_export(source, "add_store", &[crate::runtime::Value::I32(16)]),
5112            Ok(vec![crate::runtime::Value::I32(11)])
5113        );
5114    }
5115
5116    #[test]
5117    fn execute_saturating_truncation_edges() {
5118        let source = "(module (func (export \"sat\") (param f32) (result i32)
5119            local.get 0
5120            i32.trunc_sat_f32_s))";
5121        // NaN -> 0.
5122        assert_eq!(
5123            run_wat_export(source, "sat", &[crate::runtime::Value::F32(f32::NAN)]),
5124            Ok(vec![crate::runtime::Value::I32(0)])
5125        );
5126        // Above range -> i32::MAX.
5127        assert_eq!(
5128            run_wat_export(source, "sat", &[crate::runtime::Value::F32(3e9)]),
5129            Ok(vec![crate::runtime::Value::I32(i32::MAX)])
5130        );
5131        // In range -> plain truncation.
5132        assert_eq!(
5133            run_wat_export(source, "sat", &[crate::runtime::Value::F32(-2.9)]),
5134            Ok(vec![crate::runtime::Value::I32(-2)])
5135        );
5136    }
5137
5138    #[test]
5139    fn lower_bulk_memory_ops_shape() {
5140        let reg_module = lower_wat(
5141            "(module
5142               (memory 1)
5143               (data $d \"ab\")
5144               (func (export \"f\") (param i32)
5145                 local.get 0
5146                 i32.const 0
5147                 i32.const 2
5148                 memory.init $d
5149                 local.get 0
5150                 i32.const 1
5151                 i32.const 8
5152                 memory.fill
5153                 data.drop $d))",
5154        );
5155        let func = &reg_module.funcs[0];
5156        let ops: Vec<&RegOp> = func.blocks[0]
5157            .instrs
5158            .iter()
5159            .map(|instr| &instr.op)
5160            .collect();
5161        assert!(matches!(ops[3], RegOp::MemoryInit { .. }));
5162        assert!(matches!(ops[7], RegOp::MemoryFill { .. }));
5163        assert!(matches!(ops[8], RegOp::DataDrop { .. }));
5164    }
5165
5166    #[test]
5167    fn execute_host_function_milestone() {
5168        // The Phase 4 milestone: a module importing env.print_i32 calls the
5169        // host, which records the call.
5170        let source = "(module
5171            (import \"env\" \"print_i32\" (func $print (param i32)))
5172            (func (export \"main\")
5173              i32.const 42
5174              call $print))";
5175        let reg_module = lower_wat(source);
5176        let mut store = crate::runtime::Store::instantiate(&reg_module).unwrap();
5177
5178        let recorded = alloc::rc::Rc::new(core::cell::RefCell::new(Vec::new()));
5179        let sink = recorded.clone();
5180        store
5181            .register_host_func(
5182                "env",
5183                "print_i32",
5184                crate::runtime::HostFunction::new(
5185                    crate::types::FuncType {
5186                        params: vec![ValType::Num(NumType::I32)],
5187                        results: vec![],
5188                    },
5189                    move |args| {
5190                        sink.borrow_mut().push(args[0]);
5191                        Ok(vec![])
5192                    },
5193                ),
5194            )
5195            .unwrap();
5196
5197        let result = crate::runtime::execute_export(&reg_module, &store, "main", &[]);
5198        assert_eq!(result, Ok(vec![]));
5199        assert_eq!(
5200            recorded.borrow().as_slice(),
5201            &[crate::runtime::Value::I32(42)]
5202        );
5203    }
5204
5205    #[test]
5206    fn execute_host_function_with_results() {
5207        let source = "(module
5208            (import \"env\" \"double\" (func $double (param i32) (result i32)))
5209            (func (export \"go\") (param i32) (result i32)
5210              local.get 0
5211              call $double
5212              i32.const 2
5213              i32.add))";
5214        let reg_module = lower_wat(source);
5215        let mut store = crate::runtime::Store::instantiate(&reg_module).unwrap();
5216        store
5217            .register_host_func(
5218                "env",
5219                "double",
5220                crate::runtime::HostFunction::new(
5221                    crate::types::FuncType {
5222                        params: vec![ValType::Num(NumType::I32)],
5223                        results: vec![ValType::Num(NumType::I32)],
5224                    },
5225                    |args| {
5226                        let crate::runtime::Value::I32(v) = args[0] else {
5227                            unreachable!()
5228                        };
5229                        Ok(vec![crate::runtime::Value::I32(v * 2)])
5230                    },
5231                ),
5232            )
5233            .unwrap();
5234
5235        assert_eq!(
5236            crate::runtime::execute_export(
5237                &reg_module,
5238                &store,
5239                "go",
5240                &[crate::runtime::Value::I32(20)],
5241            ),
5242            Ok(vec![crate::runtime::Value::I32(42)])
5243        );
5244    }
5245
5246    #[test]
5247    fn unregistered_import_fails_on_call_not_instantiation() {
5248        let source = "(module
5249            (import \"env\" \"missing\" (func $missing))
5250            (func (export \"go\")
5251              call $missing))";
5252        let reg_module = lower_wat(source);
5253        // Instantiation succeeds (lazy resolution).
5254        let store = crate::runtime::Store::instantiate(&reg_module).unwrap();
5255        let error = crate::runtime::execute_export(&reg_module, &store, "go", &[])
5256            .expect_err("expected UnknownImport on call");
5257        assert_eq!(
5258            error.kind,
5259            crate::runtime::RuntimeErrorKind::UnknownImport {
5260                module: "env".into(),
5261                name: "missing".into(),
5262            }
5263        );
5264    }
5265
5266    #[test]
5267    fn host_registration_rejects_mismatch_and_unknown() {
5268        let source = "(module
5269            (import \"env\" \"f\" (func $f (param i32))))";
5270        let reg_module = lower_wat(source);
5271        let mut store = crate::runtime::Store::instantiate(&reg_module).unwrap();
5272
5273        // Wrong signature.
5274        let error = store
5275            .register_host_func(
5276                "env",
5277                "f",
5278                crate::runtime::HostFunction::new(
5279                    crate::types::FuncType {
5280                        params: vec![],
5281                        results: vec![],
5282                    },
5283                    |_| Ok(vec![]),
5284                ),
5285            )
5286            .expect_err("expected ImportTypeMismatch");
5287        assert!(matches!(
5288            error.kind,
5289            crate::runtime::RuntimeErrorKind::ImportTypeMismatch { .. }
5290        ));
5291
5292        // Undeclared import name.
5293        let error = store
5294            .register_host_func(
5295                "env",
5296                "nope",
5297                crate::runtime::HostFunction::new(
5298                    crate::types::FuncType {
5299                        params: vec![],
5300                        results: vec![],
5301                    },
5302                    |_| Ok(vec![]),
5303                ),
5304            )
5305            .expect_err("expected UnknownImport");
5306        assert_eq!(
5307            error.kind,
5308            crate::runtime::RuntimeErrorKind::UnknownImport {
5309                module: "env".into(),
5310                name: "nope".into(),
5311            }
5312        );
5313    }
5314
5315    #[test]
5316    fn execute_imported_memory() {
5317        let source = "(module
5318            (import \"env\" \"mem\" (memory 1 2))
5319            (func (export \"roundtrip\") (param i32 i32) (result i32)
5320              local.get 0
5321              local.get 1
5322              i32.store
5323              local.get 0
5324              i32.load)
5325            (func (export \"grow\") (param i32) (result i32)
5326              local.get 0
5327              memory.grow))";
5328        let reg_module = lower_wat(source);
5329        let imports = crate::runtime::Imports::new().memory(
5330            "env",
5331            "mem",
5332            crate::types::MemType {
5333                limits: crate::types::Limits {
5334                    min: 1,
5335                    max: Some(2),
5336                },
5337            },
5338        );
5339        let store = crate::runtime::Store::instantiate_with_imports(&reg_module, &imports).unwrap();
5340
5341        assert_eq!(
5342            crate::runtime::execute_export(
5343                &reg_module,
5344                &store,
5345                "roundtrip",
5346                &[
5347                    crate::runtime::Value::I32(8),
5348                    crate::runtime::Value::I32(42)
5349                ],
5350            ),
5351            Ok(vec![crate::runtime::Value::I32(42)])
5352        );
5353        // Grow to the provided max (2 pages) succeeds; beyond fails with -1.
5354        assert_eq!(
5355            crate::runtime::execute_export(
5356                &reg_module,
5357                &store,
5358                "grow",
5359                &[crate::runtime::Value::I32(1)],
5360            ),
5361            Ok(vec![crate::runtime::Value::I32(1)])
5362        );
5363        assert_eq!(
5364            crate::runtime::execute_export(
5365                &reg_module,
5366                &store,
5367                "grow",
5368                &[crate::runtime::Value::I32(1)],
5369            ),
5370            Ok(vec![crate::runtime::Value::I32(-1)])
5371        );
5372    }
5373
5374    #[test]
5375    fn execute_imported_global_and_chained_init() {
5376        let source = "(module
5377            (import \"env\" \"base\" (global $base i32))
5378            (global $derived i32 (i32.add (global.get $base) (i32.const 8)))
5379            (func (export \"get_derived\") (result i32)
5380              global.get $derived))";
5381        let reg_module = lower_wat(source);
5382        let imports = crate::runtime::Imports::new().global(
5383            "env",
5384            "base",
5385            crate::types::GlobalType {
5386                val_type: ValType::Num(NumType::I32),
5387                mutability: crate::types::Mutability::Const,
5388            },
5389            crate::runtime::Value::I32(42),
5390        );
5391        let store = crate::runtime::Store::instantiate_with_imports(&reg_module, &imports).unwrap();
5392
5393        // The init expr read the imported global's value at instantiation.
5394        assert_eq!(
5395            crate::runtime::execute_export(&reg_module, &store, "get_derived", &[]),
5396            Ok(vec![crate::runtime::Value::I32(50)])
5397        );
5398    }
5399
5400    #[test]
5401    fn execute_imported_mutable_global() {
5402        let source = "(module
5403            (import \"env\" \"counter\" (global $counter (mut i32)))
5404            (func (export \"bump\") (result i32)
5405              global.get $counter
5406              i32.const 1
5407              i32.add
5408              global.set $counter
5409              global.get $counter))";
5410        let reg_module = lower_wat(source);
5411        let imports = crate::runtime::Imports::new().global(
5412            "env",
5413            "counter",
5414            crate::types::GlobalType {
5415                val_type: ValType::Num(NumType::I32),
5416                mutability: crate::types::Mutability::Var,
5417            },
5418            crate::runtime::Value::I32(41),
5419        );
5420        let store = crate::runtime::Store::instantiate_with_imports(&reg_module, &imports).unwrap();
5421        assert_eq!(
5422            crate::runtime::execute_export(&reg_module, &store, "bump", &[]),
5423            Ok(vec![crate::runtime::Value::I32(42)])
5424        );
5425    }
5426
5427    #[test]
5428    fn execute_imported_table() {
5429        let source = "(module
5430            (import \"env\" \"tbl\" (table 2 funcref))
5431            (type $t (func (result i32)))
5432            (func $f (type $t) i32.const 42)
5433            (elem declare func $f)
5434            (func (export \"go\") (param i32) (result i32)
5435              local.get 0
5436              call_indirect (type $t))
5437            (func (export \"seed\")
5438              i32.const 1
5439              ref.func $f
5440              table.set))";
5441        let reg_module = lower_wat(source);
5442        let imports = crate::runtime::Imports::new().table(
5443            "env",
5444            "tbl",
5445            crate::types::TableType {
5446                elem: crate::types::RefType::FuncRef,
5447                limits: crate::types::Limits { min: 2, max: None },
5448                init: None,
5449            },
5450        );
5451        let store = crate::runtime::Store::instantiate_with_imports(&reg_module, &imports).unwrap();
5452
5453        crate::runtime::execute_export(&reg_module, &store, "seed", &[]).unwrap();
5454        assert_eq!(
5455            crate::runtime::execute_export(
5456                &reg_module,
5457                &store,
5458                "go",
5459                &[crate::runtime::Value::I32(1)],
5460            ),
5461            Ok(vec![crate::runtime::Value::I32(42)])
5462        );
5463    }
5464
5465    #[test]
5466    fn start_function_runs_at_instantiation() {
5467        let source = "(module
5468            (global $g (mut i32) (i32.const 0))
5469            (func $init
5470              i32.const 42
5471              global.set $g)
5472            (func (export \"get\") (result i32)
5473              global.get $g)
5474            (start $init))";
5475        let reg_module = lower_wat(source);
5476        let mut store = crate::runtime::Store::instantiate(&reg_module).unwrap();
5477        store.run_start(&reg_module).unwrap();
5478        assert_eq!(
5479            crate::runtime::execute_export(&reg_module, &store, "get", &[]),
5480            Ok(vec![crate::runtime::Value::I32(42)])
5481        );
5482    }
5483
5484    #[test]
5485    fn instantiate_missing_and_mismatched_state_imports() {
5486        let source = "(module
5487            (import \"env\" \"mem\" (memory 2))
5488            (import \"env\" \"g\" (global i32)))";
5489        let reg_module = lower_wat(source);
5490
5491        // Missing providers fail eagerly at instantiation.
5492        let error =
5493            crate::runtime::Store::instantiate(&reg_module).expect_err("expected UnknownImport");
5494        assert_eq!(
5495            error.kind,
5496            crate::runtime::RuntimeErrorKind::UnknownImport {
5497                module: "env".into(),
5498                name: "mem".into(),
5499            }
5500        );
5501
5502        // Provided limits below declared min fail matching.
5503        let imports = crate::runtime::Imports::new()
5504            .memory(
5505                "env",
5506                "mem",
5507                crate::types::MemType {
5508                    limits: crate::types::Limits { min: 1, max: None },
5509                },
5510            )
5511            .global(
5512                "env",
5513                "g",
5514                crate::types::GlobalType {
5515                    val_type: ValType::Num(NumType::I32),
5516                    mutability: crate::types::Mutability::Const,
5517                },
5518                crate::runtime::Value::I32(0),
5519            );
5520        let error = crate::runtime::Store::instantiate_with_imports(&reg_module, &imports)
5521            .expect_err("expected ImportTypeMismatch");
5522        assert!(matches!(
5523            error.kind,
5524            crate::runtime::RuntimeErrorKind::ImportTypeMismatch { .. }
5525        ));
5526    }
5527
5528    #[test]
5529    fn linked_function_call_across_modules() {
5530        // Module A exports "add"; module B imports it as ("a", "add") and
5531        // calls it through a link_func bridge.
5532        let module_a = alloc::rc::Rc::new(lower_wat(
5533            "(module
5534               (func (export \"add\") (param i32 i32) (result i32)
5535                 local.get 0 local.get 1 i32.add))",
5536        ));
5537        let store_a = alloc::rc::Rc::new(core::cell::RefCell::new(
5538            crate::runtime::Store::instantiate(&module_a).unwrap(),
5539        ));
5540        let func_idx = store_a.borrow().export_func("add").unwrap();
5541
5542        let module_b = lower_wat(
5543            "(module
5544               (import \"a\" \"add\" (func $add (param i32 i32) (result i32)))
5545               (func (export \"go\") (param i32 i32) (result i32)
5546                 local.get 0 local.get 1 call $add))",
5547        );
5548        let mut store_b = crate::runtime::Store::instantiate(&module_b).unwrap();
5549        let ty = module_b.imported_funcs[0].ty.clone();
5550        store_b
5551            .register_host_func(
5552                "a",
5553                "add",
5554                crate::runtime::link_func(module_a.clone(), store_a.clone(), func_idx, ty),
5555            )
5556            .unwrap();
5557
5558        assert_eq!(
5559            crate::runtime::execute_export(
5560                &module_b,
5561                &store_b,
5562                "go",
5563                &[
5564                    crate::runtime::Value::I32(20),
5565                    crate::runtime::Value::I32(22)
5566                ],
5567            ),
5568            Ok(vec![crate::runtime::Value::I32(42)])
5569        );
5570    }
5571
5572    #[test]
5573    fn shared_memory_across_modules() {
5574        // A exports its memory; B imports the same handle and writes through
5575        // it; the write is visible to A.
5576        let module_a = alloc::rc::Rc::new(lower_wat(
5577            "(module
5578               (memory (export \"mem\") 1)
5579               (func (export \"load\") (param i32) (result i32)
5580                 local.get 0 i32.load))",
5581        ));
5582        let store_a = alloc::rc::Rc::new(core::cell::RefCell::new(
5583            crate::runtime::Store::instantiate(&module_a).unwrap(),
5584        ));
5585        let (mem_ty, shared_mem) = store_a.borrow().export_memory("mem").unwrap();
5586
5587        let module_b = lower_wat(
5588            "(module
5589               (import \"a\" \"mem\" (memory 1))
5590               (func (export \"store\") (param i32 i32)
5591                 local.get 0 local.get 1 i32.store))",
5592        );
5593        let imports = crate::runtime::Imports::new().shared_memory("a", "mem", mem_ty, shared_mem);
5594        let store_b = crate::runtime::Store::instantiate_with_imports(&module_b, &imports).unwrap();
5595
5596        crate::runtime::execute_export(
5597            &module_b,
5598            &store_b,
5599            "store",
5600            &[
5601                crate::runtime::Value::I32(16),
5602                crate::runtime::Value::I32(42),
5603            ],
5604        )
5605        .unwrap();
5606
5607        // A reads the value B wrote into the shared memory.
5608        let store_a_mut = store_a.borrow_mut();
5609        assert_eq!(
5610            crate::runtime::execute_export(
5611                &module_a,
5612                &store_a_mut,
5613                "load",
5614                &[crate::runtime::Value::I32(16)],
5615            ),
5616            Ok(vec![crate::runtime::Value::I32(42)])
5617        );
5618    }
5619
5620    #[test]
5621    fn shared_global_across_modules() {
5622        let module_a = alloc::rc::Rc::new(lower_wat(
5623            "(module
5624               (global (export \"counter\") (mut i32) (i32.const 41))
5625               (func (export \"get\") (result i32) global.get 0))",
5626        ));
5627        let store_a = alloc::rc::Rc::new(core::cell::RefCell::new(
5628            crate::runtime::Store::instantiate(&module_a).unwrap(),
5629        ));
5630        let (global_ty, shared_global) = store_a.borrow().export_global("counter").unwrap();
5631
5632        let module_b = lower_wat(
5633            "(module
5634               (import \"a\" \"counter\" (global (mut i32)))
5635               (func (export \"bump\") (result i32)
5636                 global.get 0
5637                 i32.const 1
5638                 i32.add
5639                 global.set 0
5640                 global.get 0))",
5641        );
5642        let imports =
5643            crate::runtime::Imports::new().shared_global("a", "counter", global_ty, shared_global);
5644        let store_b = crate::runtime::Store::instantiate_with_imports(&module_b, &imports).unwrap();
5645
5646        crate::runtime::execute_export(&module_b, &store_b, "bump", &[]).unwrap();
5647
5648        let store_a_mut = store_a.borrow_mut();
5649        assert_eq!(
5650            crate::runtime::execute_export(&module_a, &store_a_mut, "get", &[]),
5651            Ok(vec![crate::runtime::Value::I32(42)])
5652        );
5653    }
5654
5655    #[test]
5656    fn reentrant_store_call_fails_gracefully() {
5657        // Calling a linked function while its store is already executing
5658        // fails with ReentrantStore instead of deadlocking on the RefCell.
5659        let module_a = alloc::rc::Rc::new(lower_wat("(module (func (export \"noop\")))"));
5660        let store_a = alloc::rc::Rc::new(core::cell::RefCell::new(
5661            crate::runtime::Store::instantiate(&module_a).unwrap(),
5662        ));
5663        let func_idx = store_a.borrow().export_func("noop").unwrap();
5664        let mut guard = crate::runtime::link_func(
5665            module_a,
5666            store_a.clone(),
5667            func_idx,
5668            crate::types::FuncType {
5669                params: vec![],
5670                results: vec![],
5671            },
5672        );
5673
5674        let _hold = store_a.borrow_mut(); // A is "executing"
5675        let error = guard.call(&[]).expect_err("expected ReentrantStore");
5676        assert_eq!(error.kind, crate::runtime::RuntimeErrorKind::ReentrantStore);
5677    }
5678
5679    #[test]
5680    fn lower_else_without_if_is_an_error() {
5681        // (func i32.const 1 else end) — else outside an if frame.
5682        let bytes = [
5683            0x00, 0x61, 0x73, 0x6d, // magic
5684            0x01, 0x00, 0x00, 0x00, // version
5685            0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // type: [] -> []
5686            0x03, 0x02, 0x01, 0x00, // function type 0
5687            0x0a, 0x05, 0x01, 0x03, 0x00, // one body, no locals
5688            0x05, // else
5689            0x0b, // end
5690        ];
5691        let module = Module::decode(&bytes).unwrap();
5692        // The validator rejects it; if it reaches lowering, lowering must
5693        // reject it too.
5694        let error = module.lower().unwrap_err();
5695        assert!(matches!(
5696            error.kind,
5697            LowerErrorKind::Validation(_) | LowerErrorKind::UnexpectedElse
5698        ));
5699    }
5700}