Summary
Loom's constant-folding pass folds i32.div_s(INT_MIN, -1) (and the i64 equivalent) to the constant INT_MIN, eliminating a mandatory integer-overflow trap. The optimized module returns a value where the original module traps — a silent, observable miscompile that changes program semantics.
Found on loom 1.1.18, cross-checked against wasmtime 42.0.1.
Repro
i32:
(module (func (export "f") (result i32)
(i32.div_s (i32.const -2147483648) (i32.const -1))))
loom optimize f.wat --wat -o f.opt.wat
Optimized body becomes i32.const -2147483648.
|
wasmtime 42 |
| original |
wasm trap: integer overflow |
| loom-optimized |
returns -2147483648 |
i64 (i64.div_s (i64.const -9223372036854775808) (i64.const -1)): original traps; optimized returns -9223372036854775808. Same divergence.
Spec
WebAssembly Core Spec, idiv_s(N, i1, i2): the operation traps when i2 = 0 or when the exact result 2^(N-1) is not representable — i.e. INT_MIN / -1. So -2^31 / -1 and -2^63 / -1 MUST trap; they have no defined value. An optimizer replacing a trapping op with a constant is unsound because a program may rely on the trap (e.g. INT_MIN/-1 guarding a subsequent operation).
Root cause
loom-shared/src/lib.rs:
- i32 —
ValueData::I32DivS arm, lines ~4704-4716:
(ValueData::I32Const { val: l }, ValueData::I32Const { val: r })
if r.value() != 0 =>
{
// Signed division with wrapping for overflow case (INT_MIN / -1)
iconst32(Imm32(l.value().wrapping_div(r.value())))
}
- i64 —
ValueData::I64DivS arm, lines ~4776-4787 (same shape with Imm64).
The guard only excludes r == 0 (div-by-zero trap correctly preserved — verified: div_s(10, 0) is NOT folded). It then uses wrapping_div, which yields INT_MIN for the INT_MIN / -1 overflow case instead of preserving the trap. The comment documents the wrong intent.
Fix
Also decline to fold (fall through to idivs32/idivs64, preserving the runtime trap) when l == iN::MIN && r == -1:
(ValueData::I32Const { val: l }, ValueData::I32Const { val: r })
if r.value() != 0 && !(l.value() == i32::MIN && r.value() == -1) =>
{
iconst32(Imm32(l.value() / r.value()))
}
Scope check (these are correct, not affected)
div_s/div_u/rem_s/rem_u by 0 → not folded, trap preserved. ✓
rem_s(INT_MIN, -1) → correctly folds to 0 (spec: no trap, result 0). ✓
i32.add/mul wrap, rotr by 32 → folded correctly. ✓
Environment: loom 1.1.18 (--no-default-features build), wasmtime 42.0.1.
Summary
Loom's constant-folding pass folds
i32.div_s(INT_MIN, -1)(and the i64 equivalent) to the constantINT_MIN, eliminating a mandatory integer-overflow trap. The optimized module returns a value where the original module traps — a silent, observable miscompile that changes program semantics.Found on
loom 1.1.18, cross-checked against wasmtime 42.0.1.Repro
i32:
Optimized body becomes
i32.const -2147483648.wasm trap: integer overflow-2147483648i64
(i64.div_s (i64.const -9223372036854775808) (i64.const -1)): original traps; optimized returns-9223372036854775808. Same divergence.Spec
WebAssembly Core Spec,
idiv_s(N, i1, i2): the operation traps wheni2 = 0or when the exact result2^(N-1)is not representable — i.e.INT_MIN / -1. So-2^31 / -1and-2^63 / -1MUST trap; they have no defined value. An optimizer replacing a trapping op with a constant is unsound because a program may rely on the trap (e.g.INT_MIN/-1guarding a subsequent operation).Root cause
loom-shared/src/lib.rs:ValueData::I32DivSarm, lines ~4704-4716:ValueData::I64DivSarm, lines ~4776-4787 (same shape withImm64).The guard only excludes
r == 0(div-by-zero trap correctly preserved — verified:div_s(10, 0)is NOT folded). It then useswrapping_div, which yieldsINT_MINfor theINT_MIN / -1overflow case instead of preserving the trap. The comment documents the wrong intent.Fix
Also decline to fold (fall through to
idivs32/idivs64, preserving the runtime trap) whenl == iN::MIN && r == -1:Scope check (these are correct, not affected)
div_s/div_u/rem_s/rem_u by 0→ not folded, trap preserved. ✓rem_s(INT_MIN, -1)→ correctly folds to0(spec: no trap, result 0). ✓i32.add/mulwrap,rotrby 32 → folded correctly. ✓Environment: loom 1.1.18 (
--no-default-featuresbuild), wasmtime 42.0.1.