mirror of
https://github.com/hanzoai/wasm2go.git
synced 2026-10-30 15:06:48 +00:00
Adds the wasm threads proposal to the codegen and, on top of it, a
copy-on-write memory-sharing facility for embedders that stand up many
instances of the same module.
Threads
- Lower the 0xFE atomic family (load/store/rmw/cmpxchg across widths and
sub-word lanes, plus wait/notify) to sync/atomic-based module helpers.
- wasi_thread_spawn runs each guest thread on a goroutine. A shared memory
is allocated at its declared ceiling once and never relocates, so every
agent's cached base pointer stays valid; growth is a lone atomic store.
- Passive data segments, per-agent globals, coherent plain access for
shared memories, and multi-package output for threaded modules.
- A shared memory's ceiling is a runtime value the embedder chooses, not
the binary's declared maximum.
Copy-on-write shared memory
- NewWithMemory lets the caller supply the linear memory, and memory.init
compares before it writes — so a copy-on-write map of a pre-initialized
image survives the start section instead of being faulted private.
- NewSharedImage builds an initialized image once per process and maps it
copy-on-write into every instance: the data segments (identical in every
instance, read-mostly) then cost almost nothing per instance.
- NewSharedSnapshot / NewFromSnapshot share a WHOLE initialized instance —
memory and wasm globals both — so instances re-run nothing at all.
SaveGlobals/RestoreGlobals carry the initialized state that lives outside
linear memory (the stack pointer and other mutable globals).
Also: coverage for the threads/atomics helpers, a WASM2GO_SSA_PASSES_OFF
diagnostic knob, an AtomicWait timeout margin, and thread-trap visibility.
Co-authored-by: Claude Opus 4.8 <[email protected]>
47 lines
2.1 KiB
WebAssembly Text Format
47 lines
2.1 KiB
WebAssembly Text Format
;; A Drepper-style futex mutex (0=free,1=locked,2=locked+waiters) hammered from
|
|
;; both the main thread and an agent — the exact handoff wasi-libc's
|
|
;; pthread_mutex does. If the unlock's exchange ever fails to observe a
|
|
;; waiter's 1->2 transition, one side sleeps forever and the test times out.
|
|
(module
|
|
(import "wasi" "thread-spawn" (func $spawn (param i32) (result i32)))
|
|
(memory 1 4 shared)
|
|
;; 16: mutex word / 24: shared counter / 32: agent-done flag
|
|
(func $lock
|
|
(block $acquired
|
|
;; fast path
|
|
(br_if $acquired
|
|
(i32.eqz (i32.atomic.rmw.cmpxchg offset=12 (i32.const 4) (i32.const 0) (i32.const 1))))
|
|
(loop $retry
|
|
;; mark contended and wait while it stays 2
|
|
(if (i32.ne (i32.atomic.rmw.xchg offset=12 (i32.const 4) (i32.const 2)) (i32.const 0))
|
|
(then
|
|
(drop (memory.atomic.wait32 offset=12 (i32.const 4) (i32.const 2) (i64.const -1)))
|
|
(br $retry))))))
|
|
(func $unlock
|
|
(if (i32.ne (i32.atomic.rmw.xchg offset=12 (i32.const 4) (i32.const 0)) (i32.const 1))
|
|
(then (drop (memory.atomic.notify offset=12 (i32.const 4) (i32.const 1))))))
|
|
(func $bump (param $n i32)
|
|
(local $i i32)
|
|
(block $done (loop $l
|
|
(br_if $done (i32.ge_u (local.get $i) (local.get $n)))
|
|
(call $lock)
|
|
(i32.store (i32.const 24) (i32.add (i32.load (i32.const 24)) (i32.const 1)))
|
|
(call $unlock)
|
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
(br $l))))
|
|
(func (export "wasi_thread_start") (param $tid i32) (param $arg i32)
|
|
(call $bump (i32.const 50000))
|
|
(i32.atomic.store (i32.const 32) (i32.const 1)))
|
|
(func (export "run") (result i32)
|
|
(local $i i32)
|
|
(drop (call $spawn (i32.const 0)))
|
|
(call $bump (i32.const 50000))
|
|
;; wait for the agent to finish (bounded spin: a wedged agent FAILS)
|
|
(block $got (loop $wait
|
|
(br_if $got (i32.eq (i32.atomic.load (i32.const 32)) (i32.const 1)))
|
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
(br_if $got (i32.ge_u (local.get $i) (i32.const 2000000000)))
|
|
(br $wait)))
|
|
(call $lock)
|
|
(i32.load (i32.const 24))))
|