Skip to content

Dead-code elimination removes dead-result i32/i64 div/rem, eliminating the divide-by-zero & overflow traps (runtime-variable divisor; distinct from #273) #274

Description

@avrabe

Summary

loom optimize (default pipeline) removes dead-result integer division/remainder operations, eliminating their mandatory divide-by-zero and INT_MIN/-1 overflow traps. When the result of an i32/i64 div_s/div_u/rem_s/rem_u is unused, the whole operation is deleted as dead code — so a module that must trap at runtime instead returns normally.

This is a trap-preservation / unsound-DCE bug. It is related to #273 but distinct and broader:

Same underlying gap as #273: value-equivalence verification does not model traps, so removing a trapping-but-dead op is accepted.

Repro

repro.wat:

(module
  (func (export "f") (param $d i32) (result i32)
    (local $t i32)
    (local.set $t (i32.div_s (i32.const 100) (local.get $d)))
    (i32.const 7)))
$ wasm-tools parse repro.wat -o repro.wasm
$ wasmtime run --invoke f repro.wasm 0
Error: wasm trap: integer divide by zero        # correct: div by zero MUST trap

$ loom optimize repro.wasm -o repro.opt.wasm
$ wasmtime run --invoke f repro.opt.wasm 0
7                                               # WRONG: trap eliminated, returns normally

Optimized function body is reduced to i32.const 7 — the div_s is gone.

loom --version = 1.1.18. --verify reports ✓ Verification: 37/37 tests passed on the miscompiled output.

Scope (all confirmed, original traps / optimized returns 7)

op (result dead) divisor orig loom-optimized
i32.div_s(100, $d) runtime $d=0 trap 7
i32.div_s(1, 0) const trap 7
i32.div_u(1, 0) const trap 7
i32.rem_s(1, 0) const trap 7
i32.rem_u(1, 0) const trap 7
i32.div_s(INT_MIN, -1) const trap 7

Correctly preserved (control): dead-result i32.load at OOB address, dead-result i32.trunc_f32_s(nan), and an explicit (drop (i32.div_s 1 0)) all still trap. So the bug is specific to div/rem reached through a dead local.set in the default orchestration.

Mechanism

The removal happens in the default loom optimize orchestration (the ISLE term round-trip instructions_to_terms → simplify → terms_to_instructions, combined with eliminate_dead_locals at loom-core/src/lib.rs:11849 whose neutralize_dead_writes turns a dead LocalSet into Drop). Notably:

Expected

Per the WebAssembly spec, idiv_s/idiv_u/irem_s/irem_u trap when the divisor is 0, and idiv_s traps on INT_MIN / -1 overflow. These traps are observable side effects; an operation that may trap must not be removed just because its produced value is unused. Either treat integer div/rem as side-effecting for DCE/dead-local purposes, or keep the trap (e.g. lower a dead div to its trap-check guard only).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions