Skip to content

thumb-2: i64.div_s(INT64_MIN, -1) silently returns INT64_MIN instead of trapping (overflow guard missing on i64 path; i32 path has it) #633

Description

@avrabe

Summary

On the ARM/thumb-2 backend (-t cortex-m3), i64.div_s(INT64_MIN, -1) silently returns a wrong value instead of trapping. Per WebAssembly Core §4.3.2 (idiv_s), the result of -2^63 / -1 is +2^63, which is not representable in i64, so the operation must trap (integer overflow). wasmtime traps; synth computes 0x8000000000000000 (= INT64_MIN) and returns it with no trap.

This is a silent overflow miscompile. The i32 path already emits the overflow guard; the i64 path does not — an asymmetry.

  • synth 0.31.0 (v0.31.0-1-g28296d3)
  • target cortex-m3 (thumb-2), --relocatable
  • oracle: wasmtime 42.0.1

Reproducer

(module
  (func (export "f") (param i32 i32) (result i32)
    (i32.wrap_i64
      (i64.div_s
        (i64.or (i64.shl (i64.extend_i32_u (local.get 1)) (i64.const 32))
                (i64.extend_i32_u (local.get 0)))
        (i64.const -1)))))

Dividend assembled from local 0 (low) / local 1 (high); divisor is the constant -1.

dividend divisor WASM spec wasmtime synth (qemu)
INT64_MIN (lo=0, hi=0x80000000) -1 trap (overflow) wasm trap returns 0 (low word of 0x8000000000000000, no trap)
100 (lo=100, hi=0) -1 -100 -100 -100 ✓ (normal path OK)

Normal signed 64-bit division is correct; only the INT64_MIN / -1 overflow case is mishandled.

Root cause

The i64 signed-division helper emits only the divide-by-zero trap and no INT64_MIN / -1 overflow guard. It negates the dividend (INT64_MIN negates to itself — the wrap), negates the divisor (-11), divides, and returns 0x8000000000000000. The corresponding i32 path does emit the overflow guard, so this is a missing-guard asymmetry between the i32 and i64 signed-division lowerings.

Fix-guard twin (please keep passing after the fix)

i64.rem_s(INT64_MIN, -1) must return 0 and must not trap (WASM Core §4.3.2 irem_s). synth currently returns 0 correctly — a fix for the div_s overflow trap must not over-eagerly make rem_s trap. (Verified: synth rem_s(INT64_MIN,-1)=0 today, matches wasmtime.)

Cross-reference

RV32 had related signed-division guard bugs (#232 i32 overflow-guard clobber, #317 i64 sign clobber) — both closed. This is the ARM/thumb-2 i64 backend missing the overflow guard entirely.

Found via QEMU Cortex-M semihosting execution-differential vs wasmtime; source location narrowed by a spec-mining pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions