Skip to content

thumb-2: i64 global.set/global.get truncate to 32 bits (high word silently dropped) #643

Description

@avrabe

Summary

On the Thumb-2 backend (-t cortex-m3), global.set / global.get of an i64 global truncate to the low 32 bits: global.set stores only the low word and discards the high word; global.get loads only one word. The high 32 bits of every i64 global are silently lost. It compiles with no error or warning — a silent miscompile.

  • synth 0.32.1
  • oracle: wasmtime 42.0.1

Reproducer

(module
  (global $c (mut i64) (i64.const 0))
  (func (export "f")(param i32 i32)(result i32)
    (global.set $c (i64.const 0x123456789ABCDEF0))
    (i32.wrap_i64 (i64.shr_u (global.get $c) (i64.const 32)))))   ;; return the high word
  • wasmtime: f() = 305419896 (0x12345678, the correct high word)
  • synth (qemu, R9 = globals base): f() = 32 — wrong (the high word was never stored/loaded)

Root cause (Thumb-2 disassembly)

movw r0, #0xdef0 ; movt r0, #0x9abc   ; r0 = low  word 0x9ABCDEF0
movw r1, #0x5678 ; movt r1, #0x1234   ; r1 = high word 0x12345678  (computed…)
str.w r0, [r9]                        ; global.set: stores ONLY r0 (low). r1 (high) DISCARDED.
ldr.w r2, [r9]                        ; global.get: loads ONLY one word.
...                                   ; the i64.shr_u then operates on a malformed pair -> 32

Exactly one str and one ldr to [r9] (the globals base) — an i64 global needs two of each ([r9] and [r9,#4]). The high-word register r1 is materialized and then dropped. Source confirms the i32-only lowering (optimizer_bridge.rs:2382): "GlobalGet pushes a fresh i32; GlobalSet pops one." There is no i64 register-pair path for globals, and the idx*4 slot addressing has no room for the second word.

Impact

Any module with a mutable or immutable i64 global (counters, 64-bit accumulators, timestamps, bitsets, packed handles) reads back a corrupted value after the first global.set, with no diagnostic. i32 globals are correct; only i64 (and by extension f64, same pair width — worth checking) globals are affected.

Suggested fix

Lower i64 global.get/global.set as a register-pair access (str/ldr of both words at [r9,#off] and [r9,#off+4], with 8-byte slot layout), or — if i64 globals are intentionally unsupported — reject them with an honest error (the #369/#503/#518 "unsupported operator" precedent) rather than silently truncating.

Found via the QEMU full-image execution-differential harness (R9 = globals base) vs wasmtime; source + disasm confirmed.

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