mirror of
https://github.com/hanzoai/wasm2go.git
synced 2026-10-30 15:06:48 +00:00
* 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]>
61 lines
3.1 KiB
WebAssembly Text Format
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)))))
|
|
)
|