Skip to content

runtime/wasm: enable cooperative safepoints and GC by default (based on #2204, #2207) - #2214

Draft
cpunion wants to merge 63 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-cooperative-safepoints
Draft

runtime/wasm: enable cooperative safepoints and GC by default (based on #2204, #2207)#2214
cpunion wants to merge 63 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-cooperative-safepoints

Conversation

@cpunion

@cpunion cpunion commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Depends on #2204 and #2207.

Problem

The root frames from #2207 make running and suspended goroutine pointers visible to the wasm collector, but collection still requires an internal opt-in tag. A CPU-bound goroutine can also keep the single wasm worker indefinitely, preventing host timers, runnable goroutines, and timer-triggered GC from executing.

Implementation

  • Add an independent internal/safepointplan CFG analysis. It inserts polls at declared Go function entries and DFS cycle-closing backedges, which intersects reducible and irreducible cycles without polling every loop block.
  • Feed synthetic polls into the existing GC liveness planner, including pointer-bearing parameters and closure context at the entry poll, so every value live across a possible yield remains published.
  • Resolve generic SSA instances through their Origin/Parent before deciding whether to instrument them. Runtime packages, //go:nosplit, cgo bodies, and package-less synthetic forwarding wrappers are not instrumented; this prevents scheduler helper re-entry while a G is already transitioning state.
  • Add a testable fixed poll budget. The inlined fast path decrements one shared single-worker budget; every 1,024 polls the noinline slow path dispatches due host events and yields only when another G is runnable.
  • Enable the existing non-moving collector, root publication, and cooperative polls by default for J32, J64, and default single-worker P1. Explicit LLGO_WASI_THREADS=1 keeps the historical pthread/nogc path, and explicitly combining it with llgo_wasm_gc remains an error.
  • Keep llgo_wasm_gc as an internal source-selection tag appended by the build; users no longer need to pass it.
  • Make nested SSA field access apply the same type patch as aggregate lowering. Default J32 exposed this with syscall/js.Value nested in another struct.
  • Supply posix_memalign from the wasm collector allocator because default Emscripten GC selection uses -sMALLOC=none.

Runtime flow

  1. A compiled application function publishes its root frame and executes the entry poll.
  2. Loop backedges execute the same inlined budget check.
  3. Most checks return immediately. On budget expiry, the runtime polls the host event queue.
  4. A due timer callback becomes runnable; if the run queue is non-empty, the current G is requeued and the scheduler switches Gs.
  5. GC runs synchronously on that one worker. Other Gs are suspended with root chains owned by their contexts, so this is naturally stop-the-world without a multi-worker handshake.

Runtime implementation packages remain outside instrumentation. The compiler analysis, root liveness, budget, scheduler slow path, host event queue, and collector remain separate modules.

Validation

All local work used GOMAXPROCS=2, GOMEMLIMIT=3GiB, and -p=1 where applicable.

  • Go 1.26.5 full tests: internal/safepointplan, internal/build, cl, and ssa pass.
  • Go 1.24.11 focused compatibility tests for every new path pass.
  • Runtime pollbudget, gcroot, and wasmevent tests pass.
  • J32, J64, and P1 GC fixtures execute successfully at -O0 and -O3; J64 and P1 ThinLTO also pass.
  • The GC fixture covers globals, an active CPU-bound G, a timer-triggered collection, a channel-waiting G, panic/recover root restoration, reclamation, aligned allocation, and linear-memory growth.
  • Existing runtime, scheduler, blocking, and timer fixtures pass at -O3 on J32/J64/P1 (12 combinations). Every P1 artifact passes wasm-tools validate --features all.
  • Explicit P1 pthread mode still builds and contains no llvm_gc_root_chain, collector, or CooperativeSafepoint symbol.
  • Ubuntu 24.04/amd64 validation passes in a container limited to 15 GiB and 2 CPUs with Go 1.26.5 and LLVM 19.1.7.

Coverage highlights from local profiles:

  • internal/safepointplan: 100%
  • runtime/internal/pollbudget: 100%
  • configureWasmGC: 100%
  • CL safepoint emission helpers: 85.7-100%
  • root preparation: 97.9%

Cost

On Apple M4 Max, BenchmarkBudgetPoll is 1.795-1.799 ns/op with 0 B/op and 0 allocs/op.

A target-level -O3 microbenchmark compared the same 20-million-iteration arithmetic loop with a normal poll at every backedge against a //go:nosplit no-poll control. Values are medians of five rounds:

Target no poll poll absolute delta
J32 / Node 3.342 ns/op 4.911 ns/op +1.568 ns/op
J64 / Node 3.863 ns/op 5.459 ns/op +1.595 ns/op
P1 / Wasmtime 3.879 ns/op 5.601 ns/op +1.722 ns/op

This is a deliberately minimal worst-case loop, so the relative increase is 41-47%; the absolute backedge cost is about 1.6-1.7 ns and does not allocate. The public fast wrapper is absent from the final wasm symbol strings after optimization; only the noinline slow path remains.

Against the direct #2204 + #2207 integration base, representative native Mach-O output remains 92,816 bytes and every section size is identical; only the linker UUID/hash differs. Local cortex-m-qemu comparison is currently blocked by the same pre-existing runtime debug/pclntab compile errors on both the base and this branch, so the existing bounded Targets CI remains the embedded artifact gate.

Review scope

Excluding the dependency merge, this PR changes 21 files with 775 insertions and 30 deletions. The commits separate nested type-patch correctness, compiler planning/emission, runtime polling, and default build/acceptance behavior.

cpunion added 30 commits July 28, 2026 12:56
cpunion added 25 commits July 29, 2026 07:43
…orker-asyncify-scheduler

# Conflicts:
#	internal/build/source_patch_test.go
…orker-asyncify-scheduler

# Conflicts:
#	runtime/internal/runtime/z_default.go
# Conflicts:
#	.github/workflows/llgo.yml
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant