Files
8017569e5d codegen: inline full-width atomic loads/stores as sync/atomic intrinsics (#33)
* codegen: inline full-width atomic loads/stores as sync/atomic intrinsics

i32/i64 atomic.load and atomic.store were emitted as base helper calls
three //go:noinline frames deep (AtomicLoad32 -> AtomicPtr32 ->
AtomicEA), where a wasm engine emits a single instruction. Interpreter
guests hit an atomic load on every dispatch (interrupt-flag checks), so
the chain shows up as tens of percent of runtime: profiling SpiderMonkey's
portable baseline interpreter transpiled by wasm2go put the helper chain
at ~34% flat of a fib(30) benchmark.

Emit the four full-width aligned forms inline instead:

    int32(atomic.LoadUint32((*uint32)(unsafe.Add(mBase, off))))
    atomic.StoreUint32((*uint32)(unsafe.Add(mBase, off)), uint32(vN))

sync/atomic Load/Store are compiler intrinsics with the seq-cst
ordering the threads proposal requires. Alignment and bounds checks are
dropped, mirroring the documented plain load/store rationale in
emit_memops.go. Sub-word forms, RMW families, cmpxchg, wait/notify and
fence keep the helper form.

Atomic stores never push a result (the lowering discards the dummy
scalar the helpers used to return), so ComputeHoist now leaves them in
statement position instead of hoisting 'vN = <store>' — which would not
compile once the store is a no-value intrinsic call.

Co-Authored-By: Claude Fable 5 <[email protected]>

* emit: force-hoist atomic-store value operands

The inline atomic store renders its value through an unsigned cast
(uint32(vN) / uint64(vN)). With a constant operand the cast is a Go
constant expression, and a negative constant fails to compile
('constant -1 overflows uint32') — the exact failure mode narrowing
stores already solve by force-hoisting their value argument. Apply the
same treatment to atomic stores, and lock it with a negative-constant
store case in the cg_atomics fixture. Found transpiling SpiderMonkey:
p1 held i32.atomic.store sites with -1 and i32.min constants.

Co-Authored-By: Claude Fable 5 <[email protected]>

* gcasm: import sync/atomic in decls files when fallback bodies use it

Fallback bodies now carry inline atomic.LoadUint32/StoreUint32 sites;
the decls_<arch>.go writer sniffed only math/math-bits references, so a
bundle whose pure-fallback functions touched atomics failed to compile
('undefined: atomic'). Detect atomic. references the same way math and
bits are detected. Found on the SpiderMonkey bundle, where the huge
interpreter functions route to pure fallback.

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-22 14:07:17 +09:00

61 lines
3.1 KiB
WebAssembly Text Format

;; cg_atomics.wat — threads-proposal atomics in a single agent: loads,
;; stores, every RMW family incl. subword lanes, cmpxchg, fence, notify,
;; and a zero-timeout wait. Results are observable values, so the fixture
;; harness can diff them against wazero (threads feature enabled).
(module
(memory 1 1 shared)
(func (export "rmw32") (result i32)
(i32.atomic.store (i32.const 16) (i32.const 100))
(drop (i32.atomic.rmw.add (i32.const 16) (i32.const 5)))
(drop (i32.atomic.rmw.sub (i32.const 16) (i32.const 1)))
(drop (i32.atomic.rmw.and (i32.const 16) (i32.const 0xff)))
(drop (i32.atomic.rmw.or (i32.const 16) (i32.const 0x100)))
(drop (i32.atomic.rmw.xor (i32.const 16) (i32.const 0x1)))
(i32.atomic.load (i32.const 16)))
(func (export "rmw64") (result i64)
(i64.atomic.store (i32.const 24) (i64.const 4000000000))
(drop (i64.atomic.rmw.add (i32.const 24) (i64.const 4000000000)))
(i64.atomic.load (i32.const 24)))
(func (export "xchg") (result i32)
(i32.atomic.store (i32.const 32) (i32.const 7))
;; old value (7) + new value (9) observed
(i32.add (i32.atomic.rmw.xchg (i32.const 32) (i32.const 9))
(i32.atomic.load (i32.const 32))))
(func (export "cmpxchg") (result i32)
(i32.atomic.store (i32.const 40) (i32.const 5))
(drop (i32.atomic.rmw.cmpxchg (i32.const 40) (i32.const 5) (i32.const 6))) ;; hits
(drop (i32.atomic.rmw.cmpxchg (i32.const 40) (i32.const 5) (i32.const 7))) ;; misses
(i32.atomic.load (i32.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 (i32.const 48) (i32.const 0))
(i32.atomic.store8 (i32.const 49) (i32.const 0xaa))
(drop (i32.atomic.rmw8.add_u (i32.const 49) (i32.const 1)))
(drop (i32.atomic.rmw16.xchg_u (i32.const 50) (i32.const 0x1234)))
(i32.atomic.load (i32.const 48)))
(func (export "subword_cmpxchg") (result i32)
(i32.atomic.store8 (i32.const 56) (i32.const 3))
(drop (i32.atomic.rmw8.cmpxchg_u (i32.const 56) (i32.const 3) (i32.const 9)))
(i32.atomic.load8_u (i32.const 56)))
(func (export "wait_notify") (result i32)
(i32.atomic.store (i32.const 64) (i32.const 1))
;; not-equal (expected 0, actual 1) => 1; notify returns 0 woken
(i32.add
(memory.atomic.wait32 (i32.const 64) (i32.const 0) (i64.const 0))
(memory.atomic.notify (i32.const 64) (i32.const 5))))
(func (export "fenced_load") (result i32)
(i32.atomic.store (i32.const 72) (i32.const 42))
(atomic.fence)
(i32.atomic.load (i32.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 (i32.const 80) (i32.const -1))
(i64.atomic.store (i32.const 88) (i64.const -0x8000000000000000))
(i32.add
(i32.atomic.load (i32.const 80))
(i32.wrap_i64 (i64.atomic.load (i32.const 88)))))
)