Skip to main content

baedeker_core/runtime/
mod.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Minimal register-IR execution core.
5//!
6//! This is the first Phase 2 runtime slice: execute straight-line lowered IR
7//! independently from validation. Broader control flow, calls, memory, tables,
8//! traps, and host integration are added in later checkpoints.
9
10use alloc::{string::String, vec::Vec};
11
12mod store;
13mod table;
14
15pub mod gpu;
16pub mod host;
17pub mod verify;
18
19pub use table::Table;
20
21pub use host::{HostFunction, link_func};
22pub use store::{Imports, LinkGroup, PAGE_SIZE, Store};
23
24use crate::lower::{
25    BinaryOp, LaneShape, Reg, RegFunc, RegInstr, RegModule, RegOp, RegTerm, UnaryOp, V128BinaryKind,
26};
27use crate::types::{FuncIdx, MemArg, NumType, RefType, TableIdx, ValType};
28
29/// A runtime WebAssembly value.
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum Value {
32    I32(i32),
33    I64(i64),
34    F32(f32),
35    F64(f64),
36    /// A reference value: either null or an `(instance, function)` pair
37    /// identifying a function in some instance. Instance identity makes
38    /// funcref values meaningful across linked modules; instance 0 is the
39    /// default for unlinked execution.
40    FuncRef(Option<(u32, u32)>),
41    /// An external reference value from the host: either null or a
42    /// host-assigned index.
43    ExternRef(Option<u32>),
44    /// A 128-bit vector, stored as raw little-endian bytes; lane
45    /// interpretation happens per operation.
46    V128([u8; 16]),
47}
48
49impl Value {
50    fn val_type(self) -> ValType {
51        match self {
52            Value::I32(_) => ValType::Num(NumType::I32),
53            Value::I64(_) => ValType::Num(NumType::I64),
54            Value::F32(_) => ValType::Num(NumType::F32),
55            Value::F64(_) => ValType::Num(NumType::F64),
56            Value::FuncRef(_) => ValType::Ref(RefType::FuncRef),
57            Value::ExternRef(_) => ValType::Ref(RefType::ExternRef),
58            Value::V128(_) => ValType::Vec(crate::types::VecType::V128),
59        }
60    }
61}
62
63/// Runtime execution error.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct RuntimeError {
66    pub kind: RuntimeErrorKind,
67}
68
69/// Specific runtime execution failures.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum RuntimeErrorKind {
72    ArityMismatch {
73        expected: usize,
74        found: usize,
75    },
76    TypeMismatch {
77        expected: ValType,
78        found: ValType,
79    },
80    Trap(RuntimeTrap),
81    UninitializedLocal {
82        local: u32,
83    },
84    UninitializedRegister {
85        reg: Reg,
86    },
87    UnknownRegister {
88        reg: Reg,
89    },
90    UnknownExport {
91        name: String,
92    },
93    ExportedFunctionNotLowered {
94        func: u32,
95    },
96    UnknownFunction {
97        func: u32,
98    },
99    UnknownImport {
100        module: String,
101        name: String,
102    },
103    ImportTypeMismatch {
104        module: String,
105        name: String,
106    },
107    ReentrantStore,
108    UnknownInstance {
109        instance: u32,
110    },
111    ResourceLimitExceeded {
112        what: &'static str,
113    },
114    /// The store's instruction fuel budget ran out (see `Store::set_fuel`).
115    FuelExhausted,
116    /// A host function returned a failure (FFI or embedding-layer error).
117    HostError {
118        message: String,
119    },
120    ImportedFunctionCallUnsupported {
121        func: u32,
122    },
123    UnknownMemory {
124        memory: u32,
125    },
126    UnknownDataSegment {
127        data: u32,
128    },
129    UnknownGlobal {
130        global: u32,
131    },
132    UnknownTable {
133        table: u32,
134    },
135    UnknownElem {
136        elem: u32,
137    },
138    UnknownType {
139        type_idx: u32,
140    },
141    ImportedMemoryAccessUnsupported {
142        memory: u32,
143    },
144    ImportedGlobalAccessUnsupported {
145        global: u32,
146    },
147    ImportedTableAccessUnsupported {
148        table: u32,
149    },
150    InvalidConstExpr,
151    InvalidLaneIndex {
152        lane: u8,
153    },
154    Gpu(crate::runtime::gpu::GpuError),
155    MissingStore,
156    MissingReturn,
157}
158
159/// WebAssembly runtime traps surfaced by the interpreter.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum RuntimeTrap {
162    Unreachable,
163    CallStackExhausted,
164    OutOfBoundsMemoryAccess,
165    OutOfBoundsTableAccess,
166    UndefinedElement,
167    UninitializedElement,
168    IndirectCallTypeMismatch,
169    IntegerDivideByZero,
170    IntegerOverflow,
171    InvalidConversionToInteger,
172    NullFunctionReference,
173    NullReference,
174}
175
176impl RuntimeTrap {
177    /// The canonical WAST assertion message for this trap.
178    pub fn wast_message(self) -> &'static str {
179        match self {
180            RuntimeTrap::Unreachable => "unreachable",
181            RuntimeTrap::CallStackExhausted => "call stack exhausted",
182            RuntimeTrap::OutOfBoundsMemoryAccess => "out of bounds memory access",
183            RuntimeTrap::OutOfBoundsTableAccess => "out of bounds table access",
184            RuntimeTrap::UndefinedElement => "undefined element",
185            RuntimeTrap::UninitializedElement => "uninitialized element",
186            RuntimeTrap::IndirectCallTypeMismatch => "indirect call type mismatch",
187            RuntimeTrap::IntegerDivideByZero => "integer divide by zero",
188            RuntimeTrap::IntegerOverflow => "integer overflow",
189            RuntimeTrap::InvalidConversionToInteger => "invalid conversion to integer",
190            RuntimeTrap::NullFunctionReference => "null function reference",
191            RuntimeTrap::NullReference => "null reference",
192        }
193    }
194}
195
196/// Execute an exported lowered function by name.
197pub fn execute_export(
198    module: &RegModule,
199    store: &Store,
200    name: &str,
201    args: &[Value],
202) -> Result<Vec<Value>, RuntimeError> {
203    let export = module
204        .exports
205        .iter()
206        .find(|export| export.name == name)
207        .ok_or_else(|| RuntimeError {
208            kind: RuntimeErrorKind::UnknownExport { name: name.into() },
209        })?;
210
211    let func_idx = match export.desc {
212        crate::lower::RegExportDesc::Func(idx) => idx,
213        _ => {
214            return Err(RuntimeError {
215                kind: RuntimeErrorKind::UnknownExport { name: name.into() },
216            });
217        }
218    };
219    // Re-exported imported functions dispatch to the registered host
220    // function; defined functions execute in the interpreter.
221    if func_idx.0 < module.imported_func_count {
222        return store.call_host(func_idx.0, args);
223    }
224    let func = module
225        .funcs
226        .iter()
227        .find(|func| func.idx == func_idx)
228        .ok_or(RuntimeError {
229            kind: RuntimeErrorKind::ExportedFunctionNotLowered { func: func_idx.0 },
230        })?;
231
232    execute_func_in(Some(module), Some(store), func, args, 0)
233}
234
235/// Resolve and invoke a direct call target.
236fn execute_call(
237    module: Option<&RegModule>,
238    store: Option<&Store>,
239    callee_idx: &FuncIdx,
240    call_args: &[Value],
241    depth: usize,
242) -> Result<Vec<Value>, RuntimeError> {
243    let module = module.ok_or(RuntimeError {
244        kind: RuntimeErrorKind::UnknownFunction { func: callee_idx.0 },
245    })?;
246    if callee_idx.0 < module.imported_func_count {
247        // Imported function: dispatch to a registered host function (lazy
248        // import resolution).
249        let store = store.ok_or(RuntimeError {
250            kind: RuntimeErrorKind::MissingStore,
251        })?;
252        return store.call_host(callee_idx.0, call_args);
253    }
254    let callee = module
255        .funcs
256        .iter()
257        .find(|func| func.idx == *callee_idx)
258        .ok_or(RuntimeError {
259            kind: RuntimeErrorKind::UnknownFunction { func: callee_idx.0 },
260        })?;
261    execute_func_in(Some(module), store, callee, call_args, depth + 1)
262}
263
264/// Resolve and invoke an indirect call target through a table.
265#[allow(clippy::too_many_arguments)]
266fn execute_call_indirect(
267    module: Option<&RegModule>,
268    store: Option<&Store>,
269    type_idx: &crate::types::TypeIdx,
270    table: &TableIdx,
271    idx: u32,
272    call_args: &[Value],
273    depth: usize,
274) -> Result<Vec<Value>, RuntimeError> {
275    let module = module.ok_or(RuntimeError {
276        kind: RuntimeErrorKind::UnknownFunction { func: 0 },
277    })?;
278    let store = store.ok_or(RuntimeError {
279        kind: RuntimeErrorKind::MissingStore,
280    })?;
281    let target = store
282        .with_table(table.0, |table| table.get(idx))
283        .ok_or(RuntimeError {
284            kind: RuntimeErrorKind::UnknownTable { table: table.0 },
285        })?
286        .ok_or(trap(RuntimeTrap::UndefinedElement))?;
287    let Value::FuncRef(func_idx) = target else {
288        return Err(RuntimeError {
289            kind: RuntimeErrorKind::TypeMismatch {
290                expected: ValType::Ref(RefType::FuncRef),
291                found: target.val_type(),
292            },
293        });
294    };
295    let Some((instance_id, func_idx)) = func_idx else {
296        return Err(trap(RuntimeTrap::UninitializedElement));
297    };
298    execute_funcref(
299        module,
300        store,
301        type_idx,
302        instance_id,
303        func_idx,
304        call_args,
305        depth,
306    )
307}
308
309/// Execute a `call_ref`: the function reference comes from the stack.
310fn execute_call_ref(
311    module: Option<&RegModule>,
312    store: Option<&Store>,
313    type_idx: &crate::types::TypeIdx,
314    target: Value,
315    call_args: &[Value],
316    depth: usize,
317) -> Result<Vec<Value>, RuntimeError> {
318    let module = module.ok_or(RuntimeError {
319        kind: RuntimeErrorKind::UnknownFunction { func: 0 },
320    })?;
321    let store = store.ok_or(RuntimeError {
322        kind: RuntimeErrorKind::MissingStore,
323    })?;
324    let Value::FuncRef(func_idx) = target else {
325        return Err(RuntimeError {
326            kind: RuntimeErrorKind::TypeMismatch {
327                expected: ValType::Ref(RefType::FuncRef),
328                found: target.val_type(),
329            },
330        });
331    };
332    let Some((instance_id, func_idx)) = func_idx else {
333        return Err(trap(RuntimeTrap::NullFunctionReference));
334    };
335    execute_funcref(
336        module,
337        store,
338        type_idx,
339        instance_id,
340        func_idx,
341        call_args,
342        depth,
343    )
344}
345
346/// Shared tail of `call_indirect`/`call_ref`: resolve `(instance_id,
347/// func_idx)` against the current instance or its link group, check the
348/// structural type, and execute (host dispatch for imported targets).
349#[allow(clippy::too_many_arguments)]
350fn execute_funcref(
351    module: &RegModule,
352    store: &Store,
353    type_idx: &crate::types::TypeIdx,
354    instance_id: u32,
355    func_idx: u32,
356    call_args: &[Value],
357    depth: usize,
358) -> Result<Vec<Value>, RuntimeError> {
359    if instance_id != store.instance_id() {
360        // Cross-instance funcref: resolve through the link group and execute
361        // against the owning instance (structural type check against the
362        // remote module's type entries).
363        let (module, target_store) = {
364            let group = store.link_group().ok_or(RuntimeError {
365                kind: RuntimeErrorKind::UnknownInstance {
366                    instance: instance_id,
367                },
368            })?;
369            let group = group.borrow();
370            let Some((module, store)) = group.get(&instance_id) else {
371                return Err(RuntimeError {
372                    kind: RuntimeErrorKind::UnknownInstance {
373                        instance: instance_id,
374                    },
375                });
376            };
377            (module.clone(), store.clone())
378        };
379        let expected = module.types.get(type_idx.0 as usize).ok_or(RuntimeError {
380            kind: RuntimeErrorKind::UnknownType {
381                type_idx: type_idx.0,
382            },
383        })?;
384        let actual = if func_idx < module.imported_func_count {
385            &module
386                .imported_funcs
387                .get(func_idx as usize)
388                .ok_or(RuntimeError {
389                    kind: RuntimeErrorKind::UnknownFunction { func: func_idx },
390                })?
391                .ty
392        } else {
393            let callee = module
394                .funcs
395                .iter()
396                .find(|func| func.idx.0 == func_idx)
397                .ok_or(RuntimeError {
398                    kind: RuntimeErrorKind::UnknownFunction { func: func_idx },
399                })?;
400            module
401                .types
402                .get(callee.type_idx.0 as usize)
403                .ok_or(RuntimeError {
404                    kind: RuntimeErrorKind::UnknownType {
405                        type_idx: callee.type_idx.0,
406                    },
407                })?
408        };
409        if expected != actual {
410            return Err(trap(RuntimeTrap::IndirectCallTypeMismatch));
411        }
412        let target_store = target_store.borrow();
413        if func_idx < module.imported_func_count {
414            return target_store.call_host(func_idx, call_args);
415        }
416        let callee = module
417            .funcs
418            .iter()
419            .find(|func| func.idx.0 == func_idx)
420            .ok_or(RuntimeError {
421                kind: RuntimeErrorKind::UnknownFunction { func: func_idx },
422            })?;
423        return execute_func_in(
424            Some(&module),
425            Some(&*target_store),
426            callee,
427            call_args,
428            depth + 1,
429        );
430    }
431    // Same-instance resolution below.
432    // Structural type check: the callee's type must match the declared
433    // one — for imported targets, the import declaration's type; for
434    // defined targets, the function's type entry.
435    let expected = module.types.get(type_idx.0 as usize).ok_or(RuntimeError {
436        kind: RuntimeErrorKind::UnknownType {
437            type_idx: type_idx.0,
438        },
439    })?;
440    if func_idx < module.imported_func_count {
441        let import_ty = &module
442            .imported_funcs
443            .get(func_idx as usize)
444            .ok_or(RuntimeError {
445                kind: RuntimeErrorKind::UnknownFunction { func: func_idx },
446            })?
447            .ty;
448        if expected != import_ty {
449            return Err(trap(RuntimeTrap::IndirectCallTypeMismatch));
450        }
451        // Indirect call targeting an imported function: host dispatch.
452        return store.call_host(func_idx, call_args);
453    }
454    let callee = module
455        .funcs
456        .iter()
457        .find(|func| func.idx.0 == func_idx)
458        .ok_or(RuntimeError {
459            kind: RuntimeErrorKind::UnknownFunction { func: func_idx },
460        })?;
461    let actual = module
462        .types
463        .get(callee.type_idx.0 as usize)
464        .ok_or(RuntimeError {
465            kind: RuntimeErrorKind::UnknownType {
466                type_idx: callee.type_idx.0,
467            },
468        })?;
469    if expected != actual {
470        return Err(trap(RuntimeTrap::IndirectCallTypeMismatch));
471    }
472    execute_func_in(Some(module), Some(store), callee, call_args, depth + 1)
473}
474
475/// Maximum call depth before the interpreter traps with stack exhaustion.
476/// Bounded so that unbounded recursion exhausts the interpreter before the
477/// host thread's stack does (test threads run with small stacks).
478const MAX_CALL_DEPTH: usize = 512;
479
480/// Execute a single lowered function with positional arguments.
481///
482/// Functions containing `call` instructions require module context; use
483/// [`execute_export`] for those (a bare `execute_func` call fails with
484/// [`RuntimeErrorKind::UnknownFunction`] on any `call`).
485pub fn execute_func(func: &RegFunc, args: &[Value]) -> Result<Vec<Value>, RuntimeError> {
486    execute_func_in(None, None, func, args, 0)
487}
488
489/// Execute a function with optional module context for resolving `call`
490/// targets and optional store context for memory/global access, tracking
491/// recursion depth for stack exhaustion.
492pub(crate) fn execute_func_in(
493    module: Option<&RegModule>,
494    store: Option<&Store>,
495    func: &RegFunc,
496    args: &[Value],
497    depth: usize,
498) -> Result<Vec<Value>, RuntimeError> {
499    if depth >= MAX_CALL_DEPTH {
500        return Err(trap(RuntimeTrap::CallStackExhausted));
501    }
502    if args.len() != func.params.len() {
503        return Err(RuntimeError {
504            kind: RuntimeErrorKind::ArityMismatch {
505                expected: func.params.len(),
506                found: args.len(),
507            },
508        });
509    }
510
511    for (&arg, &expected) in args.iter().zip(func.params.iter()) {
512        if !value_satisfies(expected, arg) {
513            return Err(RuntimeError {
514                kind: RuntimeErrorKind::TypeMismatch {
515                    expected,
516                    found: arg.val_type(),
517                },
518            });
519        }
520    }
521
522    let mut locals = alloc::vec![None; func.locals.len()];
523    for (idx, &arg) in args.iter().enumerate() {
524        locals[idx] = Some(arg);
525    }
526    // Non-parameter locals are zero-initialized per spec (null for
527    // nullable references).
528    for (slot, &ty) in locals.iter_mut().zip(func.locals.iter()).skip(args.len()) {
529        if slot.is_none() {
530            *slot = match ty {
531                ValType::Num(NumType::I32) => Some(Value::I32(0)),
532                ValType::Num(NumType::I64) => Some(Value::I64(0)),
533                ValType::Num(NumType::F32) => Some(Value::F32(0.0)),
534                ValType::Num(NumType::F64) => Some(Value::F64(0.0)),
535                ValType::Ref(ref_type) if ref_nullable(&ref_type) => {
536                    Some(ref_null_value(&ref_type))
537                }
538                ValType::Vec(_) => Some(Value::V128([0; 16])),
539                _ => None,
540            };
541        }
542    }
543
544    let mut registers = alloc::vec![None; func.reg_types.len()];
545
546    if func.blocks.is_empty() {
547        return Err(RuntimeError {
548            kind: RuntimeErrorKind::MissingReturn,
549        });
550    }
551
552    let mut block_idx: u32 = 0;
553    // Backstop against true infinite loops when no fuel budget is set (the
554    // coarse guard predates configurable fuel; with fuel, exhaustion is the
555    // honest `FuelExhausted` error instead of `MissingReturn`).
556    let max_iterations = 10_000_000;
557    let mut iteration: usize = 0;
558    loop {
559        iteration += 1;
560        if iteration > max_iterations {
561            return Err(RuntimeError {
562                kind: RuntimeErrorKind::MissingReturn,
563            });
564        }
565        if let Some(store) = store
566            && !store.charge_fuel()
567        {
568            return Err(RuntimeError {
569                kind: RuntimeErrorKind::FuelExhausted,
570            });
571        }
572        let block = func.blocks.get(block_idx as usize).ok_or(RuntimeError {
573            kind: RuntimeErrorKind::MissingReturn,
574        })?;
575
576        // Execute straight-line instructions in this block
577        for instr in &block.instrs {
578            if let Some(store) = store
579                && !store.charge_fuel()
580            {
581                return Err(RuntimeError {
582                    kind: RuntimeErrorKind::FuelExhausted,
583                });
584            }
585            if let RegOp::Call {
586                func: callee_idx,
587                args: arg_regs,
588                results,
589            } = &instr.op
590            {
591                let call_args = arg_regs
592                    .iter()
593                    .map(|&reg| get_reg(&registers, reg))
594                    .collect::<Result<Vec<_>, _>>()?;
595                let returned = execute_call(module, store, callee_idx, &call_args, depth)?;
596                for (&dst, value) in results.iter().zip(returned) {
597                    set_reg(&mut registers, dst, value)?;
598                }
599            } else if let RegOp::CallIndirect {
600                type_idx,
601                table,
602                index,
603                args: arg_regs,
604                results,
605            } = &instr.op
606            {
607                let idx = expect_addr(get_reg(&registers, *index)?)?;
608                let call_args = arg_regs
609                    .iter()
610                    .map(|&reg| get_reg(&registers, reg))
611                    .collect::<Result<Vec<_>, _>>()?;
612                let returned =
613                    execute_call_indirect(module, store, type_idx, table, idx, &call_args, depth)?;
614                for (&dst, value) in results.iter().zip(returned) {
615                    set_reg(&mut registers, dst, value)?;
616                }
617            } else if let RegOp::CallRef {
618                type_idx,
619                func,
620                args: arg_regs,
621                results,
622            } = &instr.op
623            {
624                let target = get_reg(&registers, *func)?;
625                let call_args = arg_regs
626                    .iter()
627                    .map(|&reg| get_reg(&registers, reg))
628                    .collect::<Result<Vec<_>, _>>()?;
629                let returned =
630                    execute_call_ref(module, store, type_idx, target, &call_args, depth)?;
631                for (&dst, value) in results.iter().zip(returned) {
632                    set_reg(&mut registers, dst, value)?;
633                }
634            } else {
635                execute_reg_op(store, &mut registers, &mut locals, instr)?;
636            }
637        }
638
639        // Follow the terminator
640        match &block.term {
641            RegTerm::Return { values } => {
642                let mut results = Vec::with_capacity(values.len());
643                for &reg in values {
644                    results.push(get_reg(&registers, reg)?);
645                }
646                return Ok(results);
647            }
648            RegTerm::IfFork {
649                cond,
650                then_block,
651                else_block,
652            } => {
653                let val = get_reg(&registers, *cond)?;
654                if let Value::I32(v) = val {
655                    if v != 0 {
656                        block_idx = *then_block;
657                    } else {
658                        block_idx = *else_block;
659                    }
660                } else {
661                    block_idx = *else_block;
662                }
663            }
664            RegTerm::Br { target_block, .. } => {
665                block_idx = *target_block;
666            }
667            RegTerm::BrIf {
668                cond, target_block, ..
669            } => {
670                let val = get_reg(&registers, *cond)?;
671                if let Value::I32(v) = val {
672                    if v != 0 {
673                        block_idx = *target_block;
674                    } else {
675                        block_idx += 1;
676                    }
677                } else {
678                    block_idx += 1;
679                }
680            }
681            RegTerm::BrIfNull {
682                value,
683                target_block,
684                ..
685            } => {
686                let val = get_reg(&registers, *value)?;
687                if is_null_ref(&val) {
688                    block_idx = *target_block;
689                } else {
690                    block_idx += 1;
691                }
692            }
693            RegTerm::BrIfNonNull {
694                value,
695                target_block,
696                ..
697            } => {
698                let val = get_reg(&registers, *value)?;
699                if is_null_ref(&val) {
700                    block_idx += 1;
701                } else {
702                    block_idx = *target_block;
703                }
704            }
705            RegTerm::BrTable {
706                index,
707                targets,
708                default,
709                ..
710            } => {
711                let val = get_reg(&registers, *index)?;
712                // The index is read as u32: negative i32 values are large
713                // and fall through to the default target.
714                let idx = match val {
715                    Value::I32(v) => v as u32 as usize,
716                    _ => targets.len(),
717                };
718                block_idx = if idx < targets.len() {
719                    targets[idx]
720                } else {
721                    *default
722                };
723            }
724            RegTerm::Fallthrough => {
725                block_idx += 1;
726                if block_idx as usize >= func.blocks.len() {
727                    return Err(RuntimeError {
728                        kind: RuntimeErrorKind::MissingReturn,
729                    });
730                }
731            }
732            RegTerm::Trap => {
733                return Err(trap(RuntimeTrap::Unreachable));
734            }
735        }
736    }
737}
738
739fn execute_reg_op(
740    store: Option<&Store>,
741    registers: &mut [Option<Value>],
742    locals: &mut [Option<Value>],
743    instr: &RegInstr,
744) -> Result<(), RuntimeError> {
745    match &instr.op {
746        RegOp::LocalGet { dst, local } => {
747            let value = locals
748                .get(local.0 as usize)
749                .and_then(|value| *value)
750                .ok_or(RuntimeError {
751                    kind: RuntimeErrorKind::UninitializedLocal { local: local.0 },
752                })?;
753            set_reg(registers, *dst, value)?;
754        }
755        RegOp::LocalSet { local, value } | RegOp::LocalTee { local, value } => {
756            let value = get_reg(registers, *value)?;
757            let slot = locals.get_mut(local.0 as usize).ok_or(RuntimeError {
758                kind: RuntimeErrorKind::UninitializedLocal { local: local.0 },
759            })?;
760            *slot = Some(value);
761        }
762        RegOp::Drop { value } => {
763            get_reg(registers, *value)?;
764        }
765        RegOp::I32Const { dst, value } => {
766            set_reg(registers, *dst, Value::I32(*value))?;
767        }
768        RegOp::I64Const { dst, value } => {
769            set_reg(registers, *dst, Value::I64(*value))?;
770        }
771        RegOp::F32Const { dst, value } => {
772            set_reg(registers, *dst, Value::F32(*value))?;
773        }
774        RegOp::F64Const { dst, value } => {
775            set_reg(registers, *dst, Value::F64(*value))?;
776        }
777        RegOp::Unary { op, dst, value } => execute_unary_op(registers, *op, *dst, *value)?,
778        RegOp::Binary { op, dst, lhs, rhs } => execute_binary_op(registers, *op, *dst, *lhs, *rhs)?,
779        RegOp::Copy { dst, src } => {
780            let value = get_reg(registers, *src)?;
781            set_reg(registers, *dst, value)?;
782        }
783        RegOp::Select { dst, v1, v2, cond } => {
784            let cond_value = get_reg(registers, *cond)?;
785            let taken = matches!(cond_value, Value::I32(v) if v != 0);
786            let value = get_reg(registers, if taken { *v1 } else { *v2 })?;
787            set_reg(registers, *dst, value)?;
788        }
789        RegOp::Call { func, .. } => {
790            // Calls are handled in `execute_func_in`, which has module
791            // context; reaching this arm means there was none.
792            return Err(RuntimeError {
793                kind: RuntimeErrorKind::UnknownFunction { func: func.0 },
794            });
795        }
796        RegOp::CallIndirect { .. } => {
797            // Same, but without a direct function index to report.
798            return Err(RuntimeError {
799                kind: RuntimeErrorKind::UnknownFunction { func: 0 },
800            });
801        }
802        RegOp::CallRef { .. } => {
803            // Same: needs module context from `execute_func_in`.
804            return Err(RuntimeError {
805                kind: RuntimeErrorKind::UnknownFunction { func: 0 },
806            });
807        }
808        RegOp::RefAsNonNull { dst, value } => {
809            let val = get_reg(registers, *value)?;
810            if is_null_ref(&val) {
811                return Err(trap(RuntimeTrap::NullReference));
812            }
813            set_reg(registers, *dst, val)?;
814        }
815        RegOp::Load {
816            op,
817            dst,
818            addr,
819            memarg,
820        } => {
821            let addr = expect_addr(get_reg(registers, *addr)?)?;
822            let store = require_store(store)?;
823            let mem = store.shared_memory(memarg.memory.0).ok_or(RuntimeError {
824                kind: RuntimeErrorKind::UnknownMemory {
825                    memory: memarg.memory.0,
826                },
827            })?;
828            let mem = mem.borrow();
829            let range = memory_bounds(&mem, memarg, addr, op.byte_width())?;
830            let bytes = &mem[range];
831            let value = match op {
832                crate::lower::LoadOp::I32 => {
833                    Value::I32(i32::from_le_bytes(bytes.try_into().expect("width checked")))
834                }
835                crate::lower::LoadOp::I64 => {
836                    Value::I64(i64::from_le_bytes(bytes.try_into().expect("width checked")))
837                }
838                crate::lower::LoadOp::F32 => Value::F32(f32::from_bits(u32::from_le_bytes(
839                    bytes.try_into().expect("width checked"),
840                ))),
841                crate::lower::LoadOp::F64 => Value::F64(f64::from_bits(u64::from_le_bytes(
842                    bytes.try_into().expect("width checked"),
843                ))),
844                crate::lower::LoadOp::I32Load8S => Value::I32(bytes[0] as i8 as i32),
845                crate::lower::LoadOp::I32Load8U => Value::I32(bytes[0] as i32),
846                crate::lower::LoadOp::I32Load16S => {
847                    Value::I32(i16::from_le_bytes(bytes.try_into().expect("width checked")) as i32)
848                }
849                crate::lower::LoadOp::I32Load16U => {
850                    Value::I32(u16::from_le_bytes(bytes.try_into().expect("width checked")) as i32)
851                }
852                crate::lower::LoadOp::I64Load8S => Value::I64(bytes[0] as i8 as i64),
853                crate::lower::LoadOp::I64Load8U => Value::I64(bytes[0] as i64),
854                crate::lower::LoadOp::I64Load16S => {
855                    Value::I64(i16::from_le_bytes(bytes.try_into().expect("width checked")) as i64)
856                }
857                crate::lower::LoadOp::I64Load16U => {
858                    Value::I64(u16::from_le_bytes(bytes.try_into().expect("width checked")) as i64)
859                }
860                crate::lower::LoadOp::I64Load32S => {
861                    Value::I64(i32::from_le_bytes(bytes.try_into().expect("width checked")) as i64)
862                }
863                crate::lower::LoadOp::I64Load32U => {
864                    Value::I64(u32::from_le_bytes(bytes.try_into().expect("width checked")) as i64)
865                }
866                crate::lower::LoadOp::V128 => Value::V128(bytes.try_into().expect("width checked")),
867            };
868            set_reg(registers, *dst, value)?;
869        }
870        RegOp::Store {
871            op,
872            addr,
873            value,
874            memarg,
875        } => {
876            let addr = expect_addr(get_reg(registers, *addr)?)?;
877            let value = get_reg(registers, *value)?;
878            let store = require_store(store)?;
879            let mem = store.shared_memory(memarg.memory.0).ok_or(RuntimeError {
880                kind: RuntimeErrorKind::UnknownMemory {
881                    memory: memarg.memory.0,
882                },
883            })?;
884            let mut mem = mem.borrow_mut();
885            let range = memory_bounds(&mem, memarg, addr, op.byte_width())?;
886            let bytes = &mut mem[range];
887            match (op, value) {
888                (crate::lower::StoreOp::I32, Value::I32(v)) => {
889                    bytes.copy_from_slice(&v.to_le_bytes());
890                }
891                (crate::lower::StoreOp::I64, Value::I64(v)) => {
892                    bytes.copy_from_slice(&v.to_le_bytes());
893                }
894                (crate::lower::StoreOp::F32, Value::F32(v)) => {
895                    bytes.copy_from_slice(&v.to_bits().to_le_bytes());
896                }
897                (crate::lower::StoreOp::F64, Value::F64(v)) => {
898                    bytes.copy_from_slice(&v.to_bits().to_le_bytes());
899                }
900                (crate::lower::StoreOp::I32Store8, Value::I32(v)) => {
901                    bytes[0] = v as u8;
902                }
903                (crate::lower::StoreOp::I64Store8, Value::I64(v)) => {
904                    bytes[0] = v as u8;
905                }
906                (crate::lower::StoreOp::I32Store16, Value::I32(v)) => {
907                    bytes.copy_from_slice(&(v as u16).to_le_bytes());
908                }
909                (crate::lower::StoreOp::I64Store16, Value::I64(v)) => {
910                    bytes.copy_from_slice(&(v as u16).to_le_bytes());
911                }
912                (crate::lower::StoreOp::I64Store32, Value::I64(v)) => {
913                    bytes.copy_from_slice(&(v as u32).to_le_bytes());
914                }
915                (crate::lower::StoreOp::V128, Value::V128(v)) => {
916                    bytes.copy_from_slice(&v);
917                }
918                (op, value) => {
919                    return Err(RuntimeError {
920                        kind: RuntimeErrorKind::TypeMismatch {
921                            expected: op.value_type(),
922                            found: value.val_type(),
923                        },
924                    });
925                }
926            }
927        }
928        RegOp::GlobalGet { dst, global } => {
929            let store = require_store(store)?;
930            let value = store.global(global.0).ok_or(RuntimeError {
931                kind: RuntimeErrorKind::UnknownGlobal { global: global.0 },
932            })?;
933            set_reg(registers, *dst, value)?;
934        }
935        RegOp::GlobalSet { global, value } => {
936            let store = require_store(store)?;
937            let value = get_reg(registers, *value)?;
938            store.set_global(global.0, value).ok_or(RuntimeError {
939                kind: RuntimeErrorKind::UnknownGlobal { global: global.0 },
940            })?;
941        }
942        RegOp::MemorySize { dst, memory } => {
943            let store = require_store(store)?;
944            let pages = store
945                .with_memory(memory.0, |mem| mem.len())
946                .ok_or(RuntimeError {
947                    kind: RuntimeErrorKind::UnknownMemory { memory: memory.0 },
948                })?
949                / PAGE_SIZE;
950            set_reg(registers, *dst, Value::I32(pages as i32))?;
951        }
952        RegOp::MemoryGrow { dst, memory, delta } => {
953            let store = require_store(store)?;
954            let delta = expect_addr(get_reg(registers, *delta)?)?;
955            let max_pages = store
956                .memory_type(memory.0)
957                .and_then(|ty| ty.limits.max)
958                .unwrap_or(65536) as usize;
959            let mem = store.shared_memory(memory.0).ok_or(RuntimeError {
960                kind: RuntimeErrorKind::UnknownMemory { memory: memory.0 },
961            })?;
962            let result = {
963                let mut mem = mem.borrow_mut();
964                let old_pages = mem.len() / PAGE_SIZE;
965                match old_pages.checked_add(delta as usize) {
966                    Some(new_pages)
967                        if new_pages <= max_pages && grow_memory_fallible(&mut mem, new_pages) =>
968                    {
969                        old_pages as i32
970                    }
971                    _ => -1,
972                }
973            };
974            set_reg(registers, *dst, Value::I32(result))?;
975        }
976        RegOp::MemoryInit {
977            memory,
978            data,
979            dst,
980            src,
981            count,
982        } => {
983            let store = require_store(store)?;
984            let dst = expect_addr(get_reg(registers, *dst)?)? as usize;
985            let src = expect_addr(get_reg(registers, *src)?)? as usize;
986            let count = expect_addr(get_reg(registers, *count)?)? as usize;
987            // Bounds-check and copy the segment range before borrowing memory.
988            let (bytes, dst_end) = {
989                let segment = store
990                    .with_data(data.0, |segment| segment.cloned())
991                    .ok_or(RuntimeError {
992                        kind: RuntimeErrorKind::UnknownDataSegment { data: data.0 },
993                    })?
994                    .ok_or(trap(RuntimeTrap::OutOfBoundsMemoryAccess))?;
995                let segment = &segment;
996                let (Some(src_end), Some(dst_end)) =
997                    (src.checked_add(count), dst.checked_add(count))
998                else {
999                    return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1000                };
1001                if src_end > segment.len() {
1002                    return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1003                }
1004                (segment[src..src_end].to_vec(), dst_end)
1005            };
1006            store
1007                .with_memory_mut(memory.0, |mem| {
1008                    if dst_end > mem.len() {
1009                        return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1010                    }
1011                    mem[dst..dst_end].copy_from_slice(&bytes);
1012                    Ok(())
1013                })
1014                .ok_or(RuntimeError {
1015                    kind: RuntimeErrorKind::UnknownMemory { memory: memory.0 },
1016                })??;
1017        }
1018        RegOp::DataDrop { data } => {
1019            let store = require_store(store)?;
1020            store.drop_data(data.0).ok_or(RuntimeError {
1021                kind: RuntimeErrorKind::UnknownDataSegment { data: data.0 },
1022            })?;
1023        }
1024        RegOp::MemoryCopy {
1025            dst_memory,
1026            src_memory,
1027            dst,
1028            src,
1029            count,
1030        } => {
1031            let store = require_store(store)?;
1032            let dst = expect_addr(get_reg(registers, *dst)?)? as usize;
1033            let src = expect_addr(get_reg(registers, *src)?)? as usize;
1034            let count = expect_addr(get_reg(registers, *count)?)? as usize;
1035            // Bounds-check both ranges before copying (via a temporary, so
1036            // overlapping regions copy per spec).
1037            let src_len = store
1038                .with_memory(src_memory.0, |mem| mem.len())
1039                .ok_or(RuntimeError {
1040                    kind: RuntimeErrorKind::UnknownMemory {
1041                        memory: src_memory.0,
1042                    },
1043                })?;
1044            let dst_len = store
1045                .with_memory(dst_memory.0, |mem| mem.len())
1046                .ok_or(RuntimeError {
1047                    kind: RuntimeErrorKind::UnknownMemory {
1048                        memory: dst_memory.0,
1049                    },
1050                })?;
1051            let (Some(src_end), Some(dst_end)) = (src.checked_add(count), dst.checked_add(count))
1052            else {
1053                return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1054            };
1055            if src_end > src_len || dst_end > dst_len {
1056                return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1057            }
1058            let temp: Vec<u8> = store
1059                .with_memory(src_memory.0, |mem| mem[src..src_end].to_vec())
1060                .ok_or(RuntimeError {
1061                    kind: RuntimeErrorKind::UnknownMemory {
1062                        memory: src_memory.0,
1063                    },
1064                })?;
1065            store
1066                .with_memory_mut(dst_memory.0, |mem| {
1067                    mem[dst..dst_end].copy_from_slice(&temp);
1068                })
1069                .ok_or(RuntimeError {
1070                    kind: RuntimeErrorKind::UnknownMemory {
1071                        memory: dst_memory.0,
1072                    },
1073                })?;
1074        }
1075        RegOp::MemoryFill {
1076            memory,
1077            dst,
1078            value,
1079            count,
1080        } => {
1081            let store = require_store(store)?;
1082            let dst = expect_addr(get_reg(registers, *dst)?)? as usize;
1083            let count = expect_addr(get_reg(registers, *count)?)? as usize;
1084            let value = expect_addr(get_reg(registers, *value)?)? as u8;
1085            store
1086                .with_memory_mut(memory.0, |mem| {
1087                    let Some(end) = dst.checked_add(count) else {
1088                        return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1089                    };
1090                    if end > mem.len() {
1091                        return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1092                    }
1093                    mem[dst..end].fill(value);
1094                    Ok(())
1095                })
1096                .ok_or(RuntimeError {
1097                    kind: RuntimeErrorKind::UnknownMemory { memory: memory.0 },
1098                })??;
1099        }
1100        RegOp::TableGet { dst, table, index } => {
1101            let store = require_store(store)?;
1102            let idx = expect_addr(get_reg(registers, *index)?)?;
1103            let value = store
1104                .with_table(table.0, |table| table.get(idx))
1105                .ok_or(RuntimeError {
1106                    kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1107                })?
1108                .ok_or(trap(RuntimeTrap::OutOfBoundsTableAccess))?;
1109            set_reg(registers, *dst, value)?;
1110        }
1111        RegOp::TableSet {
1112            table,
1113            index,
1114            value,
1115        } => {
1116            let store = require_store(store)?;
1117            let idx = expect_addr(get_reg(registers, *index)?)?;
1118            let value = get_reg(registers, *value)?;
1119            store
1120                .with_table_mut(table.0, |table| {
1121                    if table.set(idx, value) {
1122                        Ok(())
1123                    } else {
1124                        Err(trap(RuntimeTrap::OutOfBoundsTableAccess))
1125                    }
1126                })
1127                .ok_or(RuntimeError {
1128                    kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1129                })??;
1130        }
1131        RegOp::TableSize { dst, table } => {
1132            let store = require_store(store)?;
1133            let len = store
1134                .with_table(table.0, |table| table.len())
1135                .ok_or(RuntimeError {
1136                    kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1137                })?;
1138            set_reg(registers, *dst, Value::I32(len as i32))?;
1139        }
1140        RegOp::TableGrow {
1141            dst,
1142            table,
1143            value,
1144            delta,
1145        } => {
1146            let store = require_store(store)?;
1147            let delta = expect_addr(get_reg(registers, *delta)?)?;
1148            let value = get_reg(registers, *value)?;
1149            let tbl = store.shared_table(table.0).ok_or(RuntimeError {
1150                kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1151            })?;
1152            let result = {
1153                let mut tbl = tbl.borrow_mut();
1154                match tbl.grow(delta, value) {
1155                    Some(old) => old as i32,
1156                    None => -1,
1157                }
1158            };
1159            set_reg(registers, *dst, Value::I32(result))?;
1160        }
1161        RegOp::TableFill {
1162            table,
1163            dst,
1164            value,
1165            count,
1166        } => {
1167            let store = require_store(store)?;
1168            let dst = expect_addr(get_reg(registers, *dst)?)?;
1169            let count = expect_addr(get_reg(registers, *count)?)?;
1170            let value = get_reg(registers, *value)?;
1171            store
1172                .with_table_mut(table.0, |tbl| {
1173                    if tbl.fill(dst, value, count) {
1174                        Ok(())
1175                    } else {
1176                        Err(trap(RuntimeTrap::OutOfBoundsTableAccess))
1177                    }
1178                })
1179                .ok_or(RuntimeError {
1180                    kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1181                })??;
1182        }
1183        RegOp::TableCopy {
1184            dst_table,
1185            src_table,
1186            dst,
1187            src,
1188            count,
1189        } => {
1190            let store = require_store(store)?;
1191            let dst = expect_addr(get_reg(registers, *dst)?)?;
1192            let src = expect_addr(get_reg(registers, *src)?)?;
1193            let count = expect_addr(get_reg(registers, *count)?)?;
1194            // Bounds-check both ranges before copying (via a temporary, so
1195            // overlapping copies within one table behave per spec).
1196            let temp: Vec<Value> = store
1197                .with_table(src_table.0, |table| table.read_slice(src, count))
1198                .ok_or(RuntimeError {
1199                    kind: RuntimeErrorKind::UnknownTable { table: src_table.0 },
1200                })?
1201                .ok_or(trap(RuntimeTrap::OutOfBoundsTableAccess))?;
1202            store
1203                .with_table_mut(dst_table.0, |table| table.write_slice(dst, &temp))
1204                .ok_or(RuntimeError {
1205                    kind: RuntimeErrorKind::UnknownTable { table: dst_table.0 },
1206                })?
1207                .then_some(())
1208                .ok_or(trap(RuntimeTrap::OutOfBoundsTableAccess))?;
1209        }
1210        RegOp::TableInit {
1211            table,
1212            elem,
1213            dst,
1214            src,
1215            count,
1216        } => {
1217            let store = require_store(store)?;
1218            let dst = expect_addr(get_reg(registers, *dst)?)? as usize;
1219            let src = expect_addr(get_reg(registers, *src)?)? as usize;
1220            let count = expect_addr(get_reg(registers, *count)?)? as usize;
1221            let segment = store
1222                .with_elem(elem.0, |segment| segment.cloned())
1223                .ok_or(RuntimeError {
1224                    kind: RuntimeErrorKind::UnknownElem { elem: elem.0 },
1225                })?
1226                .ok_or(trap(RuntimeTrap::OutOfBoundsTableAccess))?;
1227            let segment = &segment;
1228            let table_len = store
1229                .with_table(table.0, |table| table.len())
1230                .ok_or(RuntimeError {
1231                    kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1232                })?;
1233            let (Some(src_end), Some(dst_end)) = (src.checked_add(count), dst.checked_add(count))
1234            else {
1235                return Err(trap(RuntimeTrap::OutOfBoundsTableAccess));
1236            };
1237            if src_end > segment.len() || dst_end as u32 > table_len {
1238                return Err(trap(RuntimeTrap::OutOfBoundsTableAccess));
1239            }
1240            let temp: Vec<Value> = segment[src..src_end].to_vec();
1241            store
1242                .with_table_mut(table.0, |table| {
1243                    if table.write_slice(dst as u32, &temp) {
1244                        Ok(())
1245                    } else {
1246                        Err(trap(RuntimeTrap::OutOfBoundsTableAccess))
1247                    }
1248                })
1249                .ok_or(RuntimeError {
1250                    kind: RuntimeErrorKind::UnknownTable { table: table.0 },
1251                })??;
1252        }
1253        RegOp::ElemDrop { elem } => {
1254            let store = require_store(store)?;
1255            store.drop_elem(elem.0).ok_or(RuntimeError {
1256                kind: RuntimeErrorKind::UnknownElem { elem: elem.0 },
1257            })?;
1258        }
1259        RegOp::RefNull { dst, ref_type } => {
1260            set_reg(registers, *dst, ref_null_value(ref_type))?;
1261        }
1262        RegOp::RefFunc { dst, func } => {
1263            let store = require_store(store)?;
1264            set_reg(
1265                registers,
1266                *dst,
1267                Value::FuncRef(Some((store.instance_id(), func.0))),
1268            )?;
1269        }
1270        RegOp::RefIsNull { dst, value } => {
1271            let value = get_reg(registers, *value)?;
1272            set_reg(registers, *dst, Value::I32(is_null_ref(&value) as i32))?;
1273        }
1274        RegOp::V128Const { dst, value } => {
1275            set_reg(registers, *dst, Value::V128(*value))?;
1276        }
1277        RegOp::V128Splat { dst, shape, src } => {
1278            let scalar = get_reg(registers, *src)?;
1279            let bytes = splat_bytes(*shape, scalar)?;
1280            set_reg(registers, *dst, Value::V128(bytes))?;
1281        }
1282        RegOp::V128ExtractLane {
1283            dst,
1284            shape,
1285            src,
1286            lane,
1287        } => {
1288            let Value::V128(bytes) = get_reg(registers, *src)? else {
1289                return Err(RuntimeError {
1290                    kind: RuntimeErrorKind::TypeMismatch {
1291                        expected: ValType::Vec(crate::types::VecType::V128),
1292                        found: get_reg(registers, *src)?.val_type(),
1293                    },
1294                });
1295            };
1296            let value = extract_lane(*shape, &bytes, *lane)?;
1297            set_reg(registers, *dst, value)?;
1298        }
1299        RegOp::V128ReplaceLane {
1300            dst,
1301            shape,
1302            vec,
1303            scalar,
1304            lane,
1305        } => {
1306            let Value::V128(mut bytes) = get_reg(registers, *vec)? else {
1307                return Err(RuntimeError {
1308                    kind: RuntimeErrorKind::TypeMismatch {
1309                        expected: ValType::Vec(crate::types::VecType::V128),
1310                        found: get_reg(registers, *vec)?.val_type(),
1311                    },
1312                });
1313            };
1314            let scalar = get_reg(registers, *scalar)?;
1315            replace_lane(*shape, &mut bytes, *lane, scalar)?;
1316            set_reg(registers, *dst, Value::V128(bytes))?;
1317        }
1318        RegOp::V128Binary {
1319            shape,
1320            kind,
1321            dst,
1322            lhs,
1323            rhs,
1324        } => {
1325            let Value::V128(lhs_bytes) = get_reg(registers, *lhs)? else {
1326                return Err(RuntimeError {
1327                    kind: RuntimeErrorKind::TypeMismatch {
1328                        expected: ValType::Vec(crate::types::VecType::V128),
1329                        found: get_reg(registers, *lhs)?.val_type(),
1330                    },
1331                });
1332            };
1333            let Value::V128(rhs_bytes) = get_reg(registers, *rhs)? else {
1334                return Err(RuntimeError {
1335                    kind: RuntimeErrorKind::TypeMismatch {
1336                        expected: ValType::Vec(crate::types::VecType::V128),
1337                        found: get_reg(registers, *rhs)?.val_type(),
1338                    },
1339                });
1340            };
1341            let bytes = v128_binary(*shape, *kind, &lhs_bytes, &rhs_bytes);
1342            set_reg(registers, *dst, Value::V128(bytes))?;
1343        }
1344        RegOp::V128Not { dst, src } => {
1345            let Value::V128(bytes) = get_reg(registers, *src)? else {
1346                return Err(RuntimeError {
1347                    kind: RuntimeErrorKind::TypeMismatch {
1348                        expected: ValType::Vec(crate::types::VecType::V128),
1349                        found: get_reg(registers, *src)?.val_type(),
1350                    },
1351                });
1352            };
1353            let mut out = [0u8; 16];
1354            for (dst_byte, src_byte) in out.iter_mut().zip(bytes.iter()) {
1355                *dst_byte = !src_byte;
1356            }
1357            set_reg(registers, *dst, Value::V128(out))?;
1358        }
1359    }
1360    Ok(())
1361}
1362
1363fn require_store(store: Option<&Store>) -> Result<&Store, RuntimeError> {
1364    store.ok_or(RuntimeError {
1365        kind: RuntimeErrorKind::MissingStore,
1366    })
1367}
1368
1369/// Whether a runtime value satisfies a value type at the boundary: exact
1370/// for numerics/vectors, nullable-aware subtyping for references (a
1371/// non-null value satisfies `(ref T)`; abstract funcref/externref accept
1372/// matching heap families at any nullability).
1373fn value_satisfies(expected: ValType, value: Value) -> bool {
1374    use crate::types::HeapType;
1375    match (expected, value) {
1376        (ValType::Ref(expected_ref), value) => match (expected_ref, value) {
1377            (RefType::FuncRef, Value::FuncRef(_)) => true,
1378            (RefType::ExternRef, Value::ExternRef(_)) => true,
1379            (RefType::Typed { nullable, heap }, value) => {
1380                let non_null = match value {
1381                    Value::FuncRef(inner) => inner.is_some(),
1382                    Value::ExternRef(inner) => inner.is_some(),
1383                    _ => return false,
1384                };
1385                if !nullable && !non_null {
1386                    return false;
1387                }
1388                matches!(
1389                    (heap, value),
1390                    (HeapType::Func | HeapType::Type(_), Value::FuncRef(_))
1391                        | (HeapType::Extern, Value::ExternRef(_))
1392                )
1393            }
1394            _ => false,
1395        },
1396        (expected, value) => expected == value.val_type(),
1397    }
1398}
1399
1400/// WASM min: NaN propagates as the canonical NaN; -0 is smaller than +0.
1401fn wasm_f32_min(a: f32, b: f32) -> f32 {
1402    if a.is_nan() || b.is_nan() {
1403        return f32::from_bits(0x7fc0_0000);
1404    }
1405    if a == b {
1406        if a == 0.0 && (a.is_sign_negative() || b.is_sign_negative()) {
1407            return -0.0;
1408        }
1409        return a;
1410    }
1411    if a < b { a } else { b }
1412}
1413
1414/// WASM max: NaN propagates as the canonical NaN; +0 is larger than -0.
1415fn wasm_f32_max(a: f32, b: f32) -> f32 {
1416    if a.is_nan() || b.is_nan() {
1417        return f32::from_bits(0x7fc0_0000);
1418    }
1419    if a == b {
1420        if a == 0.0 && (a.is_sign_positive() || b.is_sign_positive()) {
1421            return 0.0;
1422        }
1423        return a;
1424    }
1425    if a > b { a } else { b }
1426}
1427
1428fn wasm_f64_min(a: f64, b: f64) -> f64 {
1429    if a.is_nan() || b.is_nan() {
1430        return f64::from_bits(0x7ff8_0000_0000_0000);
1431    }
1432    if a == b {
1433        if a == 0.0 && (a.is_sign_negative() || b.is_sign_negative()) {
1434            return -0.0;
1435        }
1436        return a;
1437    }
1438    if a < b { a } else { b }
1439}
1440
1441fn wasm_f64_max(a: f64, b: f64) -> f64 {
1442    if a.is_nan() || b.is_nan() {
1443        return f64::from_bits(0x7ff8_0000_0000_0000);
1444    }
1445    if a == b {
1446        if a == 0.0 && (a.is_sign_positive() || b.is_sign_positive()) {
1447            return 0.0;
1448        }
1449        return a;
1450    }
1451    if a > b { a } else { b }
1452}
1453
1454/// Grow a memory to `new_pages`, returning `false` when the allocation
1455/// fails (huge growth must yield -1, not abort the process).
1456fn grow_memory_fallible(mem: &mut Vec<u8>, new_pages: usize) -> bool {
1457    let additional = new_pages
1458        .saturating_mul(PAGE_SIZE)
1459        .saturating_sub(mem.len());
1460    if mem.try_reserve(additional).is_err() {
1461        return false;
1462    }
1463    mem.resize(new_pages * PAGE_SIZE, 0);
1464    true
1465}
1466
1467/// Grow a table to `new` entries, returning `false` when the allocation
1468/// fails.
1469/// Bounds-check `addr + memarg.offset` over `width` bytes against a memory,
1470/// returning the valid byte range.
1471fn memory_bounds(
1472    mem: &[u8],
1473    memarg: &MemArg,
1474    addr: u32,
1475    width: usize,
1476) -> Result<core::ops::Range<usize>, RuntimeError> {
1477    let ea = addr as u64 + memarg.offset as u64;
1478    let end = ea
1479        .checked_add(width as u64)
1480        .ok_or(trap(RuntimeTrap::OutOfBoundsMemoryAccess))?;
1481    if end > mem.len() as u64 {
1482        return Err(trap(RuntimeTrap::OutOfBoundsMemoryAccess));
1483    }
1484    Ok(ea as usize..end as usize)
1485}
1486
1487/// Whether a reference value is null (`ref.null`).
1488fn is_null_ref(value: &Value) -> bool {
1489    matches!(value, Value::FuncRef(None) | Value::ExternRef(None))
1490}
1491
1492/// Extract an i32 memory address operand as u32.
1493fn expect_addr(value: Value) -> Result<u32, RuntimeError> {
1494    match value {
1495        Value::I32(value) => Ok(value as u32),
1496        other => Err(RuntimeError {
1497            kind: RuntimeErrorKind::TypeMismatch {
1498                expected: ValType::Num(NumType::I32),
1499                found: other.val_type(),
1500            },
1501        }),
1502    }
1503}
1504
1505/// Whether references of this type default to null (nullable refs).
1506fn ref_nullable(ref_type: &RefType) -> bool {
1507    match ref_type {
1508        RefType::FuncRef | RefType::ExternRef => true,
1509        RefType::Typed { nullable, .. } => *nullable,
1510    }
1511}
1512
1513/// The null value for a reference type (funcref null vs externref null).
1514fn ref_null_value(ref_type: &RefType) -> Value {
1515    match ref_type {
1516        RefType::ExternRef
1517        | RefType::Typed {
1518            heap: crate::types::HeapType::Extern,
1519            ..
1520        } => Value::ExternRef(None),
1521        _ => Value::FuncRef(None),
1522    }
1523}
1524
1525/// Lane-wise integer arithmetic (wrapping, per spec).
1526macro_rules! lane_int_op {
1527    ($kind:expr, $a:expr, $b:expr, $ty:ty) => {{
1528        let (a, b) = ($a as $ty, $b as $ty);
1529        match $kind {
1530            V128BinaryKind::Add => a.wrapping_add(b),
1531            V128BinaryKind::Sub => a.wrapping_sub(b),
1532            V128BinaryKind::Mul => a.wrapping_mul(b),
1533            V128BinaryKind::Div => a.wrapping_div(b),
1534            _ => unreachable!("bitwise kinds handled separately"),
1535        }
1536    }};
1537}
1538
1539/// Lane-wise float arithmetic.
1540macro_rules! lane_float_op {
1541    ($kind:expr, $a:expr, $b:expr, $ty:ty) => {{
1542        let (a, b) = ($a as $ty, $b as $ty);
1543        match $kind {
1544            V128BinaryKind::Add => a + b,
1545            V128BinaryKind::Sub => a - b,
1546            V128BinaryKind::Mul => a * b,
1547            V128BinaryKind::Div => a / b,
1548            _ => unreachable!("bitwise kinds handled separately"),
1549        }
1550    }};
1551}
1552
1553/// The little-endian bytes of a scalar at a shape's lane width (int lanes
1554/// truncate from i32, like splat).
1555fn scalar_lane_bytes(shape: LaneShape, scalar: Value) -> Result<[u8; 8], RuntimeError> {
1556    let mut buf = [0u8; 8];
1557    match (shape, scalar) {
1558        (LaneShape::I8x16, Value::I32(v)) => buf[0] = v as u8,
1559        (LaneShape::I16x8, Value::I32(v)) => buf[..2].copy_from_slice(&(v as u16).to_le_bytes()),
1560        (LaneShape::I32x4, Value::I32(v)) => buf[..4].copy_from_slice(&v.to_le_bytes()),
1561        (LaneShape::I64x2, Value::I64(v)) => buf.copy_from_slice(&v.to_le_bytes()),
1562        (LaneShape::F32x4, Value::F32(v)) => {
1563            buf[..4].copy_from_slice(&v.to_bits().to_le_bytes());
1564        }
1565        (LaneShape::F64x2, Value::F64(v)) => buf.copy_from_slice(&v.to_bits().to_le_bytes()),
1566        (shape, value) => {
1567            return Err(RuntimeError {
1568                kind: RuntimeErrorKind::TypeMismatch {
1569                    expected: shape.scalar_type(),
1570                    found: value.val_type(),
1571                },
1572            });
1573        }
1574    }
1575    Ok(buf)
1576}
1577
1578/// Broadcast a scalar into all 16 bytes per shape.
1579fn splat_bytes(shape: LaneShape, scalar: Value) -> Result<[u8; 16], RuntimeError> {
1580    let lane = scalar_lane_bytes(shape, scalar)?;
1581    let width = shape.lane_width();
1582    let mut out = [0u8; 16];
1583    for chunk in out.chunks_exact_mut(width) {
1584        chunk.copy_from_slice(&lane[..width]);
1585    }
1586    Ok(out)
1587}
1588
1589/// Read one lane as a scalar Value.
1590fn extract_lane(shape: LaneShape, bytes: &[u8; 16], lane: u8) -> Result<Value, RuntimeError> {
1591    let width = shape.lane_width();
1592    let start = lane as usize * width;
1593    if start + width > 16 {
1594        return Err(RuntimeError {
1595            kind: RuntimeErrorKind::InvalidLaneIndex { lane },
1596        });
1597    }
1598    let lane_bytes = &bytes[start..start + width];
1599    Ok(match shape {
1600        LaneShape::I8x16 => Value::I32(lane_bytes[0] as i8 as i32),
1601        LaneShape::I16x8 => {
1602            Value::I32(i16::from_le_bytes(lane_bytes.try_into().expect("width")) as i32)
1603        }
1604        LaneShape::I32x4 => Value::I32(i32::from_le_bytes(lane_bytes.try_into().expect("width"))),
1605        LaneShape::I64x2 => Value::I64(i64::from_le_bytes(lane_bytes.try_into().expect("width"))),
1606        LaneShape::F32x4 => Value::F32(f32::from_bits(u32::from_le_bytes(
1607            lane_bytes.try_into().expect("width"),
1608        ))),
1609        LaneShape::F64x2 => Value::F64(f64::from_bits(u64::from_le_bytes(
1610            lane_bytes.try_into().expect("width"),
1611        ))),
1612    })
1613}
1614
1615/// Write a scalar into one lane in place.
1616fn replace_lane(
1617    shape: LaneShape,
1618    bytes: &mut [u8; 16],
1619    lane: u8,
1620    scalar: Value,
1621) -> Result<(), RuntimeError> {
1622    let width = shape.lane_width();
1623    let start = lane as usize * width;
1624    if start + width > 16 {
1625        return Err(RuntimeError {
1626            kind: RuntimeErrorKind::InvalidLaneIndex { lane },
1627        });
1628    }
1629    let lane_bytes = scalar_lane_bytes(shape, scalar)?;
1630    bytes[start..start + width].copy_from_slice(&lane_bytes[..width]);
1631    Ok(())
1632}
1633
1634/// Lane-wise (or bitwise) binary execution over 16-byte vectors.
1635fn v128_binary(shape: LaneShape, kind: V128BinaryKind, lhs: &[u8; 16], rhs: &[u8; 16]) -> [u8; 16] {
1636    let mut out = [0u8; 16];
1637    match kind {
1638        V128BinaryKind::And => {
1639            for i in 0..16 {
1640                out[i] = lhs[i] & rhs[i];
1641            }
1642        }
1643        V128BinaryKind::Or => {
1644            for i in 0..16 {
1645                out[i] = lhs[i] | rhs[i];
1646            }
1647        }
1648        V128BinaryKind::Xor => {
1649            for i in 0..16 {
1650                out[i] = lhs[i] ^ rhs[i];
1651            }
1652        }
1653        _ => {
1654            let width = shape.lane_width();
1655            for ((dst_lane, lhs_lane), rhs_lane) in out
1656                .chunks_exact_mut(width)
1657                .zip(lhs.chunks_exact(width))
1658                .zip(rhs.chunks_exact(width))
1659            {
1660                apply_lane_binary(shape, kind, dst_lane, lhs_lane, rhs_lane);
1661            }
1662        }
1663    }
1664    out
1665}
1666
1667fn apply_lane_binary(
1668    shape: LaneShape,
1669    kind: V128BinaryKind,
1670    dst: &mut [u8],
1671    lhs: &[u8],
1672    rhs: &[u8],
1673) {
1674    match shape {
1675        LaneShape::I8x16 => {
1676            dst[0] = lane_int_op!(kind, lhs[0], rhs[0], i8) as u8;
1677        }
1678        LaneShape::I16x8 => {
1679            let a = i16::from_le_bytes(lhs.try_into().expect("width"));
1680            let b = i16::from_le_bytes(rhs.try_into().expect("width"));
1681            let result = lane_int_op!(kind, a, b, i16);
1682            dst.copy_from_slice(&result.to_le_bytes());
1683        }
1684        LaneShape::I32x4 => {
1685            let a = i32::from_le_bytes(lhs.try_into().expect("width"));
1686            let b = i32::from_le_bytes(rhs.try_into().expect("width"));
1687            let result = lane_int_op!(kind, a, b, i32);
1688            dst.copy_from_slice(&result.to_le_bytes());
1689        }
1690        LaneShape::I64x2 => {
1691            let a = i64::from_le_bytes(lhs.try_into().expect("width"));
1692            let b = i64::from_le_bytes(rhs.try_into().expect("width"));
1693            let result = lane_int_op!(kind, a, b, i64);
1694            dst.copy_from_slice(&result.to_le_bytes());
1695        }
1696        LaneShape::F32x4 => {
1697            let a = f32::from_bits(u32::from_le_bytes(lhs.try_into().expect("width")));
1698            let b = f32::from_bits(u32::from_le_bytes(rhs.try_into().expect("width")));
1699            let result = lane_float_op!(kind, a, b, f32);
1700            dst.copy_from_slice(&result.to_bits().to_le_bytes());
1701        }
1702        LaneShape::F64x2 => {
1703            let a = f64::from_bits(u64::from_le_bytes(lhs.try_into().expect("width")));
1704            let b = f64::from_bits(u64::from_le_bytes(rhs.try_into().expect("width")));
1705            let result = lane_float_op!(kind, a, b, f64);
1706            dst.copy_from_slice(&result.to_bits().to_le_bytes());
1707        }
1708    }
1709}
1710
1711fn set_reg(registers: &mut [Option<Value>], reg: Reg, value: Value) -> Result<(), RuntimeError> {
1712    let slot = registers.get_mut(reg.0 as usize).ok_or(RuntimeError {
1713        kind: RuntimeErrorKind::UnknownRegister { reg },
1714    })?;
1715    *slot = Some(value);
1716    Ok(())
1717}
1718
1719fn get_reg(registers: &[Option<Value>], reg: Reg) -> Result<Value, RuntimeError> {
1720    registers
1721        .get(reg.0 as usize)
1722        .copied()
1723        .ok_or(RuntimeError {
1724            kind: RuntimeErrorKind::UnknownRegister { reg },
1725        })?
1726        .ok_or(RuntimeError {
1727            kind: RuntimeErrorKind::UninitializedRegister { reg },
1728        })
1729}
1730
1731fn execute_unary_op(
1732    registers: &mut [Option<Value>],
1733    op: UnaryOp,
1734    dst: Reg,
1735    value: Reg,
1736) -> Result<(), RuntimeError> {
1737    match op {
1738        UnaryOp::I32Clz => {
1739            execute_i32_unary(registers, dst, value, |value| value.leading_zeros() as i32)
1740        }
1741        UnaryOp::I32Ctz => {
1742            execute_i32_unary(registers, dst, value, |value| value.trailing_zeros() as i32)
1743        }
1744        UnaryOp::I32Popcnt => {
1745            execute_i32_unary(registers, dst, value, |value| value.count_ones() as i32)
1746        }
1747        UnaryOp::I32Eqz => execute_i32_unary(registers, dst, value, |value| i32::from(value == 0)),
1748        UnaryOp::I32WrapI64 => execute_i64_to_i32(registers, dst, value, |value| value as i32),
1749        UnaryOp::I32Extend8S => {
1750            execute_i32_unary(registers, dst, value, |value| i32::from(value as i8))
1751        }
1752        UnaryOp::I32Extend16S => {
1753            execute_i32_unary(registers, dst, value, |value| i32::from(value as i16))
1754        }
1755        UnaryOp::I64Clz => {
1756            execute_i64_unary(registers, dst, value, |value| value.leading_zeros() as i64)
1757        }
1758        UnaryOp::I64Ctz => {
1759            execute_i64_unary(registers, dst, value, |value| value.trailing_zeros() as i64)
1760        }
1761        UnaryOp::I64Popcnt => {
1762            execute_i64_unary(registers, dst, value, |value| value.count_ones() as i64)
1763        }
1764        UnaryOp::I64Eqz => execute_i64_test(registers, dst, value, |value| i32::from(value == 0)),
1765        UnaryOp::I64ExtendI32S => execute_i32_to_i64(registers, dst, value, i64::from),
1766        UnaryOp::I64ExtendI32U => {
1767            execute_i32_to_i64(registers, dst, value, |value| i64::from(value as u32))
1768        }
1769        UnaryOp::I64Extend8S => {
1770            execute_i64_unary(registers, dst, value, |value| i64::from(value as i8))
1771        }
1772        UnaryOp::I64Extend16S => {
1773            execute_i64_unary(registers, dst, value, |value| i64::from(value as i16))
1774        }
1775        UnaryOp::I64Extend32S => {
1776            execute_i64_unary(registers, dst, value, |value| i64::from(value as i32))
1777        }
1778        UnaryOp::F32Neg => execute_f32_unary(registers, dst, value, |value| -value),
1779        UnaryOp::F32Abs => execute_f32_unary(registers, dst, value, libm::fabsf),
1780        UnaryOp::F32Sqrt => execute_f32_unary(registers, dst, value, libm::sqrtf),
1781        UnaryOp::F32Ceil => execute_f32_unary(registers, dst, value, libm::ceilf),
1782        UnaryOp::F32Floor => execute_f32_unary(registers, dst, value, libm::floorf),
1783        UnaryOp::F32Trunc => execute_f32_unary(registers, dst, value, libm::truncf),
1784        UnaryOp::F32Nearest => execute_f32_unary(registers, dst, value, libm::rintf),
1785        UnaryOp::F64Neg => execute_f64_unary(registers, dst, value, |value| -value),
1786        UnaryOp::F64Abs => execute_f64_unary(registers, dst, value, libm::fabs),
1787        UnaryOp::F64Sqrt => execute_f64_unary(registers, dst, value, libm::sqrt),
1788        UnaryOp::F64Ceil => execute_f64_unary(registers, dst, value, libm::ceil),
1789        UnaryOp::F64Floor => execute_f64_unary(registers, dst, value, libm::floor),
1790        UnaryOp::F64Trunc => execute_f64_unary(registers, dst, value, libm::trunc),
1791        UnaryOp::F64Nearest => execute_f64_unary(registers, dst, value, libm::rint),
1792        UnaryOp::I32TruncF32S => {
1793            execute_f32_to_i32_checked(registers, dst, value, |v| v as i32, false)
1794        }
1795        UnaryOp::I32TruncF32U => {
1796            execute_f32_to_i32_checked(registers, dst, value, |v| v as u32 as i32, true)
1797        }
1798        UnaryOp::I32TruncF64S => {
1799            execute_f64_to_i32_checked(registers, dst, value, |v| v as i32, false)
1800        }
1801        UnaryOp::I32TruncF64U => {
1802            execute_f64_to_i32_checked(registers, dst, value, |v| v as u32 as i32, true)
1803        }
1804        UnaryOp::I64TruncF32S => {
1805            execute_f32_to_i64_checked(registers, dst, value, |v| v as i64, false)
1806        }
1807        UnaryOp::I64TruncF32U => {
1808            execute_f32_to_i64_checked(registers, dst, value, |v| v as u64 as i64, true)
1809        }
1810        UnaryOp::I64TruncF64S => {
1811            execute_f64_to_i64_checked(registers, dst, value, |v| v as i64, false)
1812        }
1813        UnaryOp::I64TruncF64U => {
1814            execute_f64_to_i64_checked(registers, dst, value, |v| v as u64 as i64, true)
1815        }
1816        UnaryOp::F32ConvertI32S => execute_i32_to_f32(registers, dst, value, |v| v as f32),
1817        UnaryOp::F32ConvertI32U => execute_i32_to_f32(registers, dst, value, |v| (v as u32) as f32),
1818        UnaryOp::F32ConvertI64S => execute_i64_to_f32(registers, dst, value, |v| v as f32),
1819        UnaryOp::F32ConvertI64U => execute_i64_to_f32(registers, dst, value, |v| (v as u64) as f32),
1820        UnaryOp::F64ConvertI32S => execute_i32_to_f64(registers, dst, value, |v| v as f64),
1821        UnaryOp::F64ConvertI32U => execute_i32_to_f64(registers, dst, value, |v| (v as u32) as f64),
1822        UnaryOp::F64ConvertI64S => execute_i64_to_f64(registers, dst, value, |v| v as f64),
1823        UnaryOp::F64ConvertI64U => execute_i64_to_f64(registers, dst, value, |v| (v as u64) as f64),
1824        UnaryOp::F32DemoteF64 => execute_f64_to_f32(registers, dst, value, |v| v as f32),
1825        UnaryOp::F64PromoteF32 => execute_f32_to_f64(registers, dst, value, |v| v as f64),
1826        UnaryOp::I32ReinterpretF32 => {
1827            execute_f32_to_i32(registers, dst, value, |v| v.to_bits() as i32)
1828        }
1829        UnaryOp::F32ReinterpretI32 => {
1830            execute_i32_to_f32(registers, dst, value, |v| f32::from_bits(v as u32))
1831        }
1832        UnaryOp::I64ReinterpretF64 => execute_f64_to_i64(registers, dst, value, |v| {
1833            i64::from_ne_bytes(v.to_ne_bytes())
1834        }),
1835        UnaryOp::F64ReinterpretI64 => execute_i64_to_f64(registers, dst, value, |v| {
1836            f64::from_ne_bytes(v.to_ne_bytes())
1837        }),
1838        UnaryOp::I32TruncSatF32S => {
1839            execute_f32_to_i32_saturating(registers, dst, value, |v: f32| -> i32 {
1840                if v.is_nan() {
1841                    0
1842                } else if v >= (i32::MAX as f32) {
1843                    i32::MAX
1844                } else if v <= (i32::MIN as f32) {
1845                    i32::MIN
1846                } else {
1847                    v as i32
1848                }
1849            })
1850        }
1851        UnaryOp::I32TruncSatF32U => {
1852            execute_f32_to_i32_saturating(registers, dst, value, |v: f32| -> i32 {
1853                if v.is_nan() || v <= -1.0 {
1854                    0
1855                } else if v >= (u32::MAX as f32) {
1856                    u32::MAX as i32
1857                } else {
1858                    v as u32 as i32
1859                }
1860            })
1861        }
1862        UnaryOp::I32TruncSatF64S => {
1863            execute_f64_to_i32_saturating(registers, dst, value, |v: f64| -> i32 {
1864                if v.is_nan() {
1865                    0
1866                } else if v >= (i32::MAX as f64) {
1867                    i32::MAX
1868                } else if v <= (i32::MIN as f64) {
1869                    i32::MIN
1870                } else {
1871                    v as i32
1872                }
1873            })
1874        }
1875        UnaryOp::I32TruncSatF64U => {
1876            execute_f64_to_i32_saturating(registers, dst, value, |v: f64| -> i32 {
1877                if v.is_nan() || v <= -1.0 {
1878                    0
1879                } else if v >= (u32::MAX as f64) {
1880                    u32::MAX as i32
1881                } else {
1882                    v as u32 as i32
1883                }
1884            })
1885        }
1886        UnaryOp::I64TruncSatF32S => {
1887            execute_f32_to_i64_saturating(registers, dst, value, |v: f32| -> i64 {
1888                if v.is_nan() {
1889                    0
1890                } else if v >= (i64::MAX as f32) {
1891                    i64::MAX
1892                } else if v <= (i64::MIN as f32) {
1893                    i64::MIN
1894                } else {
1895                    v as i64
1896                }
1897            })
1898        }
1899        UnaryOp::I64TruncSatF32U => {
1900            execute_f32_to_i64_saturating(registers, dst, value, |v: f32| -> i64 {
1901                if v.is_nan() || v <= -1.0 {
1902                    0
1903                } else if v >= (u64::MAX as f32) {
1904                    u64::MAX as i64
1905                } else {
1906                    v as u64 as i64
1907                }
1908            })
1909        }
1910        UnaryOp::I64TruncSatF64S => {
1911            execute_f64_to_i64_saturating(registers, dst, value, |v: f64| -> i64 {
1912                if v.is_nan() {
1913                    0
1914                } else if v >= (i64::MAX as f64) {
1915                    i64::MAX
1916                } else if v <= (i64::MIN as f64) {
1917                    i64::MIN
1918                } else {
1919                    v as i64
1920                }
1921            })
1922        }
1923        UnaryOp::I64TruncSatF64U => {
1924            execute_f64_to_i64_saturating(registers, dst, value, |v: f64| -> i64 {
1925                if v.is_nan() || v <= -1.0 {
1926                    0
1927                } else if v >= (u64::MAX as f64) {
1928                    u64::MAX as i64
1929                } else {
1930                    v as u64 as i64
1931                }
1932            })
1933        }
1934    }
1935}
1936
1937fn execute_binary_op(
1938    registers: &mut [Option<Value>],
1939    op: BinaryOp,
1940    dst: Reg,
1941    lhs: Reg,
1942    rhs: Reg,
1943) -> Result<(), RuntimeError> {
1944    match op {
1945        BinaryOp::I32Add => {
1946            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs.wrapping_add(rhs))
1947        }
1948        BinaryOp::I32Sub => {
1949            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs.wrapping_sub(rhs))
1950        }
1951        BinaryOp::I32Mul => {
1952            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs.wrapping_mul(rhs))
1953        }
1954        BinaryOp::I32DivS => execute_i32_binary_checked(registers, dst, lhs, rhs, i32_div_s),
1955        BinaryOp::I32DivU => execute_i32_binary_checked(registers, dst, lhs, rhs, i32_div_u),
1956        BinaryOp::I32RemS => execute_i32_binary_checked(registers, dst, lhs, rhs, i32_rem_s),
1957        BinaryOp::I32RemU => execute_i32_binary_checked(registers, dst, lhs, rhs, i32_rem_u),
1958        BinaryOp::I32And => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs & rhs),
1959        BinaryOp::I32Or => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs | rhs),
1960        BinaryOp::I32Xor => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs ^ rhs),
1961        BinaryOp::I32Shl => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1962            lhs.wrapping_shl(rhs as u32)
1963        }),
1964        BinaryOp::I32ShrS => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1965            lhs >> ((rhs as u32) & 31)
1966        }),
1967        BinaryOp::I32ShrU => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1968            ((lhs as u32) >> ((rhs as u32) & 31)) as i32
1969        }),
1970        BinaryOp::I32Rotl => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1971            lhs.rotate_left(rhs as u32)
1972        }),
1973        BinaryOp::I32Rotr => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1974            lhs.rotate_right(rhs as u32)
1975        }),
1976        BinaryOp::I32Eq => {
1977            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| i32::from(lhs == rhs))
1978        }
1979        BinaryOp::I32Ne => {
1980            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| i32::from(lhs != rhs))
1981        }
1982        BinaryOp::I32LtS => {
1983            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| i32::from(lhs < rhs))
1984        }
1985        BinaryOp::I32LtU => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1986            i32::from((lhs as u32) < (rhs as u32))
1987        }),
1988        BinaryOp::I32GtS => {
1989            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| i32::from(lhs > rhs))
1990        }
1991        BinaryOp::I32GtU => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1992            i32::from((lhs as u32) > (rhs as u32))
1993        }),
1994        BinaryOp::I32LeS => {
1995            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| i32::from(lhs <= rhs))
1996        }
1997        BinaryOp::I32LeU => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
1998            i32::from((lhs as u32) <= (rhs as u32))
1999        }),
2000        BinaryOp::I32GeS => {
2001            execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| i32::from(lhs >= rhs))
2002        }
2003        BinaryOp::I32GeU => execute_i32_binary(registers, dst, lhs, rhs, |lhs, rhs| {
2004            i32::from((lhs as u32) >= (rhs as u32))
2005        }),
2006        BinaryOp::I64Add => {
2007            execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs.wrapping_add(rhs))
2008        }
2009        BinaryOp::I64Sub => {
2010            execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs.wrapping_sub(rhs))
2011        }
2012        BinaryOp::I64Mul => {
2013            execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs.wrapping_mul(rhs))
2014        }
2015        BinaryOp::I64DivS => execute_i64_binary_checked(registers, dst, lhs, rhs, i64_div_s),
2016        BinaryOp::I64DivU => execute_i64_binary_checked(registers, dst, lhs, rhs, i64_div_u),
2017        BinaryOp::I64RemS => execute_i64_binary_checked(registers, dst, lhs, rhs, i64_rem_s),
2018        BinaryOp::I64RemU => execute_i64_binary_checked(registers, dst, lhs, rhs, i64_rem_u),
2019        BinaryOp::I64And => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs & rhs),
2020        BinaryOp::I64Or => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs | rhs),
2021        BinaryOp::I64Xor => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs ^ rhs),
2022        BinaryOp::I64Shl => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| {
2023            lhs.wrapping_shl(rhs as u32)
2024        }),
2025        BinaryOp::I64ShrS => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| {
2026            lhs >> ((rhs as u32) & 63)
2027        }),
2028        BinaryOp::I64ShrU => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| {
2029            ((lhs as u64) >> ((rhs as u32) & 63)) as i64
2030        }),
2031        BinaryOp::I64Rotl => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| {
2032            lhs.rotate_left(rhs as u32)
2033        }),
2034        BinaryOp::I64Rotr => execute_i64_binary(registers, dst, lhs, rhs, |lhs, rhs| {
2035            lhs.rotate_right(rhs as u32)
2036        }),
2037        BinaryOp::I64Eq => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs == rhs),
2038        BinaryOp::I64Ne => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs != rhs),
2039        BinaryOp::I64LtS => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs < rhs),
2040        BinaryOp::I64LtU => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| {
2041            (lhs as u64) < (rhs as u64)
2042        }),
2043        BinaryOp::I64GtS => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs > rhs),
2044        BinaryOp::I64GtU => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| {
2045            (lhs as u64) > (rhs as u64)
2046        }),
2047        BinaryOp::I64LeS => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs <= rhs),
2048        BinaryOp::I64LeU => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| {
2049            (lhs as u64) <= (rhs as u64)
2050        }),
2051        BinaryOp::I64GeS => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs >= rhs),
2052        BinaryOp::I64GeU => execute_i64_compare(registers, dst, lhs, rhs, |lhs, rhs| {
2053            (lhs as u64) >= (rhs as u64)
2054        }),
2055        BinaryOp::F32Add => execute_f32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs + rhs),
2056        BinaryOp::F32Copysign => execute_f32_binary(registers, dst, lhs, rhs, libm::copysignf),
2057        BinaryOp::F32Sub => execute_f32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs - rhs),
2058        BinaryOp::F32Mul => execute_f32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs * rhs),
2059        BinaryOp::F32Div => execute_f32_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs / rhs),
2060        BinaryOp::F32Min => execute_f32_binary(registers, dst, lhs, rhs, wasm_f32_min),
2061        BinaryOp::F32Max => execute_f32_binary(registers, dst, lhs, rhs, wasm_f32_max),
2062        BinaryOp::F64Add => execute_f64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs + rhs),
2063        BinaryOp::F64Copysign => execute_f64_binary(registers, dst, lhs, rhs, libm::copysign),
2064        BinaryOp::F64Sub => execute_f64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs - rhs),
2065        BinaryOp::F64Mul => execute_f64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs * rhs),
2066        BinaryOp::F64Div => execute_f64_binary(registers, dst, lhs, rhs, |lhs, rhs| lhs / rhs),
2067        BinaryOp::F64Min => execute_f64_binary(registers, dst, lhs, rhs, wasm_f64_min),
2068        BinaryOp::F64Max => execute_f64_binary(registers, dst, lhs, rhs, wasm_f64_max),
2069        BinaryOp::F32Eq => execute_f32_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs == rhs),
2070        BinaryOp::F32Ne => execute_f32_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs != rhs),
2071        BinaryOp::F32Lt => execute_f32_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs < rhs),
2072        BinaryOp::F32Gt => execute_f32_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs > rhs),
2073        BinaryOp::F32Le => execute_f32_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs <= rhs),
2074        BinaryOp::F32Ge => execute_f32_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs >= rhs),
2075        BinaryOp::F64Eq => execute_f64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs == rhs),
2076        BinaryOp::F64Ne => execute_f64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs != rhs),
2077        BinaryOp::F64Lt => execute_f64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs < rhs),
2078        BinaryOp::F64Gt => execute_f64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs > rhs),
2079        BinaryOp::F64Le => execute_f64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs <= rhs),
2080        BinaryOp::F64Ge => execute_f64_compare(registers, dst, lhs, rhs, |lhs, rhs| lhs >= rhs),
2081    }
2082}
2083
2084fn execute_i32_binary(
2085    registers: &mut [Option<Value>],
2086    dst: Reg,
2087    lhs: Reg,
2088    rhs: Reg,
2089    op: impl FnOnce(i32, i32) -> i32,
2090) -> Result<(), RuntimeError> {
2091    let lhs = expect_i32(get_reg(registers, lhs)?)?;
2092    let rhs = expect_i32(get_reg(registers, rhs)?)?;
2093    set_reg(registers, dst, Value::I32(op(lhs, rhs)))
2094}
2095
2096fn execute_i32_binary_checked(
2097    registers: &mut [Option<Value>],
2098    dst: Reg,
2099    lhs: Reg,
2100    rhs: Reg,
2101    op: impl FnOnce(i32, i32) -> Result<i32, RuntimeError>,
2102) -> Result<(), RuntimeError> {
2103    let lhs = expect_i32(get_reg(registers, lhs)?)?;
2104    let rhs = expect_i32(get_reg(registers, rhs)?)?;
2105    set_reg(registers, dst, Value::I32(op(lhs, rhs)?))
2106}
2107
2108fn execute_i32_unary(
2109    registers: &mut [Option<Value>],
2110    dst: Reg,
2111    value: Reg,
2112    op: impl FnOnce(i32) -> i32,
2113) -> Result<(), RuntimeError> {
2114    let value = expect_i32(get_reg(registers, value)?)?;
2115    set_reg(registers, dst, Value::I32(op(value)))
2116}
2117
2118fn execute_i64_to_i32(
2119    registers: &mut [Option<Value>],
2120    dst: Reg,
2121    value: Reg,
2122    op: impl FnOnce(i64) -> i32,
2123) -> Result<(), RuntimeError> {
2124    let value = expect_i64(get_reg(registers, value)?)?;
2125    set_reg(registers, dst, Value::I32(op(value)))
2126}
2127
2128fn execute_i32_to_i64(
2129    registers: &mut [Option<Value>],
2130    dst: Reg,
2131    value: Reg,
2132    op: impl FnOnce(i32) -> i64,
2133) -> Result<(), RuntimeError> {
2134    let value = expect_i32(get_reg(registers, value)?)?;
2135    set_reg(registers, dst, Value::I64(op(value)))
2136}
2137
2138fn execute_i64_unary(
2139    registers: &mut [Option<Value>],
2140    dst: Reg,
2141    value: Reg,
2142    op: impl FnOnce(i64) -> i64,
2143) -> Result<(), RuntimeError> {
2144    let value = expect_i64(get_reg(registers, value)?)?;
2145    set_reg(registers, dst, Value::I64(op(value)))
2146}
2147
2148fn execute_i64_binary(
2149    registers: &mut [Option<Value>],
2150    dst: Reg,
2151    lhs: Reg,
2152    rhs: Reg,
2153    op: impl FnOnce(i64, i64) -> i64,
2154) -> Result<(), RuntimeError> {
2155    let lhs = expect_i64(get_reg(registers, lhs)?)?;
2156    let rhs = expect_i64(get_reg(registers, rhs)?)?;
2157    set_reg(registers, dst, Value::I64(op(lhs, rhs)))
2158}
2159
2160fn execute_i64_binary_checked(
2161    registers: &mut [Option<Value>],
2162    dst: Reg,
2163    lhs: Reg,
2164    rhs: Reg,
2165    op: impl FnOnce(i64, i64) -> Result<i64, RuntimeError>,
2166) -> Result<(), RuntimeError> {
2167    let lhs = expect_i64(get_reg(registers, lhs)?)?;
2168    let rhs = expect_i64(get_reg(registers, rhs)?)?;
2169    set_reg(registers, dst, Value::I64(op(lhs, rhs)?))
2170}
2171
2172fn execute_i64_test(
2173    registers: &mut [Option<Value>],
2174    dst: Reg,
2175    value: Reg,
2176    op: impl FnOnce(i64) -> i32,
2177) -> Result<(), RuntimeError> {
2178    let value = expect_i64(get_reg(registers, value)?)?;
2179    set_reg(registers, dst, Value::I32(op(value)))
2180}
2181
2182fn execute_i64_compare(
2183    registers: &mut [Option<Value>],
2184    dst: Reg,
2185    lhs: Reg,
2186    rhs: Reg,
2187    op: impl FnOnce(i64, i64) -> bool,
2188) -> Result<(), RuntimeError> {
2189    let lhs = expect_i64(get_reg(registers, lhs)?)?;
2190    let rhs = expect_i64(get_reg(registers, rhs)?)?;
2191    set_reg(registers, dst, Value::I32(i32::from(op(lhs, rhs))))
2192}
2193
2194fn i32_div_s(lhs: i32, rhs: i32) -> Result<i32, RuntimeError> {
2195    if rhs == 0 {
2196        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2197    }
2198    if lhs == i32::MIN && rhs == -1 {
2199        return Err(trap(RuntimeTrap::IntegerOverflow));
2200    }
2201    Ok(lhs / rhs)
2202}
2203
2204fn i32_div_u(lhs: i32, rhs: i32) -> Result<i32, RuntimeError> {
2205    if rhs == 0 {
2206        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2207    }
2208    Ok(((lhs as u32) / (rhs as u32)) as i32)
2209}
2210
2211fn i32_rem_s(lhs: i32, rhs: i32) -> Result<i32, RuntimeError> {
2212    if rhs == 0 {
2213        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2214    }
2215    if lhs == i32::MIN && rhs == -1 {
2216        return Ok(0);
2217    }
2218    Ok(lhs % rhs)
2219}
2220
2221fn i32_rem_u(lhs: i32, rhs: i32) -> Result<i32, RuntimeError> {
2222    if rhs == 0 {
2223        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2224    }
2225    Ok(((lhs as u32) % (rhs as u32)) as i32)
2226}
2227
2228fn i64_div_s(lhs: i64, rhs: i64) -> Result<i64, RuntimeError> {
2229    if rhs == 0 {
2230        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2231    }
2232    if lhs == i64::MIN && rhs == -1 {
2233        return Err(trap(RuntimeTrap::IntegerOverflow));
2234    }
2235    Ok(lhs / rhs)
2236}
2237
2238fn i64_div_u(lhs: i64, rhs: i64) -> Result<i64, RuntimeError> {
2239    if rhs == 0 {
2240        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2241    }
2242    Ok(((lhs as u64) / (rhs as u64)) as i64)
2243}
2244
2245fn i64_rem_s(lhs: i64, rhs: i64) -> Result<i64, RuntimeError> {
2246    if rhs == 0 {
2247        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2248    }
2249    if lhs == i64::MIN && rhs == -1 {
2250        return Ok(0);
2251    }
2252    Ok(lhs % rhs)
2253}
2254
2255fn i64_rem_u(lhs: i64, rhs: i64) -> Result<i64, RuntimeError> {
2256    if rhs == 0 {
2257        return Err(trap(RuntimeTrap::IntegerDivideByZero));
2258    }
2259    Ok(((lhs as u64) % (rhs as u64)) as i64)
2260}
2261
2262fn trap(trap: RuntimeTrap) -> RuntimeError {
2263    RuntimeError {
2264        kind: RuntimeErrorKind::Trap(trap),
2265    }
2266}
2267
2268fn execute_f32_unary(
2269    registers: &mut [Option<Value>],
2270    dst: Reg,
2271    value: Reg,
2272    op: impl FnOnce(f32) -> f32,
2273) -> Result<(), RuntimeError> {
2274    let value = expect_f32(get_reg(registers, value)?)?;
2275    set_reg(registers, dst, Value::F32(op(value)))
2276}
2277
2278fn execute_f32_binary(
2279    registers: &mut [Option<Value>],
2280    dst: Reg,
2281    lhs: Reg,
2282    rhs: Reg,
2283    op: impl FnOnce(f32, f32) -> f32,
2284) -> Result<(), RuntimeError> {
2285    let lhs = expect_f32(get_reg(registers, lhs)?)?;
2286    let rhs = expect_f32(get_reg(registers, rhs)?)?;
2287    set_reg(registers, dst, Value::F32(op(lhs, rhs)))
2288}
2289
2290fn execute_f32_compare(
2291    registers: &mut [Option<Value>],
2292    dst: Reg,
2293    lhs: Reg,
2294    rhs: Reg,
2295    op: impl FnOnce(f32, f32) -> bool,
2296) -> Result<(), RuntimeError> {
2297    let lhs = expect_f32(get_reg(registers, lhs)?)?;
2298    let rhs = expect_f32(get_reg(registers, rhs)?)?;
2299    set_reg(registers, dst, Value::I32(i32::from(op(lhs, rhs))))
2300}
2301
2302fn execute_f64_unary(
2303    registers: &mut [Option<Value>],
2304    dst: Reg,
2305    value: Reg,
2306    op: impl FnOnce(f64) -> f64,
2307) -> Result<(), RuntimeError> {
2308    let value = expect_f64(get_reg(registers, value)?)?;
2309    set_reg(registers, dst, Value::F64(op(value)))
2310}
2311
2312fn execute_f64_binary(
2313    registers: &mut [Option<Value>],
2314    dst: Reg,
2315    lhs: Reg,
2316    rhs: Reg,
2317    op: impl FnOnce(f64, f64) -> f64,
2318) -> Result<(), RuntimeError> {
2319    let lhs = expect_f64(get_reg(registers, lhs)?)?;
2320    let rhs = expect_f64(get_reg(registers, rhs)?)?;
2321    set_reg(registers, dst, Value::F64(op(lhs, rhs)))
2322}
2323
2324fn execute_f64_compare(
2325    registers: &mut [Option<Value>],
2326    dst: Reg,
2327    lhs: Reg,
2328    rhs: Reg,
2329    op: impl FnOnce(f64, f64) -> bool,
2330) -> Result<(), RuntimeError> {
2331    let lhs = expect_f64(get_reg(registers, lhs)?)?;
2332    let rhs = expect_f64(get_reg(registers, rhs)?)?;
2333    set_reg(registers, dst, Value::I32(i32::from(op(lhs, rhs))))
2334}
2335
2336fn execute_i32_to_f32(
2337    registers: &mut [Option<Value>],
2338    dst: Reg,
2339    value: Reg,
2340    op: impl FnOnce(i32) -> f32,
2341) -> Result<(), RuntimeError> {
2342    let value = expect_i32(get_reg(registers, value)?)?;
2343    set_reg(registers, dst, Value::F32(op(value)))
2344}
2345
2346fn execute_i64_to_f32(
2347    registers: &mut [Option<Value>],
2348    dst: Reg,
2349    value: Reg,
2350    op: impl FnOnce(i64) -> f32,
2351) -> Result<(), RuntimeError> {
2352    let value = expect_i64(get_reg(registers, value)?)?;
2353    set_reg(registers, dst, Value::F32(op(value)))
2354}
2355
2356fn execute_i32_to_f64(
2357    registers: &mut [Option<Value>],
2358    dst: Reg,
2359    value: Reg,
2360    op: impl FnOnce(i32) -> f64,
2361) -> Result<(), RuntimeError> {
2362    let value = expect_i32(get_reg(registers, value)?)?;
2363    set_reg(registers, dst, Value::F64(op(value)))
2364}
2365
2366fn execute_i64_to_f64(
2367    registers: &mut [Option<Value>],
2368    dst: Reg,
2369    value: Reg,
2370    op: impl FnOnce(i64) -> f64,
2371) -> Result<(), RuntimeError> {
2372    let value = expect_i64(get_reg(registers, value)?)?;
2373    set_reg(registers, dst, Value::F64(op(value)))
2374}
2375
2376fn execute_f32_to_i32(
2377    registers: &mut [Option<Value>],
2378    dst: Reg,
2379    value: Reg,
2380    op: impl FnOnce(f32) -> i32,
2381) -> Result<(), RuntimeError> {
2382    let value = expect_f32(get_reg(registers, value)?)?;
2383    set_reg(registers, dst, Value::I32(op(value)))
2384}
2385
2386fn execute_f64_to_i64(
2387    registers: &mut [Option<Value>],
2388    dst: Reg,
2389    value: Reg,
2390    op: impl FnOnce(f64) -> i64,
2391) -> Result<(), RuntimeError> {
2392    let value = expect_f64(get_reg(registers, value)?)?;
2393    set_reg(registers, dst, Value::I64(op(value)))
2394}
2395
2396fn execute_f32_to_f64(
2397    registers: &mut [Option<Value>],
2398    dst: Reg,
2399    value: Reg,
2400    op: impl FnOnce(f32) -> f64,
2401) -> Result<(), RuntimeError> {
2402    let value = expect_f32(get_reg(registers, value)?)?;
2403    set_reg(registers, dst, Value::F64(op(value)))
2404}
2405
2406fn execute_f64_to_f32(
2407    registers: &mut [Option<Value>],
2408    dst: Reg,
2409    value: Reg,
2410    op: impl FnOnce(f64) -> f32,
2411) -> Result<(), RuntimeError> {
2412    let value = expect_f64(get_reg(registers, value)?)?;
2413    set_reg(registers, dst, Value::F32(op(value)))
2414}
2415
2416fn execute_f32_to_i32_checked(
2417    registers: &mut [Option<Value>],
2418    dst: Reg,
2419    value: Reg,
2420    op: impl FnOnce(f32) -> i32,
2421    unsigned: bool,
2422) -> Result<(), RuntimeError> {
2423    let value = expect_f32(get_reg(registers, value)?)?;
2424    if value.is_nan() {
2425        return Err(trap(RuntimeTrap::InvalidConversionToInteger));
2426    }
2427    if unsigned {
2428        if value <= -1.0 || value >= (u32::MAX as f32) {
2429            return Err(trap(RuntimeTrap::IntegerOverflow));
2430        }
2431    } else if value >= (i32::MAX as f32) || value < (i32::MIN as f32) {
2432        return Err(trap(RuntimeTrap::IntegerOverflow));
2433    }
2434    set_reg(registers, dst, Value::I32(op(value)))
2435}
2436
2437fn execute_f64_to_i32_checked(
2438    registers: &mut [Option<Value>],
2439    dst: Reg,
2440    value: Reg,
2441    op: impl FnOnce(f64) -> i32,
2442    unsigned: bool,
2443) -> Result<(), RuntimeError> {
2444    let value = expect_f64(get_reg(registers, value)?)?;
2445    if value.is_nan() {
2446        return Err(trap(RuntimeTrap::InvalidConversionToInteger));
2447    }
2448    if unsigned {
2449        if value <= -1.0 || value >= 4294967296.0 {
2450            return Err(trap(RuntimeTrap::IntegerOverflow));
2451        }
2452    } else if value >= 2147483648.0 || value <= -2147483649.0 {
2453        return Err(trap(RuntimeTrap::IntegerOverflow));
2454    }
2455    set_reg(registers, dst, Value::I32(op(value)))
2456}
2457
2458fn execute_f32_to_i64_checked(
2459    registers: &mut [Option<Value>],
2460    dst: Reg,
2461    value: Reg,
2462    op: impl FnOnce(f32) -> i64,
2463    unsigned: bool,
2464) -> Result<(), RuntimeError> {
2465    let value = expect_f32(get_reg(registers, value)?)?;
2466    if value.is_nan() {
2467        return Err(trap(RuntimeTrap::InvalidConversionToInteger));
2468    }
2469    if unsigned {
2470        if value <= -1.0 || value >= (u64::MAX as f32) {
2471            return Err(trap(RuntimeTrap::IntegerOverflow));
2472        }
2473    } else if value >= (i64::MAX as f32) || value < (i64::MIN as f32) {
2474        return Err(trap(RuntimeTrap::IntegerOverflow));
2475    }
2476    set_reg(registers, dst, Value::I64(op(value)))
2477}
2478
2479fn execute_f64_to_i64_checked(
2480    registers: &mut [Option<Value>],
2481    dst: Reg,
2482    value: Reg,
2483    op: impl FnOnce(f64) -> i64,
2484    unsigned: bool,
2485) -> Result<(), RuntimeError> {
2486    let value = expect_f64(get_reg(registers, value)?)?;
2487    if value.is_nan() {
2488        return Err(trap(RuntimeTrap::InvalidConversionToInteger));
2489    }
2490    if unsigned {
2491        if value <= -1.0 || value >= (u64::MAX as f64) {
2492            return Err(trap(RuntimeTrap::IntegerOverflow));
2493        }
2494    } else if value >= (i64::MAX as f64) || value < (i64::MIN as f64) {
2495        return Err(trap(RuntimeTrap::IntegerOverflow));
2496    }
2497    set_reg(registers, dst, Value::I64(op(value)))
2498}
2499
2500fn execute_f32_to_i32_saturating(
2501    registers: &mut [Option<Value>],
2502    dst: Reg,
2503    value: Reg,
2504    op: impl FnOnce(f32) -> i32,
2505) -> Result<(), RuntimeError> {
2506    let value = expect_f32(get_reg(registers, value)?)?;
2507    set_reg(registers, dst, Value::I32(op(value)))
2508}
2509
2510fn execute_f64_to_i32_saturating(
2511    registers: &mut [Option<Value>],
2512    dst: Reg,
2513    value: Reg,
2514    op: impl FnOnce(f64) -> i32,
2515) -> Result<(), RuntimeError> {
2516    let value = expect_f64(get_reg(registers, value)?)?;
2517    set_reg(registers, dst, Value::I32(op(value)))
2518}
2519
2520fn execute_f32_to_i64_saturating(
2521    registers: &mut [Option<Value>],
2522    dst: Reg,
2523    value: Reg,
2524    op: impl FnOnce(f32) -> i64,
2525) -> Result<(), RuntimeError> {
2526    let value = expect_f32(get_reg(registers, value)?)?;
2527    set_reg(registers, dst, Value::I64(op(value)))
2528}
2529
2530fn execute_f64_to_i64_saturating(
2531    registers: &mut [Option<Value>],
2532    dst: Reg,
2533    value: Reg,
2534    op: impl FnOnce(f64) -> i64,
2535) -> Result<(), RuntimeError> {
2536    let value = expect_f64(get_reg(registers, value)?)?;
2537    set_reg(registers, dst, Value::I64(op(value)))
2538}
2539
2540fn expect_i32(value: Value) -> Result<i32, RuntimeError> {
2541    match value {
2542        Value::I32(value) => Ok(value),
2543        value => Err(RuntimeError {
2544            kind: RuntimeErrorKind::TypeMismatch {
2545                expected: ValType::Num(NumType::I32),
2546                found: value.val_type(),
2547            },
2548        }),
2549    }
2550}
2551
2552fn expect_i64(value: Value) -> Result<i64, RuntimeError> {
2553    match value {
2554        Value::I64(value) => Ok(value),
2555        value => Err(RuntimeError {
2556            kind: RuntimeErrorKind::TypeMismatch {
2557                expected: ValType::Num(NumType::I64),
2558                found: value.val_type(),
2559            },
2560        }),
2561    }
2562}
2563
2564fn expect_f32(value: Value) -> Result<f32, RuntimeError> {
2565    match value {
2566        Value::F32(value) => Ok(value),
2567        value => Err(RuntimeError {
2568            kind: RuntimeErrorKind::TypeMismatch {
2569                expected: ValType::Num(NumType::F32),
2570                found: value.val_type(),
2571            },
2572        }),
2573    }
2574}
2575
2576fn expect_f64(value: Value) -> Result<f64, RuntimeError> {
2577    match value {
2578        Value::F64(value) => Ok(value),
2579        value => Err(RuntimeError {
2580            kind: RuntimeErrorKind::TypeMismatch {
2581                expected: ValType::Num(NumType::F64),
2582                found: value.val_type(),
2583            },
2584        }),
2585    }
2586}
2587
2588#[cfg(test)]
2589mod tests {
2590    use super::*;
2591    use crate::binary::module::Module;
2592
2593    #[test]
2594    fn execute_exported_add_by_name() {
2595        let reg_module = lowered_add_module();
2596
2597        let store = Store::instantiate(&reg_module).unwrap();
2598        let result = execute_export(
2599            &reg_module,
2600            &store,
2601            "add",
2602            &[Value::I32(20), Value::I32(22)],
2603        )
2604        .unwrap();
2605
2606        assert_eq!(result, alloc::vec![Value::I32(42)]);
2607    }
2608
2609    #[test]
2610    fn reject_unknown_export_name() {
2611        let reg_module = lowered_add_module();
2612
2613        let store = Store::instantiate(&reg_module).unwrap();
2614        let err = execute_export(&reg_module, &store, "missing", &[]).unwrap_err();
2615
2616        assert_eq!(
2617            err.kind,
2618            RuntimeErrorKind::UnknownExport {
2619                name: "missing".into(),
2620            }
2621        );
2622    }
2623
2624    #[test]
2625    fn reject_export_call_with_missing_arg() {
2626        let reg_module = lowered_add_module();
2627
2628        let store = Store::instantiate(&reg_module).unwrap();
2629        let err = execute_export(&reg_module, &store, "add", &[Value::I32(20)]).unwrap_err();
2630
2631        assert_eq!(
2632            err.kind,
2633            RuntimeErrorKind::ArityMismatch {
2634                expected: 2,
2635                found: 1,
2636            }
2637        );
2638    }
2639
2640    #[test]
2641    fn reject_export_call_with_extra_arg() {
2642        let reg_module = lowered_add_module();
2643
2644        let store = Store::instantiate(&reg_module).unwrap();
2645        let err = execute_export(
2646            &reg_module,
2647            &store,
2648            "add",
2649            &[Value::I32(20), Value::I32(22), Value::I32(1)],
2650        )
2651        .unwrap_err();
2652
2653        assert_eq!(
2654            err.kind,
2655            RuntimeErrorKind::ArityMismatch {
2656                expected: 2,
2657                found: 3,
2658            }
2659        );
2660    }
2661
2662    #[test]
2663    fn reject_export_call_with_wrong_arg_type() {
2664        let reg_module = lowered_add_module();
2665
2666        let store = Store::instantiate(&reg_module).unwrap();
2667        let err = execute_export(
2668            &reg_module,
2669            &store,
2670            "add",
2671            &[Value::I64(20), Value::I32(22)],
2672        )
2673        .unwrap_err();
2674
2675        assert_eq!(
2676            err.kind,
2677            RuntimeErrorKind::TypeMismatch {
2678                expected: ValType::Num(NumType::I32),
2679                found: ValType::Num(NumType::I64),
2680            }
2681        );
2682    }
2683
2684    fn lowered_add_module() -> RegModule {
2685        let bytes = baedeker_testdata::fixture_bytes("add");
2686        let module = Module::decode(&bytes).unwrap();
2687        module.lower().unwrap()
2688    }
2689}