fix(opt): wasm_to_ir missing result vreg for WasmOp::Call#109
Merged
Conversation
Closes the last latent unmapped-vreg gap surfaced by the systematic AAPCS audit (PR #108) when compiling examples/wat/simple_add.wat — the recursive fib function caused `synth compile --cortex-m` to panic on vreg v13 (the second `Call` result, consumed by the outer `i32.add`). Root cause: `optimizer_bridge::wasm_to_ir`'s match on `WasmOp` had no arm for `WasmOp::Call(idx)`, so it fell through to `Opcode::Nop`. The IR therefore had no record that `inst_id N` defined a vreg; any downstream consumer (here, the outer `i32.add`) resolved its `src` via `get_arm_reg` -> unmapped -> defensive panic (PR #101) or, under the silent R0 fallback that's in main today, silently consumed whatever value happened to be in R0. Fix: added `Opcode::Call { dest, func_idx }` to `synth-opt`, mapped `WasmOp::Call -> Opcode::Call` in `wasm_to_ir`, and lowered it in `ir_to_arm` to `BL func_<idx>` with `dest -> R0` registered in `vreg_to_arm` (AAPCS: R0 holds the return value). Scope note: this is the narrow "compile cleanly, don't panic" fix. The IR still does NOT model call-clobber-R0..R3 invalidation of surviving vregs across the BL, nor does it pop arg slots from the value-tracking stack — both are deferred to a broader call-boundary regalloc rework. Two regression tests: * `fib_compiles_through_optimized_path` — full fib WAT through optimize_full + ir_to_arm; asserts two `BL func_1` are emitted plus the outer `i32.add` lowering. * `call_emits_bl_in_optimized_path` — minimal standalone `WasmOp::Call` -> `BL func_5` round-trip. A third end-to-end test through `ArmBackend` was removed during review because synth-synthesis sits below synth-backend in the dep graph (would close a cycle). The existing synth-cli/tests/wast_compile.rs test suite already exercises the full pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
May 14, 2026
…ack (#101) With PR #109 (WasmOp::Call wasm_to_ir handler) merged, every known wasm_to_ir gap is closed. The silent R0 fallback in `get_arm_reg` is now safe to replace with a diagnostic panic — future 'wasm op silently dropped' bugs in the #93 class will surface at the boundary instead of producing miscompiled firmware. A compiler crash with diagnostic context is strictly better than a hung MCU.
avrabe
added a commit
that referenced
this pull request
May 14, 2026
Two harnesses (i64_lowering_doesnt_clobber_params, encoder_no_panic) keep finding new AAPCS-class bugs at a rate faster than we close them in a single release cycle. Mark them as non-gating (continue-on-error) so the harness infrastructure can ship as part of #100 while the bug-hunting continues in v0.3.x patches. Promotion criteria for moving a harness from exploration → gating: 60s smoke runs report zero findings on main for 2 consecutive weeks. Open bugs tracked: #103 (fixed via #106), #104 (fixed via #107), the class-wide sweep #108, the WasmOp::Call gap #109/#101, the I32WrapI64 preassign #111, and the new #112 (i64-extend chain + Movw R0).
avrabe
added a commit
that referenced
this pull request
May 15, 2026
Closes #82. Adds a workspace-excluded `fuzz/` crate with 4 harnesses + CI smoke gate: * `wasm_ops_lower_or_error` — fuzz arbitrary Vec<WasmOp> through both optimized and no-optimize paths; assert no panic / no unencodable instruction. (gating) * `wasm_to_ir_roundtrip_op_coverage` — value-producing wasm ops must produce live IR (catches the #93-class silent-drop bug). (gating) * `i64_lowering_doesnt_clobber_params` — random i64/i32-param mixes; assert no ARM instr writes R{p} before LocalGet(p), with LocalSet(p)/LocalTee(p) carved out as wasm-intended. (exploration) * `encoder_no_panic` — random ArmOp values; assert ArmEncoder doesn't panic. (exploration) CI matrix splits gating from exploration: gating harnesses block merge, exploration harnesses upload crash artifacts but use `continue-on-error: true` until their bug-lists stabilise (promotion criterion documented in fuzz-smoke.yml). This harness suite caught the bugs subsequently fixed in #103/#104/#108/#109/#111 and surfaced #112 for v0.3.x follow-up. The infrastructure value is real even with exploration harnesses non-gating today.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last latent unmapped-vreg gap surfaced by the systematic AAPCS audit (PR #108) when compiling
examples/wat/simple_add.wat— the recursive fib function causedsynth compile --cortex-mto panic on vreg v13 (the secondCallresult, consumed by the outeri32.add).Root cause
optimizer_bridge::wasm_to_ir's match onWasmOphad no arm forWasmOp::Call(idx), so it fell through toOpcode::Nop. The IR therefore had no record thatinst_id Ndefined a vreg; any downstream consumer (here, the outeri32.add) resolved itssrcviaget_arm_reg→ unmapped → defensive panic (PR #101) or, under the silent R0 fallback that's in main today, silently consumed whatever value happened to be in R0.Fix
Opcode::Call { dest, func_idx }insynth-optWasmOp::Call → Opcode::Callinwasm_to_irOpcode::Call → ArmOp::Bl { label: "func_<idx>" }inir_to_arm, withdest → R0registered invreg_to_arm(AAPCS: R0 holds the return value)Scope (narrow)
This is the "compile cleanly, don't panic on lawful WASM containing
call" fix. The IR still does NOT:Both are deferred to a broader call-boundary regalloc rework.
Tests
fib_compiles_through_optimized_path— full fib WAT throughoptimize_full + ir_to_arm; asserts twoBL func_1are emitted plus the outeri32.addloweringcall_emits_bl_in_optimized_path— minimal standaloneWasmOp::Call → BL func_5round-tripA third end-to-end test through
ArmBackendwas removed during local review because synth-synthesis sits below synth-backend in the dep graph (would close a cycle). The existingsynth-cli/tests/wast_compile.rstest suite already exercises the full pipeline.Verification
cargo test --workspace— all passcargo clippy --workspace --all-targets -- -D warnings— cleancargo fmt --check— cleansynth compile examples/wat/simple_add.wat --cortex-m -o /tmp/sa.elf— succeeds (no panic)Unblocks
PR #101 (defensive panic) — once this lands, the defensive panic can ship as a permanent guard against the wasm_to_ir-gap class.
🤖 Generated with Claude Code