Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614//! Stack-machine interpreter for WebAssembly's straight-line instructions.//!//! Implements the numeric, parametric, variable, table, and memory instruction//! groups from §4.4 of the WebAssembly Core Specification 2.0. Control flow//! (`block`/`loop`/`if`/`br*`/`return`) and function calls (`call`///! `call_indirect`) live in [`super::engine`], which drives this dispatch with//! a label and call stack on top.//!//! The interpreter trusts the validator: types on the operand stack always//! match the instruction's declared signature. Mismatches are an internal//! contract violation and surface as panics, not traps. The only failures it//! reports are the dynamic ones the spec calls out: out-of-bounds memory and//! table access, integer divide-by-zero and overflow, NaN/out-of-range float//! conversions, and the explicit `unreachable` instruction.//!//! IEEE-754 details that diverge from Rust's `std` semantics are handled//! explicitly: `min`/`max` propagate NaN and respect the `-0 < +0` ordering,//! `nearest` rounds half-to-even, and the integer ↔ float conversions reject//! their out-of-range inputs in the trapping variants and saturate in the//! `*_trunc_sat_*` variants.
use crate::instruction::Instruction;use crate::runtime::store::{DataAddr, ElementAddr, MemoryAddr, ModuleAddr, Store, TableAddr};use crate::runtime::trap::Trap;use crate::runtime::value::{Ref, WasmValue};use crate::types::MemArg;
/// Result of executing a single straight-line instruction.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum StepOutcome { /// Continue executing the next instruction in the function body. Continue, /// Hit the function's top-level `End`. The current operand stack is the /// function's result vector (arity already validated). FunctionEnd,}
/// Stack-machine interpreter over a [`Store`] and a single active module.////// Construct one per activation. The operand stack and locals frame live/// inside the interpreter; callers run a function by calling [`Interpreter::step`]/// repeatedly until they get [`StepOutcome::FunctionEnd`] or a trap.pub struct Interpreter<'a> { store: &'a mut Store, module: ModuleAddr, locals: Vec<WasmValue>, stack: Vec<WasmValue>,}
impl<'a> Interpreter<'a> { /// Build a fresh interpreter. `locals` already contains the function's /// parameters followed by zero-initialized declared locals — the /// instantiation pipeline is responsible for that layout per spec §4.5.4. pub fn new(store: &'a mut Store, module: ModuleAddr, locals: Vec<WasmValue>) -> Self { Self { store, module, locals, stack: Vec::new(), } }
/// Pre-allocate the operand stack. Useful for tight per-function reuse. pub fn with_capacity( store: &'a mut Store, module: ModuleAddr, locals: Vec<WasmValue>, stack_capacity: usize, ) -> Self { Self { store, module, locals, stack: Vec::with_capacity(stack_capacity), } }
/// Currently-active module address. pub fn module(&self) -> ModuleAddr { self.module }
/// View the current locals frame. pub fn locals(&self) -> &[WasmValue] { &self.locals }
/// Mutable access to the locals frame. pub fn locals_mut(&mut self) -> &mut Vec<WasmValue> { &mut self.locals }
/// View the current operand stack. pub fn stack(&self) -> &[WasmValue] { &self.stack }
/// Push a value onto the operand stack. pub fn push(&mut self, v: WasmValue) { self.stack.push(v); }
/// Pop a value off the operand stack. Panics if empty: the validator /// guarantees stack underflow cannot happen at runtime. pub fn pop(&mut self) -> WasmValue { self.stack.pop().expect("operand stack underflow") }
/// Consume the interpreter and return the final operand stack. pub fn into_stack(self) -> Vec<WasmValue> { self.stack }
/// The shared store, for tests that need to inspect post-execution state. pub fn store(&self) -> &Store { self.store }
/// Mutable access to the shared store. pub fn store_mut(&mut self) -> &mut Store { self.store }
/// Execute one straight-line instruction. /// /// Returns [`StepOutcome::Continue`] for normal flow, [`StepOutcome::FunctionEnd`] /// when the function's terminating `End` is reached, or a [`Trap`] for any /// dynamic failure. Passing a control-flow or call instruction here is a /// programming error — that dispatch lives in [`super::engine`]. pub fn step(&mut self, instr: &Instruction) -> Result<StepOutcome, Trap> { step_straight( self.store, self.module, &mut self.locals, &mut self.stack, instr, ) }}
// ============================================================================// Free-function dispatch — shared by [`Interpreter`] and [`super::engine`].// ============================================================================
/// Execute one straight-line instruction against the supplied stack and locals.////// Control-flow (`block`/`loop`/`if`/`else`/`br*`/`return`) and call (`call`//// `call_indirect`) instructions are NOT handled here; passing one is a/// programming error and panics. The engine driver layers above us must filter/// them out before dispatch.pub(crate) fn step_straight( store: &mut Store, module: ModuleAddr, locals: &mut [WasmValue], stack: &mut Vec<WasmValue>, instr: &Instruction,) -> Result<StepOutcome, Trap> { match instr { // ---- Trivial control -------------------------------------- Instruction::Unreachable => Err(Trap::Unreachable), Instruction::Nop => Ok(StepOutcome::Continue), Instruction::End => Ok(StepOutcome::FunctionEnd),
// ---- Out-of-scope control flow / calls -------------------- Instruction::Block(_) | Instruction::Loop(_) | Instruction::If(_) | Instruction::Else | Instruction::Br(_) | Instruction::BrIf(_) | Instruction::BrTable { .. } | Instruction::Return | Instruction::Call(_) | Instruction::CallIndirect { .. } => { unreachable!( "control-flow / call instruction {:?} is handled by the \ engine driver, not step_straight", instr ) }
// ---- Reference instructions ------------------------------ Instruction::RefNull(rt) => { stack.push(WasmValue::Ref(Ref::Null(*rt))); Ok(StepOutcome::Continue) } Instruction::RefIsNull => { let r = pop_ref(stack); stack.push(WasmValue::I32(bool_to_i32(r.is_null()))); Ok(StepOutcome::Continue) } Instruction::RefFunc(idx) => { let addr = store .module(module) .resolve_func(*idx) .expect("validator: function index out of range"); stack.push(WasmValue::Ref(Ref::Func(addr))); Ok(StepOutcome::Continue) }
// ---- Bulk table ------------------------------------------ Instruction::TableInit { elem_index, table_index, } => { let n = pop_i32(stack) as u32; let src = pop_i32(stack) as u32; let dst = pop_i32(stack) as u32; let element_addr = store .module(module) .resolve_element(*elem_index) .expect("validator: element index out of range"); let table_addr = store .module(module) .resolve_table(*table_index) .expect("validator: table index out of range"); let refs = element_slice(store, element_addr, src, n)?.to_vec(); store.table_mut(table_addr).init(dst, &refs)?; Ok(StepOutcome::Continue) } Instruction::ElemDrop(idx) => { let addr = store .module(module) .resolve_element(*idx) .expect("validator: element index out of range"); store.element_mut(addr).drop_segment(); Ok(StepOutcome::Continue) } Instruction::TableCopy { dst_table, src_table, } => { let n = pop_i32(stack) as u32; let src = pop_i32(stack) as u32; let dst = pop_i32(stack) as u32; table_copy(store, module, *dst_table, *src_table, dst, src, n)?; Ok(StepOutcome::Continue) }
// ---- Bulk memory ----------------------------------------- Instruction::MemoryInit { data_index, memory } => { let n = pop_i32(stack) as u32; let src = pop_i32(stack) as u32; let dst = pop_i32(stack) as u32; let data_addr = store .module(module) .resolve_data(*data_index) .expect("validator: data index out of range"); let memory_addr = store .module(module) .resolve_memory(*memory) .expect("validator: memory index out of range"); let bytes = data_slice(store, data_addr, src, n)?.to_vec(); store.memory_mut(memory_addr).init(dst as u64, &bytes)?; Ok(StepOutcome::Continue) } Instruction::DataDrop(idx) => { let addr = store .module(module) .resolve_data(*idx) .expect("validator: data index out of range"); store.data_mut(addr).drop_segment(); Ok(StepOutcome::Continue) } Instruction::MemoryCopy { dst_memory, src_memory, } => { let n = pop_i32(stack) as u32; let src = pop_i32(stack) as u32; let dst = pop_i32(stack) as u32; memory_copy(store, module, *dst_memory, *src_memory, dst, src, n)?; Ok(StepOutcome::Continue) } Instruction::MemoryFill(mem) => { let n = pop_i32(stack) as u32; let value = pop_i32(stack) as u8; let dst = pop_i32(stack) as u32; with_memory_mut(store, module, *mem, |memory| { memory.fill(dst as u64, n as u64, value) })?; Ok(StepOutcome::Continue) }
// ---- Parametric ------------------------------------------- Instruction::Drop => { pop_any(stack); Ok(StepOutcome::Continue) } Instruction::Select | Instruction::SelectTyped(_) => { let c = pop_i32(stack); let v2 = pop_any(stack); let v1 = pop_any(stack); stack.push(if c != 0 { v1 } else { v2 }); Ok(StepOutcome::Continue) }
// ---- Variable: locals ------------------------------------- Instruction::LocalGet(idx) => { stack.push(locals[*idx as usize]); Ok(StepOutcome::Continue) } Instruction::LocalSet(idx) => { let v = pop_any(stack); locals[*idx as usize] = v; Ok(StepOutcome::Continue) } Instruction::LocalTee(idx) => { let v = *stack.last().expect("operand stack underflow"); locals[*idx as usize] = v; Ok(StepOutcome::Continue) }
// ---- Variable: globals ------------------------------------ Instruction::GlobalGet(idx) => { let addr = store .module(module) .resolve_global(*idx) .expect("validator: global index out of range"); stack.push(store.global(addr).get()); Ok(StepOutcome::Continue) } Instruction::GlobalSet(idx) => { let addr = store .module(module) .resolve_global(*idx) .expect("validator: global index out of range"); let v = pop_any(stack); store .global_mut(addr) .set(v) .expect("validator should have rejected global.set"); Ok(StepOutcome::Continue) }
// ---- Table instructions ----------------------------------- Instruction::TableGet(idx) => { let i = pop_i32(stack) as u32; let addr = store .module(module) .resolve_table(*idx) .expect("validator: table index out of range"); let r = store.table(addr).get(i)?; stack.push(WasmValue::Ref(r)); Ok(StepOutcome::Continue) } Instruction::TableSet(idx) => { let val = pop_ref(stack); let i = pop_i32(stack) as u32; let addr = store .module(module) .resolve_table(*idx) .expect("validator: table index out of range"); store.table_mut(addr).set(i, val)?; Ok(StepOutcome::Continue) } Instruction::TableSize(idx) => { let addr = store .module(module) .resolve_table(*idx) .expect("validator: table index out of range"); let size = store.table(addr).size(); stack.push(WasmValue::I32(size as i32)); Ok(StepOutcome::Continue) } Instruction::TableGrow(idx) => { let n = pop_i32(stack) as u32; let init = pop_ref(stack); let addr = store .module(module) .resolve_table(*idx) .expect("validator: table index out of range"); let result = match store.table_mut(addr).grow(n, init) { Some(prev) => prev as i32, None => -1, }; stack.push(WasmValue::I32(result)); Ok(StepOutcome::Continue) } Instruction::TableFill(idx) => { let n = pop_i32(stack) as u32; let val = pop_ref(stack); let i = pop_i32(stack) as u32; let addr = store .module(module) .resolve_table(*idx) .expect("validator: table index out of range"); store.table_mut(addr).fill(i, n, val)?; Ok(StepOutcome::Continue) }
// ---- Memory: size / grow ---------------------------------- Instruction::MemorySize(mem_idx) => { let addr = store .module(module) .resolve_memory(*mem_idx) .expect("validator: memory index out of range"); let pages = store.memory(addr).size(); stack.push(WasmValue::I32(pages as i32)); Ok(StepOutcome::Continue) } Instruction::MemoryGrow(mem_idx) => { let delta = pop_i32(stack) as u32; let addr = store .module(module) .resolve_memory(*mem_idx) .expect("validator: memory index out of range"); let result = match store.memory_mut(addr).grow(delta) { Some(prev) => prev as i32, None => -1, }; stack.push(WasmValue::I32(result)); Ok(StepOutcome::Continue) }
// ---- Memory loads ---------------------------------------- Instruction::I32Load(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i32(a), |s, v| s.push(WasmValue::I32(v)), ), Instruction::I64Load(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i64(a), |s, v| s.push(WasmValue::I64(v)), ), Instruction::F32Load(m) => exec_load( store, module, stack, m, |mem, a| mem.load_f32_bits(a), |s, bits| s.push(WasmValue::F32(bits)), ), Instruction::F64Load(m) => exec_load( store, module, stack, m, |mem, a| mem.load_f64_bits(a), |s, bits| s.push(WasmValue::F64(bits)), ), Instruction::I32Load8S(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i8(a), |s, v| s.push(WasmValue::I32(v as i32)), ), Instruction::I32Load8U(m) => exec_load( store, module, stack, m, |mem, a| mem.load_u8(a), |s, v| s.push(WasmValue::I32(v as i32)), ), Instruction::I32Load16S(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i16(a), |s, v| s.push(WasmValue::I32(v as i32)), ), Instruction::I32Load16U(m) => exec_load( store, module, stack, m, |mem, a| mem.load_u16(a), |s, v| s.push(WasmValue::I32(v as i32)), ), Instruction::I64Load8S(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i8(a), |s, v| s.push(WasmValue::I64(v as i64)), ), Instruction::I64Load8U(m) => exec_load( store, module, stack, m, |mem, a| mem.load_u8(a), |s, v| s.push(WasmValue::I64(v as i64)), ), Instruction::I64Load16S(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i16(a), |s, v| s.push(WasmValue::I64(v as i64)), ), Instruction::I64Load16U(m) => exec_load( store, module, stack, m, |mem, a| mem.load_u16(a), |s, v| s.push(WasmValue::I64(v as i64)), ), Instruction::I64Load32S(m) => exec_load( store, module, stack, m, |mem, a| mem.load_i32(a), |s, v| s.push(WasmValue::I64(v as i64)), ), Instruction::I64Load32U(m) => exec_load( store, module, stack, m, |mem, a| mem.load_u32(a), |s, v| s.push(WasmValue::I64(v as i64)), ),
// ---- Memory stores --------------------------------------- Instruction::I32Store(m) => { let v = pop_i32(stack); let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_i32(addr, v))?; Ok(StepOutcome::Continue) } Instruction::I64Store(m) => { let v = pop_i64(stack); let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_i64(addr, v))?; Ok(StepOutcome::Continue) } Instruction::F32Store(m) => { let bits = match pop_any(stack) { WasmValue::F32(b) => b, other => unreachable!("expected f32, got {:?}", other), }; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| { mem.store_f32_bits(addr, bits) })?; Ok(StepOutcome::Continue) } Instruction::F64Store(m) => { let bits = match pop_any(stack) { WasmValue::F64(b) => b, other => unreachable!("expected f64, got {:?}", other), }; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| { mem.store_f64_bits(addr, bits) })?; Ok(StepOutcome::Continue) } Instruction::I32Store8(m) => { let v = pop_i32(stack) as u8; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_u8(addr, v))?; Ok(StepOutcome::Continue) } Instruction::I32Store16(m) => { let v = pop_i32(stack) as u16; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_u16(addr, v))?; Ok(StepOutcome::Continue) } Instruction::I64Store8(m) => { let v = pop_i64(stack) as u8; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_u8(addr, v))?; Ok(StepOutcome::Continue) } Instruction::I64Store16(m) => { let v = pop_i64(stack) as u16; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_u16(addr, v))?; Ok(StepOutcome::Continue) } Instruction::I64Store32(m) => { let v = pop_i64(stack) as u32; let addr = effective_addr(stack, m); with_memory_mut(store, module, m.memory, |mem| mem.store_u32(addr, v))?; Ok(StepOutcome::Continue) }
// ---- Numeric constants ----------------------------------- Instruction::I32Const(v) => { stack.push(WasmValue::I32(*v)); Ok(StepOutcome::Continue) } Instruction::I64Const(v) => { stack.push(WasmValue::I64(*v)); Ok(StepOutcome::Continue) } Instruction::F32Const(v) => { stack.push(WasmValue::F32(v.to_bits())); Ok(StepOutcome::Continue) } Instruction::F64Const(v) => { stack.push(WasmValue::F64(v.to_bits())); Ok(StepOutcome::Continue) }
// ---- i32 comparisons ------------------------------------- Instruction::I32Eqz => { let a = pop_i32(stack); stack.push(WasmValue::I32(bool_to_i32(a == 0))); Ok(StepOutcome::Continue) } Instruction::I32Eq => bin_i32_cmp(stack, |a, b| a == b), Instruction::I32Ne => bin_i32_cmp(stack, |a, b| a != b), Instruction::I32LtS => bin_i32_cmp(stack, |a, b| a < b), Instruction::I32LtU => bin_i32_cmp(stack, |a, b| (a as u32) < (b as u32)), Instruction::I32GtS => bin_i32_cmp(stack, |a, b| a > b), Instruction::I32GtU => bin_i32_cmp(stack, |a, b| (a as u32) > (b as u32)), Instruction::I32LeS => bin_i32_cmp(stack, |a, b| a <= b), Instruction::I32LeU => bin_i32_cmp(stack, |a, b| (a as u32) <= (b as u32)), Instruction::I32GeS => bin_i32_cmp(stack, |a, b| a >= b), Instruction::I32GeU => bin_i32_cmp(stack, |a, b| (a as u32) >= (b as u32)),
// ---- i64 comparisons ------------------------------------- Instruction::I64Eqz => { let a = pop_i64(stack); stack.push(WasmValue::I32(bool_to_i32(a == 0))); Ok(StepOutcome::Continue) } Instruction::I64Eq => bin_i64_cmp(stack, |a, b| a == b), Instruction::I64Ne => bin_i64_cmp(stack, |a, b| a != b), Instruction::I64LtS => bin_i64_cmp(stack, |a, b| a < b), Instruction::I64LtU => bin_i64_cmp(stack, |a, b| (a as u64) < (b as u64)), Instruction::I64GtS => bin_i64_cmp(stack, |a, b| a > b), Instruction::I64GtU => bin_i64_cmp(stack, |a, b| (a as u64) > (b as u64)), Instruction::I64LeS => bin_i64_cmp(stack, |a, b| a <= b), Instruction::I64LeU => bin_i64_cmp(stack, |a, b| (a as u64) <= (b as u64)), Instruction::I64GeS => bin_i64_cmp(stack, |a, b| a >= b), Instruction::I64GeU => bin_i64_cmp(stack, |a, b| (a as u64) >= (b as u64)),
// ---- f32 comparisons ------------------------------------- Instruction::F32Eq => bin_f32_cmp(stack, |a, b| a == b), Instruction::F32Ne => bin_f32_cmp(stack, |a, b| a != b), Instruction::F32Lt => bin_f32_cmp(stack, |a, b| a < b), Instruction::F32Gt => bin_f32_cmp(stack, |a, b| a > b), Instruction::F32Le => bin_f32_cmp(stack, |a, b| a <= b), Instruction::F32Ge => bin_f32_cmp(stack, |a, b| a >= b),
// ---- f64 comparisons ------------------------------------- Instruction::F64Eq => bin_f64_cmp(stack, |a, b| a == b), Instruction::F64Ne => bin_f64_cmp(stack, |a, b| a != b), Instruction::F64Lt => bin_f64_cmp(stack, |a, b| a < b), Instruction::F64Gt => bin_f64_cmp(stack, |a, b| a > b), Instruction::F64Le => bin_f64_cmp(stack, |a, b| a <= b), Instruction::F64Ge => bin_f64_cmp(stack, |a, b| a >= b),
// ---- i32 numeric ----------------------------------------- Instruction::I32Clz => un_i32(stack, |a| a.leading_zeros() as i32), Instruction::I32Ctz => un_i32(stack, |a| a.trailing_zeros() as i32), Instruction::I32Popcnt => un_i32(stack, |a| a.count_ones() as i32), Instruction::I32Add => bin_i32(stack, |a, b| a.wrapping_add(b)), Instruction::I32Sub => bin_i32(stack, |a, b| a.wrapping_sub(b)), Instruction::I32Mul => bin_i32(stack, |a, b| a.wrapping_mul(b)), Instruction::I32DivS => bin_i32_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else if a == i32::MIN && b == -1 { Err(Trap::IntegerOverflow) } else { Ok(a.wrapping_div(b)) } }), Instruction::I32DivU => bin_i32_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else { Ok(((a as u32) / (b as u32)) as i32) } }), Instruction::I32RemS => bin_i32_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else if a == i32::MIN && b == -1 { Ok(0) } else { Ok(a.wrapping_rem(b)) } }), Instruction::I32RemU => bin_i32_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else { Ok(((a as u32) % (b as u32)) as i32) } }), Instruction::I32And => bin_i32(stack, |a, b| a & b), Instruction::I32Or => bin_i32(stack, |a, b| a | b), Instruction::I32Xor => bin_i32(stack, |a, b| a ^ b), Instruction::I32Shl => bin_i32(stack, |a, b| a.wrapping_shl((b as u32) % 32)), Instruction::I32ShrS => bin_i32(stack, |a, b| a.wrapping_shr((b as u32) % 32)), Instruction::I32ShrU => bin_i32(stack, |a, b| { ((a as u32).wrapping_shr((b as u32) % 32)) as i32 }), Instruction::I32Rotl => { bin_i32(stack, |a, b| (a as u32).rotate_left((b as u32) % 32) as i32) } Instruction::I32Rotr => bin_i32(stack, |a, b| { (a as u32).rotate_right((b as u32) % 32) as i32 }),
// ---- i64 numeric ----------------------------------------- Instruction::I64Clz => un_i64(stack, |a| a.leading_zeros() as i64), Instruction::I64Ctz => un_i64(stack, |a| a.trailing_zeros() as i64), Instruction::I64Popcnt => un_i64(stack, |a| a.count_ones() as i64), Instruction::I64Add => bin_i64(stack, |a, b| a.wrapping_add(b)), Instruction::I64Sub => bin_i64(stack, |a, b| a.wrapping_sub(b)), Instruction::I64Mul => bin_i64(stack, |a, b| a.wrapping_mul(b)), Instruction::I64DivS => bin_i64_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else if a == i64::MIN && b == -1 { Err(Trap::IntegerOverflow) } else { Ok(a.wrapping_div(b)) } }), Instruction::I64DivU => bin_i64_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else { Ok(((a as u64) / (b as u64)) as i64) } }), Instruction::I64RemS => bin_i64_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else if a == i64::MIN && b == -1 { Ok(0) } else { Ok(a.wrapping_rem(b)) } }), Instruction::I64RemU => bin_i64_try(stack, |a, b| { if b == 0 { Err(Trap::IntegerDivideByZero) } else { Ok(((a as u64) % (b as u64)) as i64) } }), Instruction::I64And => bin_i64(stack, |a, b| a & b), Instruction::I64Or => bin_i64(stack, |a, b| a | b), Instruction::I64Xor => bin_i64(stack, |a, b| a ^ b), Instruction::I64Shl => bin_i64(stack, |a, b| a.wrapping_shl((b as u64 % 64) as u32)), Instruction::I64ShrS => bin_i64(stack, |a, b| a.wrapping_shr((b as u64 % 64) as u32)), Instruction::I64ShrU => bin_i64(stack, |a, b| { ((a as u64).wrapping_shr((b as u64 % 64) as u32)) as i64 }), Instruction::I64Rotl => bin_i64(stack, |a, b| { (a as u64).rotate_left((b as u64 % 64) as u32) as i64 }), Instruction::I64Rotr => bin_i64(stack, |a, b| { (a as u64).rotate_right((b as u64 % 64) as u32) as i64 }),
// ---- f32 numeric ----------------------------------------- Instruction::F32Abs => un_f32(stack, f32::abs), Instruction::F32Neg => un_f32(stack, |a| -a), Instruction::F32Ceil => un_f32(stack, f32::ceil), Instruction::F32Floor => un_f32(stack, f32::floor), Instruction::F32Trunc => un_f32(stack, f32::trunc), Instruction::F32Nearest => un_f32(stack, nearest_f32), Instruction::F32Sqrt => un_f32(stack, f32::sqrt), Instruction::F32Add => bin_f32(stack, |a, b| a + b), Instruction::F32Sub => bin_f32(stack, |a, b| a - b), Instruction::F32Mul => bin_f32(stack, |a, b| a * b), Instruction::F32Div => bin_f32(stack, |a, b| a / b), Instruction::F32Min => bin_f32(stack, wasm_f32_min), Instruction::F32Max => bin_f32(stack, wasm_f32_max), Instruction::F32Copysign => bin_f32(stack, f32::copysign),
// ---- f64 numeric ----------------------------------------- Instruction::F64Abs => un_f64(stack, f64::abs), Instruction::F64Neg => un_f64(stack, |a| -a), Instruction::F64Ceil => un_f64(stack, f64::ceil), Instruction::F64Floor => un_f64(stack, f64::floor), Instruction::F64Trunc => un_f64(stack, f64::trunc), Instruction::F64Nearest => un_f64(stack, nearest_f64), Instruction::F64Sqrt => un_f64(stack, f64::sqrt), Instruction::F64Add => bin_f64(stack, |a, b| a + b), Instruction::F64Sub => bin_f64(stack, |a, b| a - b), Instruction::F64Mul => bin_f64(stack, |a, b| a * b), Instruction::F64Div => bin_f64(stack, |a, b| a / b), Instruction::F64Min => bin_f64(stack, wasm_f64_min), Instruction::F64Max => bin_f64(stack, wasm_f64_max), Instruction::F64Copysign => bin_f64(stack, f64::copysign),
// ---- Conversions (trapping) ------------------------------ Instruction::I32WrapI64 => { let v = pop_i64(stack); stack.push(WasmValue::I32(v as i32)); Ok(StepOutcome::Continue) } Instruction::I32TruncF32S => { let v = pop_f32(stack); stack.push(WasmValue::I32(trunc_f32_to_i32(v)?)); Ok(StepOutcome::Continue) } Instruction::I32TruncF32U => { let v = pop_f32(stack); stack.push(WasmValue::I32(trunc_f32_to_u32(v)? as i32)); Ok(StepOutcome::Continue) } Instruction::I32TruncF64S => { let v = pop_f64(stack); stack.push(WasmValue::I32(trunc_f64_to_i32(v)?)); Ok(StepOutcome::Continue) } Instruction::I32TruncF64U => { let v = pop_f64(stack); stack.push(WasmValue::I32(trunc_f64_to_u32(v)? as i32)); Ok(StepOutcome::Continue) } Instruction::I64ExtendI32S => { let v = pop_i32(stack); stack.push(WasmValue::I64(v as i64)); Ok(StepOutcome::Continue) } Instruction::I64ExtendI32U => { let v = pop_i32(stack); stack.push(WasmValue::I64(v as u32 as i64)); Ok(StepOutcome::Continue) } Instruction::I64TruncF32S => { let v = pop_f32(stack); stack.push(WasmValue::I64(trunc_f32_to_i64(v)?)); Ok(StepOutcome::Continue) } Instruction::I64TruncF32U => { let v = pop_f32(stack); stack.push(WasmValue::I64(trunc_f32_to_u64(v)? as i64)); Ok(StepOutcome::Continue) } Instruction::I64TruncF64S => { let v = pop_f64(stack); stack.push(WasmValue::I64(trunc_f64_to_i64(v)?)); Ok(StepOutcome::Continue) } Instruction::I64TruncF64U => { let v = pop_f64(stack); stack.push(WasmValue::I64(trunc_f64_to_u64(v)? as i64)); Ok(StepOutcome::Continue) } Instruction::F32ConvertI32S => { let v = pop_i32(stack); stack.push(WasmValue::F32((v as f32).to_bits())); Ok(StepOutcome::Continue) } Instruction::F32ConvertI32U => { let v = pop_i32(stack); stack.push(WasmValue::F32((v as u32 as f32).to_bits())); Ok(StepOutcome::Continue) } Instruction::F32ConvertI64S => { let v = pop_i64(stack); stack.push(WasmValue::F32((v as f32).to_bits())); Ok(StepOutcome::Continue) } Instruction::F32ConvertI64U => { let v = pop_i64(stack); stack.push(WasmValue::F32((v as u64 as f32).to_bits())); Ok(StepOutcome::Continue) } Instruction::F32DemoteF64 => { let v = pop_f64(stack); stack.push(WasmValue::F32((v as f32).to_bits())); Ok(StepOutcome::Continue) } Instruction::F64ConvertI32S => { let v = pop_i32(stack); stack.push(WasmValue::F64((v as f64).to_bits())); Ok(StepOutcome::Continue) } Instruction::F64ConvertI32U => { let v = pop_i32(stack); stack.push(WasmValue::F64((v as u32 as f64).to_bits())); Ok(StepOutcome::Continue) } Instruction::F64ConvertI64S => { let v = pop_i64(stack); stack.push(WasmValue::F64((v as f64).to_bits())); Ok(StepOutcome::Continue) } Instruction::F64ConvertI64U => { let v = pop_i64(stack); stack.push(WasmValue::F64((v as u64 as f64).to_bits())); Ok(StepOutcome::Continue) } Instruction::F64PromoteF32 => { let v = pop_f32(stack); stack.push(WasmValue::F64((v as f64).to_bits())); Ok(StepOutcome::Continue) } Instruction::I32ReinterpretF32 => { let bits = match pop_any(stack) { WasmValue::F32(b) => b, other => unreachable!("expected f32, got {:?}", other), }; stack.push(WasmValue::I32(bits as i32)); Ok(StepOutcome::Continue) } Instruction::I64ReinterpretF64 => { let bits = match pop_any(stack) { WasmValue::F64(b) => b, other => unreachable!("expected f64, got {:?}", other), }; stack.push(WasmValue::I64(bits as i64)); Ok(StepOutcome::Continue) } Instruction::F32ReinterpretI32 => { let v = pop_i32(stack); stack.push(WasmValue::F32(v as u32)); Ok(StepOutcome::Continue) } Instruction::F64ReinterpretI64 => { let v = pop_i64(stack); stack.push(WasmValue::F64(v as u64)); Ok(StepOutcome::Continue) }
// ---- Sign-extension (post-MVP) --------------------------- Instruction::I32Extend8S => un_i32(stack, |a| (a as i8) as i32), Instruction::I32Extend16S => un_i32(stack, |a| (a as i16) as i32), Instruction::I64Extend8S => un_i64(stack, |a| (a as i8) as i64), Instruction::I64Extend16S => un_i64(stack, |a| (a as i16) as i64), Instruction::I64Extend32S => un_i64(stack, |a| (a as i32) as i64),
// ---- Saturating float-to-int ----------------------------- Instruction::I32TruncSatF32S => { let v = pop_f32(stack); stack.push(WasmValue::I32(trunc_sat_f32_to_i32(v))); Ok(StepOutcome::Continue) } Instruction::I32TruncSatF32U => { let v = pop_f32(stack); stack.push(WasmValue::I32(trunc_sat_f32_to_u32(v) as i32)); Ok(StepOutcome::Continue) } Instruction::I32TruncSatF64S => { let v = pop_f64(stack); stack.push(WasmValue::I32(trunc_sat_f64_to_i32(v))); Ok(StepOutcome::Continue) } Instruction::I32TruncSatF64U => { let v = pop_f64(stack); stack.push(WasmValue::I32(trunc_sat_f64_to_u32(v) as i32)); Ok(StepOutcome::Continue) } Instruction::I64TruncSatF32S => { let v = pop_f32(stack); stack.push(WasmValue::I64(trunc_sat_f32_to_i64(v))); Ok(StepOutcome::Continue) } Instruction::I64TruncSatF32U => { let v = pop_f32(stack); stack.push(WasmValue::I64(trunc_sat_f32_to_u64(v) as i64)); Ok(StepOutcome::Continue) } Instruction::I64TruncSatF64S => { let v = pop_f64(stack); stack.push(WasmValue::I64(trunc_sat_f64_to_i64(v))); Ok(StepOutcome::Continue) } Instruction::I64TruncSatF64U => { let v = pop_f64(stack); stack.push(WasmValue::I64(trunc_sat_f64_to_u64(v) as i64)); Ok(StepOutcome::Continue) } }}
// ---- Typed pop/push helpers ------------------------------------------------
fn pop_any(stack: &mut Vec<WasmValue>) -> WasmValue { stack.pop().expect("operand stack underflow")}
fn pop_i32(stack: &mut Vec<WasmValue>) -> i32 { match pop_any(stack) { WasmValue::I32(v) => v, other => unreachable!("validator should have rejected non-i32: {:?}", other), }}
fn pop_i64(stack: &mut Vec<WasmValue>) -> i64 { match pop_any(stack) { WasmValue::I64(v) => v, other => unreachable!("validator should have rejected non-i64: {:?}", other), }}
fn pop_f32(stack: &mut Vec<WasmValue>) -> f32 { match pop_any(stack) { WasmValue::F32(bits) => f32::from_bits(bits), other => unreachable!("validator should have rejected non-f32: {:?}", other), }}
fn pop_f64(stack: &mut Vec<WasmValue>) -> f64 { match pop_any(stack) { WasmValue::F64(bits) => f64::from_bits(bits), other => unreachable!("validator should have rejected non-f64: {:?}", other), }}
fn pop_ref(stack: &mut Vec<WasmValue>) -> Ref { match pop_any(stack) { WasmValue::Ref(r) => r, other => unreachable!("validator should have rejected non-ref: {:?}", other), }}
// ---- Memory address helpers ------------------------------------------------
fn effective_addr(stack: &mut Vec<WasmValue>, memarg: &MemArg) -> u64 { let addr = pop_i32(stack) as u32; addr as u64 + memarg.offset as u64}
fn exec_load<T, F, P>( store: &mut Store, module: ModuleAddr, stack: &mut Vec<WasmValue>, memarg: &MemArg, load: F, push: P,) -> Result<StepOutcome, Trap>where F: FnOnce(&crate::runtime::memory::Memory, u64) -> Result<T, Trap>, P: FnOnce(&mut Vec<WasmValue>, T),{ let addr = effective_addr(stack, memarg); let mem_addr = store .module(module) .resolve_memory(memarg.memory) .expect("validator: memory index out of range"); let value = load(store.memory(mem_addr), addr)?; push(stack, value); Ok(StepOutcome::Continue)}
fn with_memory_mut<F>(store: &mut Store, module: ModuleAddr, mem_idx: u32, f: F) -> Result<(), Trap>where F: FnOnce(&mut crate::runtime::memory::Memory) -> Result<(), Trap>,{ let mem_addr = store .module(module) .resolve_memory(mem_idx) .expect("validator: memory index out of range"); f(store.memory_mut(mem_addr))}
fn element_slice(store: &Store, addr: ElementAddr, src: u32, len: u32) -> Result<&[Ref], Trap> { let segment = store.element(addr); let start = src as u64; let end = start .checked_add(len as u64) .ok_or(Trap::OutOfBoundsElementSegment)?; if end > segment.len() as u64 { return Err(Trap::OutOfBoundsElementSegment); } Ok(&segment.refs()[src as usize..end as usize])}
fn data_slice(store: &Store, addr: DataAddr, src: u32, len: u32) -> Result<&[u8], Trap> { let data = store.data(addr); let start = src as u64; let end = start .checked_add(len as u64) .ok_or(Trap::OutOfBoundsDataSegment)?; if end > data.len() as u64 { return Err(Trap::OutOfBoundsDataSegment); } Ok(&data.bytes()[src as usize..end as usize])}
fn table_copy( store: &mut Store, module: ModuleAddr, dst_table: u32, src_table: u32, dst: u32, src: u32, len: u32,) -> Result<(), Trap> { let dst_addr = resolve_table(store, module, dst_table); let src_addr = resolve_table(store, module, src_table);
if dst_addr == src_addr { return store.table_mut(dst_addr).copy_within(dst, src, len); }
let refs = table_slice(store, src_addr, src, len)?.to_vec(); store.table_mut(dst_addr).init(dst, &refs)}
fn table_slice(store: &Store, addr: TableAddr, src: u32, len: u32) -> Result<&[Ref], Trap> { let table = store.table(addr); let start = src as u64; let end = start .checked_add(len as u64) .ok_or(Trap::OutOfBoundsTable)?; if end > table.size() as u64 { return Err(Trap::OutOfBoundsTable); } Ok(&table.elements()[src as usize..end as usize])}
fn memory_copy( store: &mut Store, module: ModuleAddr, dst_memory: u32, src_memory: u32, dst: u32, src: u32, len: u32,) -> Result<(), Trap> { let dst_addr = resolve_memory(store, module, dst_memory); let src_addr = resolve_memory(store, module, src_memory);
if dst_addr == src_addr { return store .memory_mut(dst_addr) .copy_within(dst as u64, src as u64, len as u64); }
let bytes = memory_slice(store, src_addr, src, len)?.to_vec(); store.memory_mut(dst_addr).init(dst as u64, &bytes)}
fn memory_slice(store: &Store, addr: MemoryAddr, src: u32, len: u32) -> Result<&[u8], Trap> { let memory = store.memory(addr); let start = src as u64; let end = start .checked_add(len as u64) .ok_or(Trap::OutOfBoundsMemory)?; if end > memory.bytes().len() as u64 { return Err(Trap::OutOfBoundsMemory); } Ok(&memory.bytes()[src as usize..end as usize])}
fn resolve_table(store: &Store, module: ModuleAddr, table_idx: u32) -> TableAddr { store .module(module) .resolve_table(table_idx) .expect("validator: table index out of range")}
fn resolve_memory(store: &Store, module: ModuleAddr, memory_idx: u32) -> MemoryAddr { store .module(module) .resolve_memory(memory_idx) .expect("validator: memory index out of range")}
fn bin_i32<F: FnOnce(i32, i32) -> i32>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_i32(stack); let a = pop_i32(stack); stack.push(WasmValue::I32(f(a, b))); Ok(StepOutcome::Continue)}
fn bin_i32_try<F: FnOnce(i32, i32) -> Result<i32, Trap>>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_i32(stack); let a = pop_i32(stack); stack.push(WasmValue::I32(f(a, b)?)); Ok(StepOutcome::Continue)}
fn bin_i32_cmp<F: FnOnce(i32, i32) -> bool>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_i32(stack); let a = pop_i32(stack); stack.push(WasmValue::I32(bool_to_i32(f(a, b)))); Ok(StepOutcome::Continue)}
fn un_i32<F: FnOnce(i32) -> i32>(stack: &mut Vec<WasmValue>, f: F) -> Result<StepOutcome, Trap> { let a = pop_i32(stack); stack.push(WasmValue::I32(f(a))); Ok(StepOutcome::Continue)}
fn bin_i64<F: FnOnce(i64, i64) -> i64>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_i64(stack); let a = pop_i64(stack); stack.push(WasmValue::I64(f(a, b))); Ok(StepOutcome::Continue)}
fn bin_i64_try<F: FnOnce(i64, i64) -> Result<i64, Trap>>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_i64(stack); let a = pop_i64(stack); stack.push(WasmValue::I64(f(a, b)?)); Ok(StepOutcome::Continue)}
fn bin_i64_cmp<F: FnOnce(i64, i64) -> bool>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_i64(stack); let a = pop_i64(stack); stack.push(WasmValue::I32(bool_to_i32(f(a, b)))); Ok(StepOutcome::Continue)}
fn un_i64<F: FnOnce(i64) -> i64>(stack: &mut Vec<WasmValue>, f: F) -> Result<StepOutcome, Trap> { let a = pop_i64(stack); stack.push(WasmValue::I64(f(a))); Ok(StepOutcome::Continue)}
fn un_f32<F: FnOnce(f32) -> f32>(stack: &mut Vec<WasmValue>, f: F) -> Result<StepOutcome, Trap> { let a = pop_f32(stack); stack.push(WasmValue::F32(f(a).to_bits())); Ok(StepOutcome::Continue)}
fn bin_f32<F: FnOnce(f32, f32) -> f32>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_f32(stack); let a = pop_f32(stack); stack.push(WasmValue::F32(f(a, b).to_bits())); Ok(StepOutcome::Continue)}
fn bin_f32_cmp<F: FnOnce(f32, f32) -> bool>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_f32(stack); let a = pop_f32(stack); stack.push(WasmValue::I32(bool_to_i32(f(a, b)))); Ok(StepOutcome::Continue)}
fn un_f64<F: FnOnce(f64) -> f64>(stack: &mut Vec<WasmValue>, f: F) -> Result<StepOutcome, Trap> { let a = pop_f64(stack); stack.push(WasmValue::F64(f(a).to_bits())); Ok(StepOutcome::Continue)}
fn bin_f64<F: FnOnce(f64, f64) -> f64>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_f64(stack); let a = pop_f64(stack); stack.push(WasmValue::F64(f(a, b).to_bits())); Ok(StepOutcome::Continue)}
fn bin_f64_cmp<F: FnOnce(f64, f64) -> bool>( stack: &mut Vec<WasmValue>, f: F,) -> Result<StepOutcome, Trap> { let b = pop_f64(stack); let a = pop_f64(stack); stack.push(WasmValue::I32(bool_to_i32(f(a, b)))); Ok(StepOutcome::Continue)}
// ============================================================================// Numeric helpers — IEEE-754 details the wasm spec pins down explicitly// ============================================================================
fn bool_to_i32(b: bool) -> i32 { if b { 1 } else { 0 }}
/// Wasm `f32.min`: NaN propagates, `-0 < +0`.fn wasm_f32_min(a: f32, b: f32) -> f32 { if a.is_nan() { return canonicalize_f32_nan(a); } if b.is_nan() { return canonicalize_f32_nan(b); } if a == 0.0 && b == 0.0 { if a.is_sign_negative() { a } else { b } } else if a < b { a } else { b }}
/// Wasm `f32.max`: NaN propagates, `+0 > -0`.fn wasm_f32_max(a: f32, b: f32) -> f32 { if a.is_nan() { return canonicalize_f32_nan(a); } if b.is_nan() { return canonicalize_f32_nan(b); } if a == 0.0 && b == 0.0 { if a.is_sign_positive() { a } else { b } } else if a > b { a } else { b }}
fn wasm_f64_min(a: f64, b: f64) -> f64 { if a.is_nan() { return canonicalize_f64_nan(a); } if b.is_nan() { return canonicalize_f64_nan(b); } if a == 0.0 && b == 0.0 { if a.is_sign_negative() { a } else { b } } else if a < b { a } else { b }}
fn wasm_f64_max(a: f64, b: f64) -> f64 { if a.is_nan() { return canonicalize_f64_nan(a); } if b.is_nan() { return canonicalize_f64_nan(b); } if a == 0.0 && b == 0.0 { if a.is_sign_positive() { a } else { b } } else if a > b { a } else { b }}
/// Force a NaN payload to be quiet. Wasm requires arithmetic NaNs (quiet, top/// fraction bit set); signaling NaN inputs to non-bitpattern-preserving ops/// must be quieted in the result.fn canonicalize_f32_nan(x: f32) -> f32 { let bits = x.to_bits(); // Force quiet bit (bit 22, the top fraction bit) and zero out the rest of // the payload to land on the canonical NaN per spec §4.3.3. f32::from_bits((bits & 0x8000_0000) | 0x7FC0_0000)}
fn canonicalize_f64_nan(x: f64) -> f64 { let bits = x.to_bits(); f64::from_bits((bits & 0x8000_0000_0000_0000) | 0x7FF8_0000_0000_0000)}
/// Wasm `f32.nearest` — round-to-nearest, ties to even. Preserves the sign of/// zero results.fn nearest_f32(x: f32) -> f32 { if x.is_nan() || x.is_infinite() { return x; } if x == 0.0 { return x; } let trunc = x.trunc(); let diff = (x - trunc).abs(); let step: f32 = if x.is_sign_negative() { -1.0 } else { 1.0 }; let result = if diff < 0.5 { trunc } else if diff > 0.5 { trunc + step } else { // Exact tie — round to the nearest even integer. if (trunc as i64).rem_euclid(2) == 0 { trunc } else { trunc + step } }; if result == 0.0 && x.is_sign_negative() { -0.0 } else { result }}
fn nearest_f64(x: f64) -> f64 { if x.is_nan() || x.is_infinite() { return x; } if x == 0.0 { return x; } let trunc = x.trunc(); let diff = (x - trunc).abs(); let step: f64 = if x.is_sign_negative() { -1.0 } else { 1.0 }; let result = if diff < 0.5 { trunc } else if diff > 0.5 { trunc + step } else if (trunc as i128).rem_euclid(2) == 0 { trunc } else { trunc + step }; if result == 0.0 && x.is_sign_negative() { -0.0 } else { result }}
// ---- Trapping float-to-int conversions ------------------------------------//// Bounds rationale: an f32/f64 input is acceptable iff its truncation lies in// the destination integer's range. The exact-representable boundary for each// (target, source) pair is what we compare against — for f32 -> i32 the lower// bound is `-2^31` (exactly representable) and the upper bound is `2^31` (the// first value above the i32 range that f32 can name).
fn trunc_f32_to_i32(x: f32) -> Result<i32, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if !(-2_147_483_648.0_f32..2_147_483_648.0_f32).contains(&t) { return Err(Trap::IntegerOverflow); } Ok(t as i32)}
fn trunc_f32_to_u32(x: f32) -> Result<u32, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if t <= -1.0_f32 || t >= 4_294_967_296.0_f32 { return Err(Trap::IntegerOverflow); } Ok(t as u32)}
fn trunc_f64_to_i32(x: f64) -> Result<i32, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if !(-2_147_483_648.0_f64..=2_147_483_647.0_f64).contains(&t) { return Err(Trap::IntegerOverflow); } Ok(t as i32)}
fn trunc_f64_to_u32(x: f64) -> Result<u32, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if t <= -1.0_f64 || t > 4_294_967_295.0_f64 { return Err(Trap::IntegerOverflow); } Ok(t as u32)}
fn trunc_f32_to_i64(x: f32) -> Result<i64, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if !(-9_223_372_036_854_775_808.0_f32..9_223_372_036_854_775_808.0_f32).contains(&t) { return Err(Trap::IntegerOverflow); } Ok(t as i64)}
fn trunc_f32_to_u64(x: f32) -> Result<u64, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if t <= -1.0_f32 || t >= 18_446_744_073_709_551_616.0_f32 { return Err(Trap::IntegerOverflow); } Ok(t as u64)}
fn trunc_f64_to_i64(x: f64) -> Result<i64, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if !(-9_223_372_036_854_775_808.0_f64..9_223_372_036_854_775_808.0_f64).contains(&t) { return Err(Trap::IntegerOverflow); } Ok(t as i64)}
fn trunc_f64_to_u64(x: f64) -> Result<u64, Trap> { if x.is_nan() { return Err(Trap::InvalidConversion); } if !x.is_finite() { return Err(Trap::IntegerOverflow); } let t = x.trunc(); if t <= -1.0_f64 || t >= 18_446_744_073_709_551_616.0_f64 { return Err(Trap::IntegerOverflow); } Ok(t as u64)}
// ---- Saturating float-to-int conversions -----------------------------------//// Rust's `as` for float → integer already implements saturating semantics// matching the spec (NaN → 0, ±∞ → bounds, out-of-range → clamp). The// wrappers exist so the dispatch table reads symmetrically with the trapping// variants.
fn trunc_sat_f32_to_i32(x: f32) -> i32 { x as i32}fn trunc_sat_f32_to_u32(x: f32) -> u32 { x as u32}fn trunc_sat_f64_to_i32(x: f64) -> i32 { x as i32}fn trunc_sat_f64_to_u32(x: f64) -> u32 { x as u32}fn trunc_sat_f32_to_i64(x: f32) -> i64 { x as i64}fn trunc_sat_f32_to_u64(x: f32) -> u64 { x as u64}fn trunc_sat_f64_to_i64(x: f64) -> i64 { x as i64}fn trunc_sat_f64_to_u64(x: f64) -> u64 { x as u64}