diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 5a9d064..1c2ae13 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -53,15 +53,16 @@ jobs: # error` is taken from the `gating` flag. Promote an exploration # harness to gating once its bug-list stabilises. include: - # `wasm_ops_lower_or_error` temporarily demoted from gating per - # issue #121 — wasm_to_ir's inst_id-as-vreg-slot model breaks for - # Drop / LocalSet / Store / Block / etc. The five rounds of fixes - # in PR #117 close the Nop/Unreachable/Return slot crashes; the - # remaining Drop-class shapes need the proper slot_stack refactor - # tracked in #121. Will re-promote to `gating: true` when that - # lands. + # `wasm_ops_lower_or_error` is gating again. It was demoted in + # PR #117 because wasm_to_ir's inst_id-as-vreg-slot model crashed + # on Drop / LocalSet / Store / Block shapes; the slot_stack + # refactor (issue #121) closed those. The last panic site in the + # optimized path was `ir_to_arm`'s defensive `get_arm_reg`, which + # now returns `Result` instead of `panic!`-ing — the whole + # `optimize_full` + `ir_to_arm` path is panic-free, so this + # harness (contract: "lower or Err, never panic") gates again. - target: wasm_ops_lower_or_error - gating: false # demoted pending issue #121 + gating: true - target: wasm_to_ir_roundtrip_op_coverage gating: true - target: i64_lowering_doesnt_clobber_params diff --git a/crates/synth-backend/src/arm_backend.rs b/crates/synth-backend/src/arm_backend.rs index 1297dde..e2370ad 100644 --- a/crates/synth-backend/src/arm_backend.rs +++ b/crates/synth-backend/src/arm_backend.rs @@ -178,17 +178,21 @@ fn compile_wasm_to_arm( }; let bridge = OptimizerBridge::with_config(opt_config); - match bridge.optimize_full(wasm_ops) { - Ok((opt_ir, _cfg, _stats)) => { - let arm_ops = bridge.ir_to_arm(&opt_ir, num_params as usize); - arm_ops - .into_iter() - .map(|op| ArmInstruction { - op, - source_line: None, - }) - .collect() - } + // `ir_to_arm` now returns `Result` — an `Err` means the optimized path + // hit an unmapped vreg (issue-#93-class). Treat it identically to an + // `optimize_full` failure: fall back to the direct selector rather + // than propagating, so the function still compiles correctly. + match bridge + .optimize_full(wasm_ops) + .and_then(|(opt_ir, _cfg, _stats)| bridge.ir_to_arm(&opt_ir, num_params as usize)) + { + Ok(arm_ops) => arm_ops + .into_iter() + .map(|op| ArmInstruction { + op, + source_line: None, + }) + .collect(), // Issue #120: the optimized path declines modules it cannot lower // (notably scalar f32/f64 ops — the IR has no float opcodes). Fall // back to the direct instruction selector, which handles f32 via diff --git a/crates/synth-synthesis/src/optimizer_bridge.rs b/crates/synth-synthesis/src/optimizer_bridge.rs index 4eedb96..c6c7935 100644 --- a/crates/synth-synthesis/src/optimizer_bridge.rs +++ b/crates/synth-synthesis/src/optimizer_bridge.rs @@ -614,14 +614,20 @@ impl OptimizerBridge { }}; } - // Helper for unary i32 ops: pops 1 source. + // Helper for unary i32 ops: pops 1 source. Returns `?` on + // underflow so a malformed input that slipped past the pre-flight + // check produces a typed Err instead of panicking — matches the + // pop_i32_binary! handling. (The earlier slot_stack Result + // conversion missed this macro because its `.expect(...)` ended + // with `,` not `;`; the re-gated fuzz harness caught it.) macro_rules! pop_i32_unary { () => {{ - OptReg( - slot_stack - .pop() - .expect("wasm validator + pre-flight check guarantee stack depth"), - ) + let src = slot_stack.pop().ok_or_else(|| { + synth_core::Error::validation( + "wasm stack underflow in wasm_to_ir (slot_stack pop on empty)", + ) + })?; + OptReg(src) }}; } @@ -1905,11 +1911,13 @@ impl OptimizerBridge { // Fallback for unsupported ops. // // We do NOT touch slot_stack here — by design. If an unknown - // op had a stack effect, downstream consumers will fail - // *loudly* via `slot_stack.pop().expect(...)` instead of - // silently mis-binding vregs. That's exactly the bug-finder - // class introduced for issue #93; the slot_stack rework - // (#121) preserves it. + // op had a stack effect, downstream consumers will surface a + // typed `Err` via `slot_stack.pop().ok_or_else(...)?` instead + // of silently mis-binding vregs. That's the same bug-finder + // class introduced for issue #93 (the slot_stack rework, #121, + // preserved it); the only change is that the failure mode is + // now an `Err` propagation, not a `panic!`, per the harness + // contract that lowering returns Err on malformed input. _ => Opcode::Nop, }; @@ -2163,7 +2171,14 @@ impl OptimizerBridge { /// /// This function maps the register-based optimizer IR to ARM instructions. /// For function parameters, local indices 0..num_params map to R0..R3 per AAPCS. - pub fn ir_to_arm(&self, instructions: &[Instruction], num_params: usize) -> Vec { + /// + /// Returns `Err` (rather than panicking) when an IR virtual register has no + /// assigned ARM register and no spill slot. With valid IR produced by + /// `wasm_to_ir` (itself guarded by the `wasm_stack_check` pre-flight) this + /// never happens, so an `Err` here is still the issue-#93-class bug signal + /// it always was — it just no longer kills the process. Callers in the + /// fuzz/AOT pipeline propagate the `Err` instead of crashing. + pub fn ir_to_arm(&self, instructions: &[Instruction], num_params: usize) -> Result> { use crate::rules::{ArmOp, Operand2, Reg}; use std::collections::HashMap; @@ -2236,36 +2251,42 @@ impl OptimizerBridge { // Also checks spill slots — if a vreg was spilled, returns R12 (IP scratch). // Callers should also call `reload_spill` to emit the actual load instruction. // - // PANICS if the vreg is neither mapped nor spilled. The previous behavior was - // a silent `Reg::R0` fallback, which produced miscompilation: a downstream - // instruction reading the "unknown" vreg would silently consume whatever - // R0 happens to hold (often a live caller param or memset's dest pointer). - // Issue #93 was exactly this — `wasm_to_ir` had no handler for - // `I64ExtendI32U`/`I64ExtendI32S`/`I32WrapI64`, so the IR they should have - // produced never got mapped to ARM regs, and downstream i64 shifts read R0 - // as their `rm_lo`/`rm_hi`, destroying the loop counter on real silicon. - // A loud panic here is strictly better than a quiet miscompilation — - // crash the compiler, not the firmware. - // History: PR #108 (47-site AAPCS audit) deferred this panic, leaving a + // Returns `Err` if the vreg is neither mapped nor spilled. The original + // behaviour here was a silent `Reg::R0` fallback, which produced + // miscompilation: a downstream instruction reading the "unknown" vreg + // would silently consume whatever R0 happens to hold (often a live + // caller param or memset's dest pointer). Issue #93 was exactly this — + // `wasm_to_ir` had no handler for `I64ExtendI32U`/`I64ExtendI32S`/ + // `I32WrapI64`, so the IR they should have produced never got mapped to + // ARM regs, and downstream i64 shifts read R0 as their `rm_lo`/`rm_hi`, + // destroying the loop counter on real silicon. + // + // PR #101 replaced the silent fallback with a hard `panic!` — strictly + // better than a quiet miscompilation. This converts that panic into a + // typed `Err`: still a loud, diagnostic "this shouldn't happen with + // valid IR" signal (the issue-#93-class bug-finder), but it no longer + // kills the process, so the optimized lowering path is panic-free and + // the `wasm_ops_lower_or_error` fuzz harness can gate on it again. + // History: PR #108 (47-site AAPCS audit) deferred the guard behind a // silent R0 fallback while one last v13 case in fib compilation was // tracked down. PR #109 fixed that case (WasmOp::Call had no wasm_to_ir - // handler, falling through to Opcode::Nop). With #109 stacked under - // this PR, every known wasm_to_ir gap is closed and this panic is safe - // to ship as a permanent guard against the class. + // handler, falling through to Opcode::Nop). With every known wasm_to_ir + // gap closed, the guard remains as a permanent defence against the + // class — now as a recoverable `Err` rather than a panic. let get_arm_reg = - |vreg: &OptReg, map: &HashMap, spills: &HashMap| -> Reg { + |vreg: &OptReg, map: &HashMap, spills: &HashMap| -> Result { if let Some(&r) = map.get(&vreg.0) { - r + Ok(r) } else if spills.contains_key(&vreg.0) { // Will be reloaded into R12 by reload_spill - Reg::R12 + Ok(Reg::R12) } else { - panic!( + Err(Error::synthesis(format!( "synth internal compiler error: vreg v{} has no assigned \ - ARM register and no spill slot. This is a wasm_to_ir bug — \ - likely a wasm op whose result is unmapped (see issue #93).", + ARM register and no spill slot. This is a wasm_to_ir bug — \ + likely a wasm op whose result is unmapped (see issue #93).", vreg.0 - ); + ))) } }; @@ -2521,7 +2542,7 @@ impl OptimizerBridge { // Store: write to local variable Opcode::Store { src, addr } => { - let rs = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rs = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; // Track which register holds this local's value // Special case: synthetic local 255 is used for preserving conditions // across nested selects - use R11 which is rarely used elsewhere @@ -2648,9 +2669,9 @@ impl OptimizerBridge { // it's still live in `vreg_to_arm`. Opcode::Add { dest, src1, src2 } => { reload_spill(src1, &spilled_vregs, &mut arm_instrs); - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; reload_spill(src2, &spilled_vregs, &mut arm_instrs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2675,9 +2696,9 @@ impl OptimizerBridge { Opcode::Sub { dest, src1, src2 } => { reload_spill(src1, &spilled_vregs, &mut arm_instrs); - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; reload_spill(src2, &spilled_vregs, &mut arm_instrs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2701,9 +2722,9 @@ impl OptimizerBridge { Opcode::Mul { dest, src1, src2 } => { reload_spill(src1, &spilled_vregs, &mut arm_instrs); - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; reload_spill(src2, &spilled_vregs, &mut arm_instrs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2722,8 +2743,8 @@ impl OptimizerBridge { } Opcode::DivS { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2784,8 +2805,8 @@ impl OptimizerBridge { } Opcode::DivU { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2811,8 +2832,8 @@ impl OptimizerBridge { // Remainder: rd = rn - (rn / rm) * rm Opcode::RemS { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2848,8 +2869,8 @@ impl OptimizerBridge { } Opcode::RemU { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2885,8 +2906,8 @@ impl OptimizerBridge { // Bitwise operations Opcode::And { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2903,8 +2924,8 @@ impl OptimizerBridge { } Opcode::Or { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2921,8 +2942,8 @@ impl OptimizerBridge { } Opcode::Xor { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2942,8 +2963,8 @@ impl OptimizerBridge { // ARM LSL/LSR/ASR by register use low byte, so shift >= 32 // produces 0 (not wrapping). We must mask first. Opcode::Shl { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2966,8 +2987,8 @@ impl OptimizerBridge { } Opcode::ShrS { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -2989,8 +3010,8 @@ impl OptimizerBridge { } Opcode::ShrU { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -3015,8 +3036,8 @@ impl OptimizerBridge { // uses low byte (ROR by 32 = no-op on ARM, but WASM wants same) // Actually ROR wraps naturally, but we mask for consistency Opcode::Rotr { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -3040,8 +3061,8 @@ impl OptimizerBridge { // Rotate left: ROTL(x, n) = ROR(x, 32 - (n & 31)) // Emit: AND R12, Rm, #31; RSB R12, R12, #32; ROR.W Rd, Rn, R12 Opcode::Rotl { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -3072,7 +3093,7 @@ impl OptimizerBridge { // Bit count operations (unary) Opcode::Clz { dest, src } => { - let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rm]); vreg_to_arm.insert(dest.0, rd); @@ -3081,7 +3102,7 @@ impl OptimizerBridge { } Opcode::Ctz { dest, src } => { - let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rm]); vreg_to_arm.insert(dest.0, rd); @@ -3092,7 +3113,7 @@ impl OptimizerBridge { } Opcode::Popcnt { dest, src } => { - let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rm]); vreg_to_arm.insert(dest.0, rd); @@ -3103,7 +3124,7 @@ impl OptimizerBridge { // Sign extension operations (unary) Opcode::Extend8S { dest, src } => { - let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rm]); vreg_to_arm.insert(dest.0, rd); @@ -3112,7 +3133,7 @@ impl OptimizerBridge { } Opcode::Extend16S { dest, src } => { - let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rm]); vreg_to_arm.insert(dest.0, rd); @@ -3122,7 +3143,7 @@ impl OptimizerBridge { // Eqz - compare with zero (unary) Opcode::Eqz { dest, src } => { - let rn = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rn]); vreg_to_arm.insert(dest.0, rd); @@ -3150,8 +3171,8 @@ impl OptimizerBridge { | Opcode::GtU { dest, src1, src2 } | Opcode::GeS { dest, src1, src2 } | Opcode::GeU { dest, src1, src2 } => { - let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs); - let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs); + let rn = get_arm_reg(src1, &vreg_to_arm, &spilled_vregs)?; + let rm = get_arm_reg(src2, &vreg_to_arm, &spilled_vregs)?; // Pre-fix this hardcoded `Reg::R7` to keep the SetCond // encodable as 16-bit Thumb (which can only address R0-R7). // R7 is callee-saved (no AAPCS clobber) but the hardcode @@ -3200,7 +3221,7 @@ impl OptimizerBridge { } Opcode::CondBranch { cond, target } => { - let rcond = get_arm_reg(cond, &vreg_to_arm, &spilled_vregs); + let rcond = get_arm_reg(cond, &vreg_to_arm, &spilled_vregs)?; arm_instrs.push(ArmOp::Cmp { rn: rcond, op2: Operand2::Imm(0), @@ -3216,7 +3237,7 @@ impl OptimizerBridge { Opcode::Return { value } => { if let Some(v) = value { - let rv = get_arm_reg(v, &vreg_to_arm, &spilled_vregs); + let rv = get_arm_reg(v, &vreg_to_arm, &spilled_vregs)?; if rv != Reg::R0 { arm_instrs.push(ArmOp::Mov { rd: Reg::R0, @@ -3236,9 +3257,9 @@ impl OptimizerBridge { val_false, cond, } => { - let r_cond = get_arm_reg(cond, &vreg_to_arm, &spilled_vregs); - let r_true = get_arm_reg(val_true, &vreg_to_arm, &spilled_vregs); - let r_false = get_arm_reg(val_false, &vreg_to_arm, &spilled_vregs); + let r_cond = get_arm_reg(cond, &vreg_to_arm, &spilled_vregs)?; + let r_true = get_arm_reg(val_true, &vreg_to_arm, &spilled_vregs)?; + let r_false = get_arm_reg(val_false, &vreg_to_arm, &spilled_vregs)?; // Pre-fix this hardcoded R3, clobbering the 4th AAPCS // arg on every Select. Use `alloc_i32_scratch` so the @@ -3400,10 +3421,10 @@ impl OptimizerBridge { // i64.add: rd = rn + rm using the actual operand regs from // vreg_to_arm — NOT hardcoded R0:R1/R2:R3 (which would clobber // AAPCS param regs). - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -3431,10 +3452,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -3462,8 +3483,8 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; // Issue #94: `i64.and` against a constant low-half mask // (typically `0xFFFFFFFF` to extract the lo32 of a u64-packed // FFI return). The lo half collapses to a no-op rename, the @@ -3491,8 +3512,8 @@ impl OptimizerBridge { continue; } } - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -3520,10 +3541,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -3551,10 +3572,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -3592,10 +3613,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3616,10 +3637,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3640,10 +3661,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3664,10 +3685,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3688,10 +3709,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3712,10 +3733,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3737,10 +3758,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3761,10 +3782,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3785,10 +3806,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3809,10 +3830,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCond { @@ -3831,8 +3852,8 @@ impl OptimizerBridge { src_lo, src_hi, } => { - let rn_lo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs)?; let (rd, _) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest.0, rd); arm_instrs.push(ArmOp::I64SetCondZ { rd, rn_lo, rn_hi }); @@ -3857,8 +3878,8 @@ impl OptimizerBridge { src_lo, src_hi, } => { - let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); - let rnhi_src = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi_src = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); if rd_hi != rnhi_src { @@ -3884,8 +3905,8 @@ impl OptimizerBridge { src_lo, src_hi, } => { - let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); - let rnhi_src = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi_src = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); if rd_hi != rnhi_src { @@ -3911,8 +3932,8 @@ impl OptimizerBridge { src_lo, src_hi, } => { - let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); - let rnhi_src = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi_src = get_arm_reg(src_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); if rd_hi != rnhi_src { @@ -3938,7 +3959,7 @@ impl OptimizerBridge { dest_hi, src_lo, } => { - let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -3954,7 +3975,7 @@ impl OptimizerBridge { dest_hi, src_lo, } => { - let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -3970,7 +3991,7 @@ impl OptimizerBridge { dest_hi, src_lo, } => { - let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -3998,7 +4019,7 @@ impl OptimizerBridge { dest_hi, src, } => { - let src_arm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let src_arm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let (new_lo, new_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); if src_arm != new_lo { @@ -4027,7 +4048,7 @@ impl OptimizerBridge { dest_hi, src, } => { - let src_arm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let src_arm = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let (new_lo, new_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); if src_arm != new_lo { @@ -4054,7 +4075,7 @@ impl OptimizerBridge { // so the lookup of `dest` is already correctly mapped to // the i64 lo half's ARM register. Emit no ARM code. Opcode::I32WrapI64 { dest, src_lo } => { - let src_arm = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs); + let src_arm = get_arm_reg(src_lo, &vreg_to_arm, &spilled_vregs)?; vreg_to_arm.insert(dest.0, src_arm); last_result_vreg = Some(dest.0); is_i64_result = false; @@ -4069,10 +4090,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -4099,10 +4120,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -4129,8 +4150,8 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; // Issue #94 (signed variant): `i64.shr_s 32; i32.wrap_i64` // also extracts the upper 32 bits unchanged. dest_lo = rn_hi, // dest_hi = sign-extension of rn_hi (ASR #31 of rn_hi). @@ -4154,8 +4175,8 @@ impl OptimizerBridge { last_result_vreg_hi = Some(dest_hi.0); is_i64_result = true; } else { - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -4183,8 +4204,8 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); + let rn_lo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rn_hi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; // Issue #94: u64-packed FFI return — `i64.shr_u 32` extracts // the high 32 bits, which are already sitting in `rn_hi`. Skip // the 38-byte runtime shift sequence; just rename `dest_lo` @@ -4212,8 +4233,8 @@ impl OptimizerBridge { last_result_vreg_hi = Some(dest_hi.0); is_i64_result = true; } else { - let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rm_lo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rm_hi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rd_lo, rd_hi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rd_lo); @@ -4241,9 +4262,9 @@ impl OptimizerBridge { src2_lo, .. } => { - let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let shift = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let shift = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -4269,9 +4290,9 @@ impl OptimizerBridge { src2_lo, .. } => { - let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let shift = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let shift = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -4297,10 +4318,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -4327,10 +4348,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -4357,10 +4378,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -4387,10 +4408,10 @@ impl OptimizerBridge { src2_lo, src2_hi, } => { - let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs); - let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs); - let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs); - let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs); + let rnlo = get_arm_reg(src1_lo, &vreg_to_arm, &spilled_vregs)?; + let rnhi = get_arm_reg(src1_hi, &vreg_to_arm, &spilled_vregs)?; + let rmlo = get_arm_reg(src2_lo, &vreg_to_arm, &spilled_vregs)?; + let rmhi = get_arm_reg(src2_hi, &vreg_to_arm, &spilled_vregs)?; let (rdlo, rdhi) = alloc_i64_pair(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs); vreg_to_arm.insert(dest_lo.0, rdlo); @@ -4427,7 +4448,7 @@ impl OptimizerBridge { // first AAPCS param on every local.tee even when neither // src nor dest had anything to do with R0. Opcode::Copy { dest, src } => { - let rs = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rs = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[rs]); vreg_to_arm.insert(dest.0, rd); @@ -4442,7 +4463,7 @@ impl OptimizerBridge { // TeeStore: Store to local AND keep value on stack (local.tee) Opcode::TeeStore { dest, src, addr } => { - let rs = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let rs = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; // For non-param locals, move to the local's dedicated register if (*addr as usize) >= num_params { @@ -4498,7 +4519,7 @@ impl OptimizerBridge { // argument on every `i32.load`. Use the scratch helper so // the destination is picked from the callee-saved bank. Opcode::MemLoad { dest, addr, offset } => { - let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs); + let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -4538,8 +4559,8 @@ impl OptimizerBridge { // MemStore: store 32-bit value to linear memory // Generates: MOVW R12, #base_lo; MOVT R12, #base_hi; ADD R12, R12, Raddr; STR Rsrc, [R12, #offset] Opcode::MemStore { src, addr, offset } => { - let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs); - let r_src = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs)?; + let r_src = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; // Linear memory base address: 0x20000100 (in SRAM, above stack area) let base: u32 = 0x20000100; @@ -4584,7 +4605,7 @@ impl OptimizerBridge { width, signed, } => { - let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs); + let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch( &vreg_to_arm, &local_to_reg, @@ -4633,8 +4654,8 @@ impl OptimizerBridge { offset, width, } => { - let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs); - let r_src = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let r_addr = get_arm_reg(addr, &vreg_to_arm, &spilled_vregs)?; + let r_src = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; let base: u32 = 0x20000100; let base_lo = (base & 0xFFFF) as u16; @@ -4686,7 +4707,7 @@ impl OptimizerBridge { // `global.set N` — store the popped i32 to global N. Opcode::GlobalSet { src, idx } => { - let r_src = get_arm_reg(src, &vreg_to_arm, &spilled_vregs); + let r_src = get_arm_reg(src, &vreg_to_arm, &spilled_vregs)?; arm_instrs.push(ArmOp::Str { rd: r_src, addr: crate::rules::MemAddr::imm(Reg::R9, (*idx as i32) * 4), @@ -4710,7 +4731,7 @@ impl OptimizerBridge { // a stub that returns -1 (the wasm spec's "grow failed" // sentinel). The `delta` is read but discarded. Opcode::MemoryGrow { dest, delta } => { - let _ = get_arm_reg(delta, &vreg_to_arm, &spilled_vregs); + let _ = get_arm_reg(delta, &vreg_to_arm, &spilled_vregs)?; let rd = alloc_i32_scratch(&vreg_to_arm, &local_to_reg, ¶m_reserved_regs, &[]); vreg_to_arm.insert(dest.0, rd); @@ -5122,7 +5143,7 @@ impl OptimizerBridge { arm_instrs.push(ArmOp::Bx { rm: Reg::LR }); } - arm_instrs + Ok(arm_instrs) } } @@ -5500,7 +5521,7 @@ mod tests { block_id: 0, is_dead: false, }]; - let arm = bridge.ir_to_arm(&instrs, 4); + let arm = bridge.ir_to_arm(&instrs, 4).expect("valid IR lowers"); // The I64Const itself is encoded with Movw/Movt — those MUST NOT // target R0..R3. The epilogue Mov-into-R0/R1 is part of the AAPCS // return convention and is correct. @@ -5539,7 +5560,7 @@ mod tests { block_id: 0, is_dead: false, }]; - let arm = bridge.ir_to_arm(&instrs, 0); + let arm = bridge.ir_to_arm(&instrs, 0).expect("valid IR lowers"); assert!(!arm.is_empty()); } @@ -5587,7 +5608,7 @@ mod tests { is_dead: false, }, ]; - let arm = bridge.ir_to_arm(&instrs, 4); + let arm = bridge.ir_to_arm(&instrs, 4).expect("valid IR lowers"); // We must see at least one Adds and at least one Adc — that's the // characteristic shape of a 64-bit add on 32-bit ARM. let has_adds = arm.iter().any(|op| matches!(op, ArmOp::Adds { .. })); @@ -5635,7 +5656,7 @@ mod tests { is_dead: false, }, ]; - let arm = bridge.ir_to_arm(&instrs, 0); + let arm = bridge.ir_to_arm(&instrs, 0).expect("valid IR lowers"); let has_subs = arm.iter().any(|op| matches!(op, ArmOp::Subs { .. })); let has_sbc = arm.iter().any(|op| matches!(op, ArmOp::Sbc { .. })); assert!(has_subs, "i64.sub should emit SUBS for the low half"); @@ -5681,7 +5702,7 @@ mod tests { is_dead: false, }, ]; - let arm = bridge.ir_to_arm(&instrs, 0); + let arm = bridge.ir_to_arm(&instrs, 0).expect("valid IR lowers"); let orr_count = arm .iter() .filter(|op| matches!(op, ArmOp::Orr { .. })) @@ -5704,7 +5725,7 @@ mod tests { block_id: 0, is_dead: false, }]; - let arm = bridge.ir_to_arm(&instrs, 2); + let arm = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); // Should not have written to R0 or R1 (the reserved param regs). for op in &arm { if let ArmOp::Mov { @@ -5838,7 +5859,7 @@ mod tests { WasmOp::I32WrapI64, ]; let (instrs, _, _) = bridge.optimize_full(&after_ops).unwrap(); - let arm_after = bridge.ir_to_arm(&instrs, 2); + let arm_after = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); let bytes_after = count_arm_byte_size(&arm_after); // Pre-fix proxy: shift-by-7 takes the generic ArmOp::I64ShrU path, @@ -5850,7 +5871,7 @@ mod tests { WasmOp::I32WrapI64, ]; let (instrs, _, _) = bridge.optimize_full(&before_ops).unwrap(); - let arm_before = bridge.ir_to_arm(&instrs, 2); + let arm_before = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); let bytes_before = count_arm_byte_size(&arm_before); println!( @@ -5883,7 +5904,7 @@ mod tests { // num_params = 1 (one i64 param occupies R0:R1 per AAPCS — but we // pass 2 to ir_to_arm because each i64 counts as two AAPCS slots // in the codegen's accounting). - let arm = bridge.ir_to_arm(&instrs, 2); + let arm = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); // After fix: NO ArmOp::I64ShrU should be emitted. The 38-byte runtime // shift sequence is the bug we're fixing. @@ -5920,7 +5941,7 @@ mod tests { ]; let (instrs, _, _) = bridge.optimize_full(&wasm_ops).unwrap(); - let arm = bridge.ir_to_arm(&instrs, 2); + let arm = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); let has_runtime_shift = arm.iter().any(|op| matches!(op, ArmOp::I64ShrS { .. })); assert!( @@ -5963,7 +5984,7 @@ mod tests { ]; let (instrs, _, _) = bridge.optimize_full(&wasm_ops).unwrap(); - let arm = bridge.ir_to_arm(&instrs, 2); + let arm = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); // After fix: at most one AND should remain, and it shouldn't be the // pair of ANDs from the generic i64.and lowering. (We allow zero ANDs @@ -5993,7 +6014,7 @@ mod tests { ]; let (instrs, _, _) = bridge.optimize_full(&wasm_ops).unwrap(); - let arm = bridge.ir_to_arm(&instrs, 2); + let arm = bridge.ir_to_arm(&instrs, 2).expect("valid IR lowers"); let has_runtime_shift = arm.iter().any(|op| matches!(op, ArmOp::I64ShrU { .. })); assert!( @@ -6051,7 +6072,7 @@ mod tests { }, ]; - let arm = bridge.ir_to_arm(&instrs, 0); + let arm = bridge.ir_to_arm(&instrs, 0).expect("valid IR lowers"); let has_runtime_shift = arm.iter().any(|op| matches!(op, ArmOp::I64ShrU { .. })); assert!( !has_runtime_shift, diff --git a/crates/synth-synthesis/tests/audit_optimized_aapcs.rs b/crates/synth-synthesis/tests/audit_optimized_aapcs.rs index 3badf06..a85c761 100644 --- a/crates/synth-synthesis/tests/audit_optimized_aapcs.rs +++ b/crates/synth-synthesis/tests/audit_optimized_aapcs.rs @@ -11,7 +11,7 @@ use synth_synthesis::{ArmOp, OptimizerBridge, Reg, WasmOp}; fn compile_optimized(wasm_ops: &[WasmOp], num_params: usize) -> Vec { let bridge = OptimizerBridge::new(); let (ir, _cfg, _stats) = bridge.optimize_full(wasm_ops).expect("optimize_full"); - bridge.ir_to_arm(&ir, num_params) + bridge.ir_to_arm(&ir, num_params).expect("ir_to_arm") } fn writes(op: &ArmOp) -> Vec { diff --git a/crates/synth-synthesis/tests/issue_104_i32_loadstore_cse.rs b/crates/synth-synthesis/tests/issue_104_i32_loadstore_cse.rs index c2c3dc5..d5ed486 100644 --- a/crates/synth-synthesis/tests/issue_104_i32_loadstore_cse.rs +++ b/crates/synth-synthesis/tests/issue_104_i32_loadstore_cse.rs @@ -249,7 +249,9 @@ fn compile_optimized(wasm: &[WasmOp]) -> Vec { let (ir, _cfg, _stats) = bridge .optimize_full(wasm) .expect("optimize_full should succeed for the store-load pattern"); - bridge.ir_to_arm(&ir, /* num_params = */ 2) + bridge + .ir_to_arm(&ir, /* num_params = */ 2) + .expect("ir_to_arm should succeed for the store-load pattern") } /// Execute the swapped-operand variant: param 1 = addr (in R1), diff --git a/crates/synth-synthesis/tests/issue_93_memset_i64_codegen.rs b/crates/synth-synthesis/tests/issue_93_memset_i64_codegen.rs index 0363e28..c81ac30 100644 --- a/crates/synth-synthesis/tests/issue_93_memset_i64_codegen.rs +++ b/crates/synth-synthesis/tests/issue_93_memset_i64_codegen.rs @@ -42,7 +42,9 @@ fn compile_optimized(wasm_ops: &[WasmOp], num_params: usize) -> Vec { let (ir, _cfg, _stats) = bridge .optimize_full(wasm_ops) .expect("optimize_full should succeed for valid input"); - bridge.ir_to_arm(&ir, num_params) + bridge + .ir_to_arm(&ir, num_params) + .expect("ir_to_arm should succeed for valid input") } /// Returns true if the ARM op is one of the 64-bit shift pseudo-ops (the diff --git a/crates/synth-synthesis/tests/regression_call_result_vreg.rs b/crates/synth-synthesis/tests/regression_call_result_vreg.rs index 4aae8dc..99782fe 100644 --- a/crates/synth-synthesis/tests/regression_call_result_vreg.rs +++ b/crates/synth-synthesis/tests/regression_call_result_vreg.rs @@ -38,7 +38,9 @@ fn compile_optimized(wasm_ops: &[WasmOp], num_params: usize) -> Vec { let (ir, _cfg, _stats) = bridge .optimize_full(wasm_ops) .expect("optimize_full should succeed for valid input"); - bridge.ir_to_arm(&ir, num_params) + bridge + .ir_to_arm(&ir, num_params) + .expect("ir_to_arm should succeed for valid input") } /// The exact wasm-op sequence emitted by `wat2wasm` for the `fib` function diff --git a/crates/synth-synthesis/tests/regression_ir_to_arm_panic_free.rs b/crates/synth-synthesis/tests/regression_ir_to_arm_panic_free.rs new file mode 100644 index 0000000..8380d66 --- /dev/null +++ b/crates/synth-synthesis/tests/regression_ir_to_arm_panic_free.rs @@ -0,0 +1,143 @@ +//! Regression: `OptimizerBridge::ir_to_arm` must never panic. +//! +//! `ir_to_arm` hosts the `get_arm_reg` closure that maps an IR virtual +//! register to an ARM register. PR #101 made an unmapped vreg a hard +//! `panic!` ("synth internal compiler error: vreg vN has no assigned ARM +//! register ...") — correct at the time (a loud crash beats a silent +//! miscompilation), but it meant the optimized lowering path +//! (`optimize_full` -> `ir_to_arm`) could panic on malformed input. +//! +//! That panic was the last panic site in the optimized path and the reason +//! the `wasm_ops_lower_or_error` fuzz harness sat at `gating: false`. The +//! fix converts `get_arm_reg`'s `panic!` into `Err(Error::synthesis(...))` +//! with the same diagnostic text, and `ir_to_arm` now returns +//! `Result>`. The defensive check is preserved — a genuine +//! `wasm_to_ir` bug still surfaces as a rich `Err`, it just no longer kills +//! the process. +//! +//! Contract enforced here: feeding a malformed `Vec` through +//! `optimize_full` + `ir_to_arm` yields `Ok` or `Err`, never a panic. + +use synth_core::WasmOp; +use synth_synthesis::OptimizerBridge; + +/// Drive the full optimized pipeline. The `optimize_full` pre-flight +/// (`wasm_stack_check`) may reject the input as `Err`; if it accepts, +/// `ir_to_arm` runs and itself returns `Result`. Neither stage may panic. +fn lower_optimized(wasm_ops: &[WasmOp], num_params: usize) { + let bridge = OptimizerBridge::new(); + if let Ok((instructions, _cfg, _stats)) = bridge.optimize_full(wasm_ops) { + // The point of the test: this returns `Ok` or `Err`, never panics. + // `ir_to_arm` is the function that previously hosted the + // process-killing `get_arm_reg` panic. + match bridge.ir_to_arm(&instructions, num_params) { + Ok(_arm) => {} + Err(_e) => {} + } + } +} + +/// The motivating shape: `[I32Const, LocalGet, I32Const, I32Add, I32ShrS, +/// I32Const]`. A mixed arithmetic / shift sequence with a trailing constant +/// that historically could leave a vreg unmapped by the time `ir_to_arm` +/// runs. +#[test] +fn mixed_i32_arith_shift_sequence_does_not_panic() { + let wasm_ops = vec![ + WasmOp::I32Const(7), + WasmOp::LocalGet(0), + WasmOp::I32Const(3), + WasmOp::I32Add, + WasmOp::I32ShrS, + WasmOp::I32Const(1), + ]; + lower_optimized(&wasm_ops, 4); +} + +/// Type-mismatched i64 sequence: an i64 op consuming i32-shaped stack +/// slots. The kind of shape that, pre-fix, could produce IR referencing a +/// vreg `wasm_to_ir` never mapped, tripping `get_arm_reg`'s panic. +#[test] +fn type_mismatched_i64_sequence_does_not_panic() { + let wasm_ops = vec![ + WasmOp::I32Const(1), + WasmOp::I32Const(2), + WasmOp::I64Add, + WasmOp::I64ShrU, + ]; + lower_optimized(&wasm_ops, 4); +} + +/// An i64 extend feeding an i64 shift — issue-#93's exact class. With the +/// extend handled in `wasm_to_ir` this lowers cleanly, but the harness +/// contract holds regardless: `Ok` or `Err`, never a panic. +#[test] +fn i64_extend_into_shift_does_not_panic() { + let wasm_ops = vec![ + WasmOp::LocalGet(0), + WasmOp::I64ExtendI32U, + WasmOp::I32Const(32), + WasmOp::I64ShrU, + ]; + lower_optimized(&wasm_ops, 4); +} + +/// Confirms the new `Result` signature on a normal, well-formed program: +/// `ir_to_arm` returns `Ok` with a non-empty instruction stream. This is +/// the happy-path guard — if the `Result` conversion broke ordinary +/// lowering, this fails. +#[test] +fn well_formed_program_lowers_to_ok() { + let wasm_ops = vec![WasmOp::LocalGet(0), WasmOp::LocalGet(1), WasmOp::I32Add]; + + let bridge = OptimizerBridge::new(); + let (instructions, _cfg, _stats) = bridge + .optimize_full(&wasm_ops) + .expect("well-formed program optimizes"); + let arm = bridge + .ir_to_arm(&instructions, 2) + .expect("well-formed program lowers to ARM"); + assert!( + !arm.is_empty(), + "a non-trivial program should emit at least one ARM op" + ); +} + +/// Exercise `ir_to_arm` directly with a hand-built IR instruction whose +/// source vreg has no producer — the exact unmapped-vreg condition the +/// `get_arm_reg` check guards. Pre-fix this panicked; post-fix it must be +/// a typed `Err` carrying the issue-#93 diagnostic. +#[test] +fn unmapped_vreg_yields_err_not_panic() { + use synth_opt::{Instruction, Opcode, Reg as OptReg}; + + let bridge = OptimizerBridge::new(); + // A single i32 add whose operands (v100, v101) were never produced by + // any prior instruction — so `vreg_to_arm` has no mapping and there is + // no spill slot. This is precisely the state a `wasm_to_ir` gap leaves + // behind, and the state `get_arm_reg`'s defensive check exists to + // catch. + let instrs = vec![Instruction { + id: 0, + opcode: Opcode::Add { + dest: OptReg(0), + src1: OptReg(100), + src2: OptReg(101), + }, + block_id: 0, + is_dead: false, + }]; + + // The contract: a typed `Err`, never a panic. v100 / v101 have no + // producer and no spill slot, so `get_arm_reg` must take its defensive + // branch — pre-fix that was a `panic!`, post-fix a recoverable `Err`. + let result = bridge.ir_to_arm(&instrs, 0); + let err = result.expect_err("unmapped src vregs must yield Err, not Ok or panic"); + let msg = err.to_string(); + // The diagnostic must survive the panic -> Err conversion: it is still + // the issue-#93-class bug-finder, just no longer process-killing. + assert!( + msg.contains("no assigned") && msg.contains("issue #93"), + "unmapped-vreg error lost its diagnostic content: {msg}" + ); +} diff --git a/crates/synth-synthesis/tests/regression_issue_120_f32_optimized.rs b/crates/synth-synthesis/tests/regression_issue_120_f32_optimized.rs index 27ec62d..734c27e 100644 --- a/crates/synth-synthesis/tests/regression_issue_120_f32_optimized.rs +++ b/crates/synth-synthesis/tests/regression_issue_120_f32_optimized.rs @@ -41,9 +41,13 @@ fn try_optimized(wasm_ops: &[WasmOp], num_params: usize) -> Result { // ir_to_arm is the function that hosts the defensive get_arm_reg - // panic. Exercising it here proves no unmapped vreg is reached. - let arm = bridge.ir_to_arm(&ir, num_params); - Ok(arm.len()) + // check. It now returns `Result` instead of panicking — an `Err` + // is a clean decline, not a crash. Exercising it here proves no + // unmapped vreg ever reaches a process-killing panic. + bridge + .ir_to_arm(&ir, num_params) + .map(|arm| arm.len()) + .map_err(|e| e.to_string()) } Err(e) => Err(e.to_string()), } diff --git a/fuzz/fuzz_targets/wasm_ops_lower_or_error.rs b/fuzz/fuzz_targets/wasm_ops_lower_or_error.rs index 21803ba..82e7aae 100644 --- a/fuzz/fuzz_targets/wasm_ops_lower_or_error.rs +++ b/fuzz/fuzz_targets/wasm_ops_lower_or_error.rs @@ -40,8 +40,12 @@ fuzz_target!(|input: FuzzInput| { // ----------------------------------------------------------------- let bridge = OptimizerBridge::new(); if let Ok((instructions, _cfg, _stats)) = bridge.optimize_full(&wasm_ops) { - let arm_ops = bridge.ir_to_arm(&instructions, input.num_params.min(4) as usize); - encode_each_or_typed_error(&arm_ops); + // `ir_to_arm` returns `Result`: `Ok` on success, `Err` for the + // issue-#93-class unmapped-vreg condition. Either is contract- + // compliant — only a panic is a crash. + if let Ok(arm_ops) = bridge.ir_to_arm(&instructions, input.num_params.min(4) as usize) { + encode_each_or_typed_error(&arm_ops); + } } // -----------------------------------------------------------------