runtime/wasm: add WASI single-worker scheduler (based on #2192) - #2197
runtime/wasm: add WASI single-worker scheduler (based on #2192)#2197cpunion wants to merge 18 commits into
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
24020c3 to
e3e8ea8
Compare
4cbebe3 to
4856fa7
Compare
There was a problem hiding this comment.
Review summary
This is a large, well-structured PR that adds single-worker WASM/WASI (Asyncify) scheduling to the LLGo runtime. The backends are cleanly separated by build tags, the intrusive run queue is allocation-free and well-tested, and wasm_postlink has strong test coverage including failure paths. No correctness-breaking bugs were found in the scheduler core, run queue, Asyncify ABI wiring, or eh.go. I did not find any blocking security issues — postLinkWasm builds argv directly (no shell) and writes atomically; the context_wasm.S offsets correctly match the Context struct layout.
Findings below are mostly minor consistency/robustness and doc-accuracy items; the most actionable is the per-goroutine memory floor. Inline comments cover the concrete diff-line findings.
Additional notes (no reliable single inline location):
- WASI yield cost (design-inherent, informational): Unlike the js backend (direct fiber
Swap), the wasip1 backend routes every switch through theRunWasmMaindispatch loop via Asyncify unwind + rewind (proc_wasip1.go,context_wasip1.go). That cost is O(active frame depth) and paid twice per switch, soGoschedon a deep stack is not cheap — runtime code should prefer park/ready handoff over spin-yielding on this backend. Largely inherent to the single-worker Asyncify design; flagging for awareness. - CI
$moduleinterpolation (low):.github/workflows/llgo.ymlinterpolates$moduleinto an inlinenode -e "import Module from '$module'; ...". The value is a CI-controlled path under$RUNNER_TEMP, so it's not an injection vector today; worth a note only if these helpers are ever reused with externally-influenced paths.
6c6211b to
a64dba2
Compare
LLGo baseline benchmarks
Program measurements
Core language and compiler benchmarks
Compared only with the latest matching platform in the main series. |
a64dba2 to
b4e35ee
Compare
b4e35ee to
51ad21a
Compare
Depends on #2192.
Part of #2152.
Problem
GOOS=wasip1 GOARCH=wasm llgo buildcan produce a module after #2192, but goroutines still select the pthread runtime. The resulting executable imports shared host memory and WASI pthread facilities, so ordinary single-worker Wasmtime execution cannot run Go scheduler behavior.The continuation implementation also needs a clear ownership boundary. Scheduler code should not know whether a backend uses Emscripten Fiber, a raw Asyncify save area, a future resumable ABI, or standardized stack switching.
Implementation
LLGO_WASI_THREADS=1compatibility mode.main.mainas the first schedulable task while keeping the host entry on the system stack.runtime/internal/wasmcontext.Swap,Resume, andSuspendremain direct concrete hot-path calls.runtime/internal/runtime; the continuation package does not inspect G/M/P state.LLGO_WASI_THREADS=1is set.runtime.EnsureLocalInitializerafter compiler/runtime: add //llgo:tls and //llgo:gls package variables #2079.The implementation is intentionally limited to the scheduler/context foundation. Channel blocking, timers and host async integration, wasm GC integration, root/safepoint work, and optional WASI Preview2 support remain follow-up work tracked by #2152.
Validation
All local commands use
GOMAXPROCS=2,GOMEMLIMIT=4GiB, and-p=1where applicable.6e2f57576, which includes upstream maine82e95fbe; package init functions are preserved in dependency order inside the schedulable main task.runtime.Goexit()lifecycle/deadlock behavior.LLGO_WASI_THREADS=1both build; default P1 validates and runs under Wasmtime 39.0.1.-O0,-O3, and ThinLTO.runtime/internal/wasmcontextandruntime/internal/runqueueeach have 100% statement coverage; the changed wasm post-link and static defer dispatch paths are covered by focused tests._demo/c/hellocpath, and the full Ubuntu/macOS CI matrix.runtime.printany; the same failure reproduces on the unmodified runtime/wasm: add single-worker Asyncify scheduler #2192 base.The lifecycle abstraction changes final scheduler modules by +2,991 bytes for J32 and +1,567 bytes for J64 against the same pre-lifecycle #2192/W tree. Binaryen reports six additional functions, two table entries, and seven indirect calls from the cold allocation callbacks. This is artifact size, not per-G memory; the existing 64 KiB stack and 64 KiB Asyncify reservations are unchanged. The measured P1 +172-byte integration delta uses an older W baseline and is not presented as a pure lifecycle comparison.
Independent diff over the current #2192 head: 33 files, +1,453/-113. The branch is linear: current #2192, then nine original #2197 commits plus one post-rebase scheduler-lifecycle integration commit. Current head:
51ad21aee. No previously runnable test is skipped or ignored.