Skip to content

runtime/wasm: add opt-in non-moving collector (based on #100) - #105

Draft
cpunion wants to merge 8 commits into
codex/fork-wasm-wasi-single-worker-20260801from
codex/fork-wasm-nonmoving-gc-20260801
Draft

runtime/wasm: add opt-in non-moving collector (based on #100)#105
cpunion wants to merge 8 commits into
codex/fork-wasm-wasi-single-worker-20260801from
codex/fork-wasm-nonmoving-gc-20260801

Conversation

@cpunion

@cpunion cpunion commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Depends on #100.

Fork-only staging PR for xgo-dev#2152 (stage T). It intentionally depends on W but not B, keeping collector work independent from channel/sync changes and avoiding CI load on xgo-dev/llgo. The old upstream xgo-dev#2203 draft remains frozen.

Problem

Wasm currently selects nogc because BDWGC is unavailable. J32, J64, and P1 therefore lack a runtime-owned allocator, reclamation, memory statistics, and linear-memory growth policy.

Default collection is not yet semantically safe: a live Go pointer may exist only in an SSA/wasm local, and suspended-G roots are not published until stage D. This PR keeps the collector behind the temporary internal llgo_wasm_gc tag; all default wasm, native, and embedded paths remain unchanged.

Implementation

  • Refactor the existing non-moving block allocator/collector behind memory-layout, growth, root-scan, and stack-stat hooks.
  • Keep the collector core in the existing runtime/internal/runtime/tinygogc package and add wasm-only adapters instead of duplicating the allocator in the scheduler or build packages.
  • Add J32/J64/P1 memory adapters using wasm linker boundaries, active stack limits, page size/growth, and metadata relocation.
  • Route Emscripten and WASI allocation entry points to the collector only for tagged builds. -sMALLOC=none is injected only for tagged JS/wasm.
  • Reuse the non-moving runtime allocation/ReadMemStats integration for bare-metal and wasm. Finalizers and cleanup hooks remain unsupported in this initial collector.
  • Reject tagged P1 builds when LLGO_WASI_THREADS=1; this collector is single-worker.
  • Run J32/J64/P1 GC fixtures in the existing wasm-runtime job. P1 is now validated with wasm-tools and executed under Wasmtime rather than only file-checked.

The collector currently scans globals and the active linear stack. It does not claim visibility into wasm locals or suspended Asyncify contexts. Stage D supplies compiler-maintained root records before any default enablement.

Validation

All local builds used GOMAXPROCS=2, GOMEMLIMIT=6GiB, -p=1, fresh -a builds, and LLGO_BUILD_CACHE=off. The Ubuntu container used 2 CPUs and a 6 GiB hard memory limit.

  • configureWasmGC, hasBuildTag, and effectiveTypeSizes are each 100% covered.
  • The runtime module passes in macOS arm64 and Ubuntu 24.04 amd64 containers with Go 1.26.5.
  • J32, J64, and P1 execute allocation statistics, reclamation, global/root retention, aligned allocation, bounded heap growth, collection, and live-data integrity checks: wasm gc ok.
  • P1 passes wasm-tools validate --features all and Wasmtime execution.
  • Tagged P1 with LLGO_WASI_THREADS=1 fails with the intended single-worker diagnostic.
  • Base runtime/wasm: add WASI single-worker scheduler (based on xgo-dev #2192, #2208) #100 head 50866cd89 has 39 passing checks across Ubuntu/macOS, Go 1.24/1.26, wasm runtime, LTO, coverage, release artifacts, and embedded targets; only release publication is intentionally skipped.
  • This head 0fa359652 has the same final result: 39 passing checks, one expected release-publication skip, and no failures; the PR is clean, mergeable, and has no unresolved review threads.

Default-path cost

Using identical inputs against #100:

  • J32 default wasm payload is byte-identical at 118,603 B.
  • J64 default wasm payload is byte-identical at 135,937 B.
  • P1 default wasm payload is byte-identical at 107,125 B.
  • The representative native binary remains 84,864 B with identical section sizes.
  • The representative Cortex-M4 ELF is byte-identical at 1,804 B (text/data/bss = 140/0/10).

Opt-in size

Using the same wasm-runtime input in default and tagged modes:

Target default opt-in GC Delta
J32 118,603 B 129,277 B +10,674 B (+9.0%)
J64 135,937 B 152,626 B +16,689 B (+12.3%)
P1 107,125 B 121,186 B +14,061 B (+13.1%)

Independent diff over #100: 29 files, +546/-62. No previously runnable test is skipped or ignored.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

0fa359652b08 | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18624 B +0.4% (worse) 246.712 ms -40.2% (better) 771.458 us -50.3% (better)
Linux fmtprintf 2217952 B +0.2% (worse) 2.530 s -34.4% (better) 1.593 ms -51.5% (better)
Linux println 71584 B -1.5% (better) 246.175 ms -38.5% (better) 986.678 us -41.0% (better)
macOS cprintf 84672 B +0.0% 331.455 ms -20.0% (better) 2.865 ms -20.2% (better)
macOS fmtprintf 2361520 B +0.7% (worse) 2.543 s -35.5% (better) 17.159 ms -26.6% (better)
macOS println 125712 B -1.0% (better) 336.921 ms -18.7% (better) 7.904 ms +8.9% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 8.097 ns/op -39.9% (better)
Linux BenchmarkMergeCompilerFlags 99.880 ns/op -35.4% (better)
Linux BenchmarkMergeLinkerFlags 64.160 ns/op -36.7% (better)
Linux BenchmarkChannelBuffered 45.810 ns/op +31.8% (worse)
Linux BenchmarkChannelHandoff 20572 ns/op -35.8% (better)
Linux BenchmarkDefer 43.400 ns/op -22.9% (better)
Linux BenchmarkDirectCall 0.276 ns/op -82.4% (better)
Linux BenchmarkGlobalRead 0.424 ns/op -72.8% (better)
Linux BenchmarkGlobalWrite 7.797 ns/op +212.6% (worse)
Linux BenchmarkGoroutine 33408 ns/op -55.8% (better)
Linux BenchmarkInterfaceCall 4.467 ns/op -42.7% (better)
Linux BenchmarkRuntimeGetG 0.805 ns/op -84.8% (better)
macOS BenchmarkLookupPCRandom 12.360 ns/op -0.6% (better)
macOS BenchmarkMergeCompilerFlags 116.300 ns/op -6.6% (better)
macOS BenchmarkMergeLinkerFlags 84.230 ns/op +21.5% (worse)
macOS BenchmarkChannelBuffered 21.350 ns/op -23.0% (better)
macOS BenchmarkChannelHandoff 7423 ns/op +5.9% (worse)
macOS BenchmarkDefer 26.690 ns/op -28.7% (better)
macOS BenchmarkDirectCall 0.998 ns/op -31.3% (better)
macOS BenchmarkGlobalRead 1.035 ns/op -14.5% (better)
macOS BenchmarkGlobalWrite 1.055 ns/op -29.1% (better)
macOS BenchmarkGoroutine 28320 ns/op -10.5% (better)
macOS BenchmarkInterfaceCall 4.241 ns/op -21.4% (better)
macOS BenchmarkRuntimeGetG 2.322 ns/op -9.9% (better)

Compared only with the latest matching platform in the main series.

Warning

  • Persistent publishing is unavailable because no data token is configured.

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