Skip to content

runtime/wasm: preserve suspended goroutine GC roots (based on #2198, #2203) - #2207

Draft
cpunion wants to merge 49 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-safepoints-roots-v2
Draft

runtime/wasm: preserve suspended goroutine GC roots (based on #2198, #2203)#2207
cpunion wants to merge 49 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-safepoints-roots-v2

Conversation

@cpunion

@cpunion cpunion commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Depends on #2198 and #2203.

Part of #2152 (stage D).

Problem

The opt-in wasm collector from #2203 can scan globals and the active linear stack, but a live Go pointer may exist only in an SSA/wasm local. A goroutine suspended by the single-worker scheduler can also retain pointers in an inactive Asyncify context. Neither source is visible to the collector, so collection while another goroutine is suspended can reclaim live objects. Panic/longjmp unwinding adds the same risk unless the active root chain is restored with the defer frame.

Implementation

  • Add an independent internal/gcrootplan SSA liveness planner. It publishes only pointer-bearing values live across GC safepoints, including aggregate pointer fields and closure contexts.
  • Emit a per-function explicit root frame with the stable layout {next, map, roots[N]} and link it through llvm_gc_root_chain. Root access is a direct frame store; there is no heap allocation or per-access lookup.
  • Use manual frames rather than LLVM llvm.gcroot/shadow-stack lowering. LLVM 22 can move or inline llvm.gcroot before backend lowering at optimized levels and crash CodeGenPrepare; the explicit frame ABI remains valid through -O0 and optimized builds.
  • Save and restore the current root-chain head with each JS/WASI scheduler context. The context switch occurs after the wrapper frame is installed, so a suspended goroutine keeps the complete chain.
  • During WASI Asyncify rewind, query the rewind tag without switching back to the system owner a second time.
  • Capture the root-chain head in defer state and restore it on panic/recover longjmp paths.

Compiler analysis, LLVM frame emission, runtime chain ownership, and collector scanning are separate modules. The feature remains selected only with the internal llgo_wasm_gc tag; default wasm, native, embedded, and threaded WASI behavior is unchanged.

Runtime flow

  1. Function entry links its stack-resident frame to llvm_gc_root_chain.
  2. Compiler-generated stores publish values when they become live at a possible collection point.
  3. Scheduler suspension stores the chain head in the goroutine context; another context installs its own head.
  4. The collector starts at the active context head and follows frame next links, using each frame map to scan only pointer slots.
  5. Normal return or panic unwinding restores the preceding chain head.

Validation

The expanded wasm GC fixture covers pointers, slices, strings, interfaces, closures, aggregate fields, suspended goroutines, and panic/recover while collection and heap growth occur. It passes on:

Target Result
J64: GOOS=js GOARCH=wasm Node Memory64: wasm gc ok
J32: llgo build -target wasm Node wasm32: wasm gc ok
P1: GOOS=wasip1 GOARCH=wasm LLGO_WASI_THREADS=0 wasm-tools validate and Wasmtime 39 with exceptions: wasm gc ok

Local macOS checks used GOMAXPROCS=2, GOMEMLIMIT=3GiB, and -p=1:

A root-wide cl run reached the existing testrt/namedslice executable hang and the Go test process timed out after 10 minutes; the new cl tests pass independently. Ubuntu validation is delegated to the bounded GitHub jobs.

Cost and review scope

Earlier no-GC scheduler comparisons measured +1,604 bytes for J32 (+0.28%) and +1,644 bytes for P1 (+0.31%); no collector/root implementation was linked into those fixtures. Excluding the #2198/#2203 merge tree, this PR changes 37 files with 1,855 additions and 66 deletions, including the planner, compiler/runtime implementation, fixtures, and tests.

cpunion added 30 commits July 28, 2026 12:56
…wasm-safepoints-roots-v2

# Conflicts:
#	.github/workflows/llgo.yml
cpunion added 19 commits July 29, 2026 07:42
…orker-asyncify-scheduler

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

# Conflicts:
#	runtime/internal/runtime/z_default.go
@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