You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(trap-semantics): unreachable traps on every backend (#665); rv32 rem_s drops the spurious INT_MIN/-1 guard (#666) (#668)
#665 — wasm `unreachable` compiled to a NO-OP on thumb-2 AND rv32 (falls
through instead of trapping, WASM Core §4.4.5). Root cause was ONE decode
drop: `convert_operator` returned `None` for `Unreachable` and
`is_intentionally_ignored` whitelisted it alongside `Nop`, so no backend
ever received the op — the selector trap arms (ARM `UDF #0`, RV32 `ebreak`)
already existed but were dead code. Fix per path:
- decoder (synth-core): `Unreachable` now decodes to `WasmOp::Unreachable`;
only `Nop` stays intentionally ignorable.
- ARM direct (`select_with_stack`) + `select_default`: existing `UDF #0`
arms now fire (no change needed).
- ARM optimized path (optimizer_bridge): previously lumped `Unreachable`
in with Nop as an IR placeholder — now a typed loud-DECLINE to the direct
selector (the bridge `Opcode` enum has no trap opcode; adding one would
ripple through the #513 mirror-pinned reg_effect/rewrite_op machinery).
Same decline-don't-drop pattern as #120 floats / #500 non-tail return.
No new ArmOp on the optimized path, so the #511 estimator oracle is
untouched (Udf was already covered by the div-zero guards).
- RV32: existing `ebreak` arm now fires (no change needed).
- aarch64: new `brk #0` encoder + selector arm (was a loud-decline).
#666 — rv32 `i32.rem_s(INT_MIN,-1)` spuriously trapped: the selector shared
div_s's INT_MIN/-1 overflow `ebreak` guard with rem_s via
`bin_with_signed_div_traps`. WASM §4.3.2 defines irem_s(INT_MIN,-1) = 0
with NO trap, and RISC-V M-ext `rem` already returns 0 for the overflow
case (unprivileged spec §7.2), so rem_s now takes plain
`bin_with_zero_trap` — zero-divisor guard KEPT, bare `rem` is exactly
wasm-correct. The pre-existing test `rv32_signed_rem_also_gets_overflow_guard`
pinned the BUG; it is rewritten as the #633-twin fix-guard pins
(`rv32_signed_rem_carries_only_zero_guard_666` +
`rv32_signed_div_still_carries_both_guards_666`), mirroring ARM's
`test_633_i64_rems_has_no_overflow_guard` and the existing i64 RV32 pin.
Oracles (red on origin/main, green here; CI job trap-semantics-oracle):
- scripts/repro/unreachable_665_differential.py — thumb2 + rv32 under
unicorn vs wasmtime: bare `unreachable` traps, guarded `unreachable`
taken traps, NOT taken returns normally (non-vacuity). Red on main:
boom(7,9) "returned" 7 (arg fall-through) on both ISAs. Known gap kept
visible: rv32 loud-declines the if/else-result-with-unreachable shape
(#343 arity check) — contract-compliant (never falls through).
- scripts/repro/rem_s_666_differential.py — rv32 trap table:
rems(INT_MIN,-1)→0 no-trap (red on main: spurious ebreak),
rems(INT_MIN,1)→0, rems(7,3)→1, rems(-7,3)→-1, rems(7,0) traps,
divs(INT_MIN,-1) traps, divs(7,0) traps, divs(7,3)→2.
Frozen anchors 10/10 bit-identical (no fixture contains `unreachable` —
verified control_step/flight_seam/flight_seam_flat/signed_div_const).
Workspace tests 108/108 suites green; fmt + clippy -D warnings clean.
Fixes#665Fixes#666
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
0 commit comments