Files
wasm2go/testdata/cg_atomics_m64.wat
0d0e628b3a Compose wasi-threads with memory64: addr64 atomics, shared-mem64 modules (#49)
The threads support and the memory64 support each worked alone but
were kept apart by two gates: shared memory64 modules were rejected
at translation, and every 0xFE atomic was rejected on a memory64
memory. A wasm64-wasip1-threads toolchain now exists, so compose
them.

- atomicEA64 mirrors simdEA64's overflow-guarded address math; every
  atomic op body moves into an ea-keyed core, the wasm32 helpers
  become one-line wrappers through atomicEA, and each gets an _m64
  twin through atomicEA64 (loads/stores/RMW families, wait/notify).
  Contended fast paths and memory orders are moved, not rewritten.
- The 0xFE lowering decodes the memarg offset as u64 under memory64
  (the one place still reading u32), passes it as Const64, and
  selects the _m64 helper names; the inline atomic fast path becomes
  width-correct instead of truncating.
- wasi_thread_spawn under memory64 passes the start_arg as i64 (it is
  a linear-memory pointer): threadSpawn splits into a shared launch
  core with per-width entries, and the module wires a threadStart64
  field for the widened wasi_thread_start export.
- memoryGrow64 gains the no-relocation branch for shared memories a
  grow must never move the backing array out from under peer agents.
  A shared memory64 requires a declared maximum: the reservation is
  the mapping ceiling, and the memory64 hard cap is not a ceiling.
- WasiStubs gains the trivial Sched_yield64 LP64 twin (musl's spin
  paths import sched_yield; single-threaded modules never did).
- New shared+i64 fixtures mirror the wasm32 threads fixtures and run
  under the race detector; an atomicEA64 unit test covers beyond-4GiB
  addresses, overflow and misalignment; a regression test pins that
  unshared memory64 modules may use atomics. Generated output for
  wasm32 modules is byte-identical.

Verified end to end: a wasm64-wasip1-threads C program
(pthread_create x4 over a shared counter) transpiles and runs
correctly on goroutines, including under -race; llama.cpp built for
wasm64-wasip1-threads decodes correctly with ggml n_threads
parallelism.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-08-22 00:31:05 +09:00

62 lines
3.2 KiB
WebAssembly Text Format

;; cg_atomics_m64.wat — cg_atomics.wat on a shared memory64: the same
;; single-agent threads-proposal atomics (loads, stores, every RMW family
;; incl. subword lanes, cmpxchg, fence, notify, zero-timeout wait) with i64
;; addressing, so every op routes through the _m64 helper family. Results are
;; deterministic observable values the driving test hardcodes.
(module
(memory i64 1 1 shared)
(func (export "rmw32") (result i32)
(i32.atomic.store (i64.const 16) (i32.const 100))
(drop (i32.atomic.rmw.add (i64.const 16) (i32.const 5)))
(drop (i32.atomic.rmw.sub (i64.const 16) (i32.const 1)))
(drop (i32.atomic.rmw.and (i64.const 16) (i32.const 0xff)))
(drop (i32.atomic.rmw.or (i64.const 16) (i32.const 0x100)))
(drop (i32.atomic.rmw.xor (i64.const 16) (i32.const 0x1)))
(i32.atomic.load (i64.const 16)))
(func (export "rmw64") (result i64)
(i64.atomic.store (i64.const 24) (i64.const 4000000000))
(drop (i64.atomic.rmw.add (i64.const 24) (i64.const 4000000000)))
(i64.atomic.load (i64.const 24)))
(func (export "xchg") (result i32)
(i32.atomic.store (i64.const 32) (i32.const 7))
;; old value (7) + new value (9) observed
(i32.add (i32.atomic.rmw.xchg (i64.const 32) (i32.const 9))
(i32.atomic.load (i64.const 32))))
(func (export "cmpxchg") (result i32)
(i32.atomic.store (i64.const 40) (i32.const 5))
(drop (i32.atomic.rmw.cmpxchg (i64.const 40) (i32.const 5) (i32.const 6))) ;; hits
(drop (i32.atomic.rmw.cmpxchg (i64.const 40) (i32.const 5) (i32.const 7))) ;; misses
(i32.atomic.load (i64.const 40)))
(func (export "subword") (result i32)
;; byte lanes at 48..51: store 0xAA at 49, add 1 at lane, read back word
(i32.atomic.store (i64.const 48) (i32.const 0))
(i32.atomic.store8 (i64.const 49) (i32.const 0xaa))
(drop (i32.atomic.rmw8.add_u (i64.const 49) (i32.const 1)))
(drop (i32.atomic.rmw16.xchg_u (i64.const 50) (i32.const 0x1234)))
(i32.atomic.load (i64.const 48)))
(func (export "subword_cmpxchg") (result i32)
(i32.atomic.store8 (i64.const 56) (i32.const 3))
(drop (i32.atomic.rmw8.cmpxchg_u (i64.const 56) (i32.const 3) (i32.const 9)))
(i32.atomic.load8_u (i64.const 56)))
(func (export "wait_notify") (result i32)
(i32.atomic.store (i64.const 64) (i32.const 1))
;; not-equal (expected 0, actual 1) => 1; notify returns 0 woken
(i32.add
(memory.atomic.wait32 (i64.const 64) (i32.const 0) (i64.const 0))
(memory.atomic.notify (i64.const 64) (i32.const 5))))
(func (export "fenced_load") (result i32)
(i32.atomic.store (i64.const 72) (i32.const 42))
(atomic.fence)
(i32.atomic.load (i64.const 72)))
(func (export "store_neg") (result i32)
;; Negative constant operands: the inline-atomic emitter renders the
;; store value through an unsigned cast, which must be a runtime
;; conversion — `uint32(-1)` as a constant expression fails to
;; compile. Locks the force-hoist of atomic-store value operands.
(i32.atomic.store (i64.const 80) (i32.const -1))
(i64.atomic.store (i64.const 88) (i64.const -0x8000000000000000))
(i32.add
(i32.atomic.load (i64.const 80))
(i32.wrap_i64 (i64.atomic.load (i64.const 88)))))
)