Skip to content

fix(#278,#281): preserve traps in operand-discarding folds (zero-annihilation + constant-condition select)#280

Open
avrabe wants to merge 2 commits into
mainfrom
fix/278-annihilation-trap-preservation
Open

fix(#278,#281): preserve traps in operand-discarding folds (zero-annihilation + constant-condition select)#280
avrabe wants to merge 2 commits into
mainfrom
fix/278-annihilation-trap-preservation

Conversation

@avrabe

@avrabe avrabe commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #278. Fixes #281. Two trap-elimination miscompiles of the operand-discarding class.

#278x*0→0/0*x→0/x&0→0 discarded a trapping operand (OOB load, div). #281 — constant-condition select(const,a,b) dropped the non-taken arm (WASM select is EAGER — both arms evaluated). Both are gated on the discarded operand/arm being trap-free (is_no_trap_expr, reused not duplicated): trap-free operands still fold; may-trap operands keep the op so the trap is preserved.

Verified behaviorally via wasmtime (trap outcome matches original) on both arms for select, and OOB-load / div-by-zero for annihilation. #278 suite 9 green, #281 suite 5 green, full workspace green, clippy clean. (Note: the earlier test harness ran the fusion pipeline which never applies these folds — repointed to constant_folding so the tests actually exercise + prove the gates.)

Last of the per-identity trap stopgaps — the systemic cure is loom#279 (v1.2.1). Folds into v1.2.0.

avrabe and others added 2 commits July 11, 2026 15:52
Gate x*0/0*x/x&0 (and other operand-discarding identities) on the discarded
operand being trap-free (is_no_trap), so an OOB load / div / other trapping op
inside the annihilated operand is preserved instead of silently deleted.

Fixes: #278
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rded-arm trap-freedom

The #278 trap-free negative/positive suite drove its `optimize()` helper through
`optimize_fused_module`, the component-fusion pipeline (adapter devirt / dedup /
DCE) — which never runs the algebraic simplifier, so the zero-annihilation /
absorption / constant-condition-`select` folds it meant to exercise were NEVER
applied. That made `t278_trap_free_mul_by_zero_still_folds_to_zero` fail (the
trap-free `(local.get 0)*0` was left un-folded) while the trapping cases passed
only vacuously (nothing was optimized). Point the helper at `constant_folding`,
the pass that drives ISLE `rewrite_pure` over the term IR where the
`is_no_trap_expr`-gated folds live. Now:
- trap-free operand-discards (`(local.get 0)*0`, const*0, …) DO fold to the
  absorbing constant, and
- may-trap operands (OOB load, div-by-zero) still block the fold — the #278
  negative cases stay green (function-with-load is skipped / Z3-rejected, so the
  trapping op survives).

#281 (5th trap-drop): WASM `select` is eager — both value operands are evaluated
before the condition (Core §4.4.1) — so folding `select(const, a, b)` to the
taken arm silently deletes a trap in the DISCARDED arm. The constant-condition
select fold in `loom-shared` `rewrite_pure` is already gated on the discarded
arm being `is_no_trap_expr` (the same predicate as #278); add end-to-end
regression coverage in `issue_281_select_trap.rs`: the discarded OOB-load / div
arm is preserved (optimized module still traps in wasmtime) with the trapping op
on EITHER side, and a trap-free discarded arm STILL folds to the taken arm.

Fixes: #278
Fixes: #281
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@avrabe avrabe changed the title fix(#278): don't discard a trapping operand in zero-annihilation folds fix(#278,#281): preserve traps in operand-discarding folds (zero-annihilation + constant-condition select) Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant