This proposal updates the WebAssembly profile proposal. The J32/J64/W32 data model is unchanged.
Implementation status (2026-09-25)
Decision
LLGo will use a pinned, LLGo-patched Binaryen as the supported WebAssembly build toolchain. The browser profile keeps Emscripten, Fiber, and Asyncify for its C/C++ and JavaScript compatibility. Removing Binaryen is no longer a scheduled migration or an acceptance criterion. The wasmresume stackless ABI remains an experimental Wasm-only alternative, to be considered after end-to-end evidence shows a material benefit and a working C/JS boundary.
| Target |
Execution and host integration |
Binaryen use |
| Browser J32/J64 |
Emscripten Fiber/Asyncify; one scheduler M initially, then a bounded Web Worker pool |
Emscripten uses the LLGo Binaryen tool suite through EM_BINARYEN_ROOT |
| WASI Preview 1 W32 |
WASI threads and pthread stacks; WAMR is the first supported runtime |
No Asyncify after the threaded profile replaces single-thread WASI |
| Other platforms |
Existing pthread/thread runtime; one goroutine creates one host thread |
None |
The browser must not create one Web Worker per goroutine. The bounded-worker prototype shows that Fiber-based goroutines can be assigned to a fixed worker pool, although its multi-worker GC is unfinished. Binaryen is therefore not itself a blocker for bounded workers. Go stacks may remain bound to their assigned worker in the first implementation; work stealing is not required.
Why a patched Binaryen
Binaryen currently performs Asyncify instrumentation, post-link optimization, and (where needed) legacy-EH-to-exnref translation. Transforming code after LLVM emits DWARF can invalidate lexical-scope ranges. Binaryen PR #8964 repairs this; upstream review and merge remain uncertain. LLGo cannot make debug correctness depend on that merge, so the supported toolchain must carry the fix itself.
The patch has been backported to Binaryen version_132, matching Emscripten 6.0.8's expected Binaryen version. In a focused LLGo browser build with debug information, stock 132 produced 125 parent-containment and 24 overlap errors in llvm-dwarfdump --verify; the patched build produced none. Both artifacts ran the sample in Node. This is a smoke result, not a substitute for the platform and C/C++ regression suite below.
Keeping the existing C stack avoids making a new stackless-Go/JSPI/C callback bridge a prerequisite for C compatibility. Emscripten remains the C/C++ SDK, sysroot, linker, ports, and JavaScript glue provider. No C function needs a blocking/async annotation merely to preserve the current build behavior.
Fork, branch, and release policy
The toolchain source is xgo-dev/binaryen, a fork of WebAssembly/binaryen:
main is a clean mirror of upstream main. It contains no LLGo commits and is fast-forwarded from upstream, never merged into llgo automatically.
llgo is the fork's default branch and the only source branch for LLGo Binaryen releases. It starts from the upstream version_132 tag and contains the tested DWARF backport and any LLGo-specific build/test changes.
- Changes to
llgo are submitted by pull request from a cpunion/binaryen branch. Do not push commits directly to xgo-dev/binaryen:llgo; release tags follow review and integration of the relevant PR.
- Release tags use
llgo-v132.N and point to a reviewed llgo commit. The release records the upstream base commit, patch provenance, Emscripten version, checksums, and license/notice files. A later upstream rebase or Binaryen upgrade is a tested update to llgo, not an automatic merge from main.
- Release automation builds the complete Binaryen tools needed by both Emscripten and LLGo, not only
wasm-opt, for Linux, macOS, and Windows hosts used in LLGo CI. Publishing is gated on the version-compatibility, DWARF, runtime, and tool smoke tests below.
LLGo pins an exact release and checksum. Browser builds set EM_BINARYEN_ROOT to the extracted release root, because Emscripten invokes wasm-emscripten-finalize and wasm-opt from that root. The standalone LLGo post-link path uses its WASMOPT setting. Setting only WASMOPT does not select the patched Binaryen in an Emscripten browser build. Build failures must report which Binaryen path and version were selected.
Binaryen is Apache-2.0; distribution includes its license, bundled third-party notices, and a record of LLGo's changes. No source-level replacement of Binaryen is planned.
Runtime direction
Browser. Keep the working Fiber/Asyncify backend. First make one-M behavior and debug output reliable, then adapt the bounded-worker prototype to the current runtime. Multiple Ms require worker-safe scheduling, allocation, root publication, and stop-the-world GC. A blocking C call occupies its M. The first pool can pin each G to one M, preserving C/Fiber/TLS ownership; the pool has a configurable bound and supports one worker.
WASI. Move W32 to WASI threads with WAMR as the first runner and retain the pthread execution model. Remove single-thread WASI/Asyncify only after threaded GC, pthread startup/shutdown, and runtime tests pass. Native and embedded pthread behavior stays unchanged.
Exceptions. Keep the currently validated EH encoding for each supported profile while testing direct LLVM legacy EH, direct standard exnref EH, and Binaryen translation side by side. Test Go panic/recover, C++ catch inside C++, C-to-Go translation at a wrapper, and reject or contain foreign EH/SjLj edges across Go. Do not change EH encoding merely as a side effect of the Binaryen fork.
WasmGC and later WASI/component runtimes. The current linear-memory heap remains the baseline. A future WasmGC heap and WASI Component Model async adapter are separate work; neither requires replacing Binaryen now. Archive/build identities must record memory width, EH, thread capability, and host ABI, so incompatible objects fail at link time.
Implementation work
- Toolchain fork and release: maintain
main as an upstream mirror; create default llgo from version 132; backport #8964 with tests; build and publish llgo-v132.N only from llgo.
- LLGo integration: pin the release/checksums in LLGo CI and local setup; select it through both
EM_BINARYEN_ROOT and WASMOPT as appropriate; record versions in build diagnostics.
- Debug regression: verify final browser and WASI DWARF with
llvm-dwarfdump --verify; exercise source stepping, variables, and C/C++ frames at supported optimization levels. Test patch upgrades against upstream Binaryen and Emscripten upgrades before changing the pin.
- WAMR threads and GC: complete threaded collector and pthread compatibility, then retire the single-thread WASI profile.
- Bounded browser workers: adapt the existing Fiber-based worker prototype, including cross-worker wakes, callbacks, lifecycle, GC, and COOP/COEP browser tests. Keep one-worker mode.
- Continuation research (optional): measure per-G memory, code size, latency, debugging, C/Go/JS callbacks, JSPI browser coverage, and EH against the supported backend.
wasmresume or LLVM CoroSplit may be selected for Wasm only after those tests show a clear benefit without losing C compatibility. Neither replaces pthread/thread on other platforms.
Acceptance gates
main equals an upstream main commit; the fork default branch is llgo; release tags are reachable from and built from llgo.
- Emscripten and LLGo both select the pinned Binaryen release. Version 132 matches the pinned Emscripten; archive checksums and notices are published.
- Debug builds pass final-Wasm validation and DWARF verification; browser C/C++ and Go runtime smoke tests pass on the supported hosts.
- W32 runs under WAMR with WASI threads and GC enabled before single-thread WASI is removed.
- One-worker and bounded multi-worker browser runs preserve Go-visible scheduling, panic, callback, and lifecycle behavior. GC works in both modes.
- EH translation choices are supported by direct comparison tests. Native and embedded pthread tests retain their current behavior.
Tradeoff and revisit trigger
The supported browser runtime currently allocates both a Fiber stack and an Asyncify buffer per G (128 KiB each by default on wasm32). That is approximately 256 KiB/G before other state, and it may limit very high goroutine counts. We will measure realistic workloads and consider smaller, pooled, or alternative continuation storage. A stackless replacement becomes a delivery plan only if it passes the C/JS/EH/GC/debug acceptance suite and materially improves the measured limit. Until then, the patched Binaryen is the build contract rather than a temporary stopgap.
This proposal updates the WebAssembly profile proposal. The J32/J64/W32 data model is unchanged.
Implementation status (2026-09-25)
llgo-v132.3Binaryen release is published and used by LLGo's pinned-toolchain integration in build/wasm: pin one LLGo Binaryen release for Emscripten and WASI #2652. The original upstream Binaryen #8964 remains open.Goexitunder WAMR threads (#2676), the fulltest/goshard, tested legacy-EH/exnrefoutput choices across runtimes (browser comparison in test/wasm: compare EH paths in Node and Chrome #2654), final DWARF/source-debug checks (build/wasm: retain final Go and C++ DWARF after optimization #2662), and removal of single-thread WASI. The WAMR signal-handler recursion found during host qualification has a separate fix in WAMR #5119.Decision
LLGo will use a pinned, LLGo-patched Binaryen as the supported WebAssembly build toolchain. The browser profile keeps Emscripten, Fiber, and Asyncify for its C/C++ and JavaScript compatibility. Removing Binaryen is no longer a scheduled migration or an acceptance criterion. The
wasmresumestackless ABI remains an experimental Wasm-only alternative, to be considered after end-to-end evidence shows a material benefit and a working C/JS boundary.EM_BINARYEN_ROOTThe browser must not create one Web Worker per goroutine. The bounded-worker prototype shows that Fiber-based goroutines can be assigned to a fixed worker pool, although its multi-worker GC is unfinished. Binaryen is therefore not itself a blocker for bounded workers. Go stacks may remain bound to their assigned worker in the first implementation; work stealing is not required.
Why a patched Binaryen
Binaryen currently performs Asyncify instrumentation, post-link optimization, and (where needed) legacy-EH-to-
exnreftranslation. Transforming code after LLVM emits DWARF can invalidate lexical-scope ranges. Binaryen PR #8964 repairs this; upstream review and merge remain uncertain. LLGo cannot make debug correctness depend on that merge, so the supported toolchain must carry the fix itself.The patch has been backported to Binaryen
version_132, matching Emscripten 6.0.8's expected Binaryen version. In a focused LLGo browser build with debug information, stock 132 produced 125 parent-containment and 24 overlap errors inllvm-dwarfdump --verify; the patched build produced none. Both artifacts ran the sample in Node. This is a smoke result, not a substitute for the platform and C/C++ regression suite below.Keeping the existing C stack avoids making a new stackless-Go/JSPI/C callback bridge a prerequisite for C compatibility. Emscripten remains the C/C++ SDK, sysroot, linker, ports, and JavaScript glue provider. No C function needs a blocking/async annotation merely to preserve the current build behavior.
Fork, branch, and release policy
The toolchain source is
xgo-dev/binaryen, a fork ofWebAssembly/binaryen:mainis a clean mirror of upstreammain. It contains no LLGo commits and is fast-forwarded from upstream, never merged intollgoautomatically.llgois the fork's default branch and the only source branch for LLGo Binaryen releases. It starts from the upstreamversion_132tag and contains the tested DWARF backport and any LLGo-specific build/test changes.llgoare submitted by pull request from acpunion/binaryenbranch. Do not push commits directly toxgo-dev/binaryen:llgo; release tags follow review and integration of the relevant PR.llgo-v132.Nand point to a reviewedllgocommit. The release records the upstream base commit, patch provenance, Emscripten version, checksums, and license/notice files. A later upstream rebase or Binaryen upgrade is a tested update tollgo, not an automatic merge frommain.wasm-opt, for Linux, macOS, and Windows hosts used in LLGo CI. Publishing is gated on the version-compatibility, DWARF, runtime, and tool smoke tests below.LLGo pins an exact release and checksum. Browser builds set
EM_BINARYEN_ROOTto the extracted release root, because Emscripten invokeswasm-emscripten-finalizeandwasm-optfrom that root. The standalone LLGo post-link path uses itsWASMOPTsetting. Setting onlyWASMOPTdoes not select the patched Binaryen in an Emscripten browser build. Build failures must report which Binaryen path and version were selected.Binaryen is Apache-2.0; distribution includes its license, bundled third-party notices, and a record of LLGo's changes. No source-level replacement of Binaryen is planned.
Runtime direction
Browser. Keep the working Fiber/Asyncify backend. First make one-M behavior and debug output reliable, then adapt the bounded-worker prototype to the current runtime. Multiple Ms require worker-safe scheduling, allocation, root publication, and stop-the-world GC. A blocking C call occupies its M. The first pool can pin each G to one M, preserving C/Fiber/TLS ownership; the pool has a configurable bound and supports one worker.
WASI. Move W32 to WASI threads with WAMR as the first runner and retain the pthread execution model. Remove single-thread WASI/Asyncify only after threaded GC, pthread startup/shutdown, and runtime tests pass. Native and embedded pthread behavior stays unchanged.
Exceptions. Keep the currently validated EH encoding for each supported profile while testing direct LLVM legacy EH, direct standard
exnrefEH, and Binaryen translation side by side. Test Go panic/recover, C++ catch inside C++, C-to-Go translation at a wrapper, and reject or contain foreign EH/SjLj edges across Go. Do not change EH encoding merely as a side effect of the Binaryen fork.WasmGC and later WASI/component runtimes. The current linear-memory heap remains the baseline. A future WasmGC heap and WASI Component Model async adapter are separate work; neither requires replacing Binaryen now. Archive/build identities must record memory width, EH, thread capability, and host ABI, so incompatible objects fail at link time.
Implementation work
mainas an upstream mirror; create defaultllgofrom version 132; backport #8964 with tests; build and publishllgo-v132.Nonly fromllgo.EM_BINARYEN_ROOTandWASMOPTas appropriate; record versions in build diagnostics.llvm-dwarfdump --verify; exercise source stepping, variables, and C/C++ frames at supported optimization levels. Test patch upgrades against upstream Binaryen and Emscripten upgrades before changing the pin.wasmresumeor LLVM CoroSplit may be selected for Wasm only after those tests show a clear benefit without losing C compatibility. Neither replaces pthread/thread on other platforms.Acceptance gates
mainequals an upstreammaincommit; the fork default branch isllgo; release tags are reachable from and built fromllgo.Tradeoff and revisit trigger
The supported browser runtime currently allocates both a Fiber stack and an Asyncify buffer per G (128 KiB each by default on wasm32). That is approximately 256 KiB/G before other state, and it may limit very high goroutine counts. We will measure realistic workloads and consider smaller, pooled, or alternative continuation storage. A stackless replacement becomes a delivery plan only if it passes the C/JS/EH/GC/debug acceptance suite and materially improves the measured limit. Until then, the patched Binaryen is the build contract rather than a temporary stopgap.