Skip to content

[Potential Bug]: Wizard x86-64 SPC: i8x16.ne (and i16x8/i32x4/i64x2) corrupts a live local #650

Description

@khagankhan

Summary

On the x86-64 single-pass compiler tiers (lazy, jit, spc), a vector
*.ne instruction whose operand is a register-cached local overwrites that
local
with all-ones. Later reads of the local return -1 instead of its real
value. The interpreter tiers (int, dyn) are correct, so the tiers disagree.

Reproducer (ne_clobber_repro.wat)

(module
  (func (export "main") (result i32)
    (local $v v128)
    v128.const i8x16 0 0 0 0 0 0x9c 0 0 0 0 0 0 0 0 0 0  ;; lane 5 = 0x9c (-100)
    local.set $v
    local.get $v  local.get $v  i8x16.ne  drop            ;; result discarded
    local.get $v  i8x16.extract_lane_s 5))                ;; return local back
wizeng --print-result --mode=int  ne_clobber_repro.wasm   ->  -100   (correct)
wizeng --print-result --mode=spc  ne_clobber_repro.wasm   ->  -1     (wrong)

The i8x16.ne result is discarded; it only acts as a side effect. The bug is
that it mutates $v, which main then returns directly.

Root cause

emit_v128_ne in src/engine/x86-64/X86_64MacroAssembler.v3 builds the all-ones
XOR mask for the complement-of-equal in the src operand register itself:

f(dst, src)        ; dst = (dst == src)        pcmpeqb
f(src, src)        ; src = all-ones            <-- clobbers src
pxor dst, src      ; dst = (dst != src)

In the SPC, visit_I8X16_NE uses do_op2_x_x, which allocates no scratch and
passes the local's cached register as src. Overwriting it corrupts the local.
The interpreter is unaffected because it loads operands into throwaway scratch
xmms. Affects i8x16/i16x8/i32x4/i64x2.ne.

Fix

Build the all-ones mask in a dedicated scratch register instead of src
(the same pattern gt_u already uses):

  • X86_64MacroAssembler.v3: emit_v128_ne takes a scratch param; use
    f(scratch, scratch) for the mask.
  • X86_64SinglePassCompiler.v3: the four visit_*_NE use do_op2_x_x_xtmp.
  • X86_64Interpreter.v3: pass the existing scratch r_xmm2 to the four
    emit_*_ne calls.

After the fix all five tiers agree (-100).

Additional information

A combination of AFL++ and Wasmlike, an Xsmith-based random program generator produced the snippet of code that found the issue. Xsmith Project

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions