Skip to content

Potential Bug: i8x16.extract_lane_u / i16x8.extract_lane_u not zero-extended in the x86-64 interpreter #648

Description

@khagankhan

At long last, I have written CRC checksum for my favorite Wasm engine. (Feel free to assign me some of them if you are busy)

The x86-64 assembly interpreter (the int/dyn tiers) returns garbage in the
upper bits of an unsigned narrow lane extract. The result should be the lane
value zero-extended to i32, but the upper bits instead contain stale bytes of
the source v128.

The single-pass compiler (lazy/jit/spc) and the portable V3 interpreter are
correct, so this shows up as a tier disagreement in differential testing.

Minimal reproducer

(module (func (export "main") (result i32)
  v128.const i32x4 0x33221100 0x77665544 0xbbaa9988 0xffeeddcc
  i16x8.extract_lane_u 0))
$ wizeng --mode=int --print-result --invoke=main repro.wasm
857870592            # 0x33221100   ← wrong
$ wizeng --mode=spc --print-result --invoke=main repro.wasm
4352                 # 0x00001100   ← correct

Lane 0 of the vector is 0x1100, so i16x8.extract_lane_u 0 must yield
0x0000_1100. The interpreter leaves the upper 16 bits as the vector's halfword (0x3322), giving 0x3322_1100.

Per-tier return values (the reproducer above)

tier --mode result
int int 0x33221100
dyn dyn 0x33221100
lazy lazy 0x00001100
jit jit 0x00001100
spc spc 0x00001100

i8x16.extract_lane_u has the same defect, e.g. i8x16.extract_lane_u 0 on the
same vector returns 0x33221100 instead of 0x00000000. The signed
variants (extract_lane_s) and the full-width i32x4/i64x2/float extracts are
all correct.

The observed wrong value follows result = (v128_bits[16:31] << 16) | lane for
i16x8, and result = (v128_bits[8:31] << 8) | lane for i8x16 — i.e. the
correct lane is in the low bits but the high bits are never cleared.

Environment

  • OS: Ubuntu 22.04.5 LTS, Linux 5.15.0-177-generic, x86_64
  • Wizard: 26.2985, commit 719aab44 ("Stack switch event monitor (Stack switch event monitor #645)")
  • Virgil: Aeneas III-11.1939, commit 1cdec1861
  • Target: x86-64-linux

Explanation

In src/engine/x86-64/X86_64Interpreter.v3, genExtract_lane reads the lane into
a temporary register and stores it back into the value-stack slot (which still
holds the full 16-byte vector). The unsigned narrow cases were generated as:

genExtract_lane(Opcode.I8X16_EXTRACT_LANE_U, BpTypeCode.I32.code, 1, asm.movb_r_m, asm.movb_m_r, false);
genExtract_lane(Opcode.I16X8_EXTRACT_LANE_U, BpTypeCode.I32.code, 2, asm.movw_r_m, asm.movw_m_r, false);

Both the load (movb_r_m/movw_r_m) and the store (movb_m_r/movw_m_r) are
partial-width: an 8/16-bit move on x86-64 does not clear the rest of the
register, and the partial store writes only the low 1–2 bytes of the result slot.
So the upper bytes of the i32 result are never overwritten and retain the
original vector bytes.

The signed variants avoid this because they sign-extend the register
(movbsx/movwsx) and store the full 32 bits (movd_m_r); the full-width
extracts move the whole value, so neither path leaves stale bits.

Fix

Use a zero-extending load and a full-width store for the unsigned narrow
extracts, mirroring the signed path:

-		genExtract_lane(Opcode.I8X16_EXTRACT_LANE_U, BpTypeCode.I32.code, 1, asm.movb_r_m, asm.movb_m_r, false);
-		genExtract_lane(Opcode.I16X8_EXTRACT_LANE_U, BpTypeCode.I32.code, 2, asm.movw_r_m, asm.movw_m_r, false);
+		genExtract_lane(Opcode.I8X16_EXTRACT_LANE_U, BpTypeCode.I32.code, 1, asm.movbzx_r_m, asm.movd_m_r, false);
+		genExtract_lane(Opcode.I16X8_EXTRACT_LANE_U, BpTypeCode.I32.code, 2, asm.movwzx_r_m, asm.movd_m_r, false);

movbzx_r_m/movwzx_r_m zero-extend the lane into the full 32-bit register, and
movd_m_r writes all four bytes of the result slot.

With this change all five tiers agree on the reproducer (0x00001100), and a
sweep over every extract_lane variant (i8x16/i16x8 signed+unsigned,
i32x4, i64x2, f32x4, f64x2) matches between int and spc.

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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