Skip to content

[Based on #142] runtime/wasm: compose resumable contexts with bounded workers - #143

Draft
cpunion wants to merge 3 commits into
codex/fork-wasm-resumable-arena-20260802from
codex/fork-wasm-resumable-workers-20260802
Draft

[Based on #142] runtime/wasm: compose resumable contexts with bounded workers#143
cpunion wants to merge 3 commits into
codex/fork-wasm-resumable-arena-20260802from
codex/fork-wasm-resumable-workers-20260802

Conversation

@cpunion

@cpunion cpunion commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Depends on #142.

Implements the K3 worker-composition stage of xgo-dev#2152. The resumable backend remains opt-in through LLGO_WASM_RESUME=1.

Problem

K2 provides reusable resumable frame arenas for a single logical execution owner. The wasm worker scheduler still uses the Asyncify-specific continuation path, so resumable contexts cannot yet coexist with worker scheduling, stop-the-world GC, worker-local state, or worker teardown.

Design

  • Select Asyncify and resumable worker continuation implementations at build time while retaining one scheduler-facing interface.
  • Restore each resumable context's logical GC-root chain before execution and capture it after suspension.
  • Share the resumable owner ABI between single-worker and worker modes instead of duplicating allocation, release, and compatibility ownership.
  • Keep runnable queues on the zero-initialized wasm scheduler mutex. GC waits use a host-level non-suspending callback so no suspended Go frame retains the queue lock.
  • Clear inactive system root chains after context transfer and preserve K2's package-local frame-arena fast paths.
  • Register worker and GC runtime helpers as explicit non-suspending compiler boundaries.
  • Keep native and embedded code generation unchanged. Cortex-M4 output is byte-identical to K2 and macOS native text/data sizes are unchanged.

Execution flow

  1. A worker installs its worker identity, then enters the LLGo locality context.
  2. The scheduler selects a resumable context and restores that context's logical GC-root chain.
  3. Context.Run resumes the lowered continuation. A blocking operation returns to the worker scheduler without retaining a scheduler lock.
  4. The context captures its current logical root chain on suspension; runnable work may then move to another worker.
  5. GC stop-the-world waits only at a host callback boundary. Context roots remain reachable from their runtime-owned resumable contexts.
  6. Context close releases its retained frame arena and compatibility owner state.

Results

Five macOS arm64 runs of the worker hardening workload (10,000 channel handoffs, median):

Backend J32 J64
Asyncify workers 5.447 us/op 6.222 us/op
Resumable workers 13.112 us/op 10.755 us/op
Relative 2.41x slower 1.73x slower

The resumable worker path is functional but does not satisfy the proposal's default-backend performance gate.

Final artifact size for the same workload:

Target Asyncify Resumable Delta
J32 (.mjs + .wasm) 2,771,457 B 2,368,576 B -402,881 B (-14.54%)
J64 (.mjs + .wasm) 3,074,244 B 2,674,025 B -400,219 B (-13.02%)

Forced J32 build time was 30.99 s median versus 15.78 s for Asyncify workers (+96.4%), which is a second reason to keep the backend opt-in.

Validation

Resource bounds: GOMAXPROCS=2, GOMEMLIMIT=4GiB, -p=1.

  • Host unit tests: internal/build 77.4%, internal/wasmresume 95.2%, ssa 93.3%
  • Runtime unit tests: internal/gcroot 94.4%, internal/wasmresume 92.3%
  • J32/J64 combined resumable workers: scheduler, GC, hardening, runtime.Goexit, 1,000 blocked goroutines, zero retained-root delta
  • J32/J64 browser execution in Chrome for combined GC and hardening modules
  • P1 single-worker resumable hardening validated and executed with Wasmtime
  • Ubuntu LLVM 19 / Go 1.26.5 / Emscripten 4.0.21: package tests and J32/J64 combined builds; J32/J64 artifacts executed under Node 25
  • Combined artifacts contain no Asyncify, Emscripten Fiber, or runtime/internal/wasmcontext dependency
  • Native macOS text/data sizes unchanged; Cortex-M4 output byte-identical to K2

Diff against #142: 19 files, +674/-182 in three reviewable commits.

@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 2, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

11fed5f4ee5b | workflow run | long-term charts

Program measurements

Platform Workload File size vs main Build vs main Run vs main
Linux cprintf 18816 B +1.5% (worse) 381.931 ms -7.4% (better) 1.403 ms -9.6% (better)
Linux fmtprintf 2219448 B +0.3% (worse) 3.634 s -5.8% (better) 2.847 ms -13.4% (better)
Linux println 71776 B -1.2% (better) 374.044 ms -6.5% (better) 1.781 ms +6.4% (worse)
macOS cprintf 84672 B +0.0% 416.225 ms +0.5% (worse) 3.086 ms -14.0% (better)
macOS fmtprintf 2361968 B +0.7% (worse) 2.795 s -29.1% (better) 17.883 ms -23.5% (better)
macOS println 125712 B -1.0% (better) 380.207 ms -8.3% (better) 4.244 ms -41.5% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 12.480 ns/op -7.4% (better)
Linux BenchmarkMergeCompilerFlags 146.400 ns/op -5.2% (better)
Linux BenchmarkMergeLinkerFlags 95.360 ns/op -5.9% (better)
Linux BenchmarkChannelBuffered 36.340 ns/op +4.5% (worse)
Linux BenchmarkChannelHandoff 28270 ns/op -11.8% (better)
Linux BenchmarkDefer 43.490 ns/op -22.8% (better)
Linux BenchmarkDirectCall 1.762 ns/op +12.7% (worse)
Linux BenchmarkGlobalRead 1.759 ns/op +12.8% (worse)
Linux BenchmarkGlobalWrite 2.804 ns/op +12.4% (worse)
Linux BenchmarkGoroutine 30342 ns/op -59.9% (better)
Linux BenchmarkInterfaceCall 8.791 ns/op +12.8% (worse)
Linux BenchmarkRuntimeGetG 1.762 ns/op -66.8% (better)
macOS BenchmarkLookupPCRandom 10.670 ns/op -14.2% (better)
macOS BenchmarkMergeCompilerFlags 114.800 ns/op -7.8% (better)
macOS BenchmarkMergeLinkerFlags 74.550 ns/op +7.5% (worse)
macOS BenchmarkChannelBuffered 23.610 ns/op -14.9% (better)
macOS BenchmarkChannelHandoff 9595 ns/op +36.9% (worse)
macOS BenchmarkDefer 30.520 ns/op -18.5% (better)
macOS BenchmarkDirectCall 1.078 ns/op -25.8% (better)
macOS BenchmarkGlobalRead 1.110 ns/op -8.3% (better)
macOS BenchmarkGlobalWrite 1.207 ns/op -18.8% (better)
macOS BenchmarkGoroutine 23018 ns/op -27.3% (better)
macOS BenchmarkInterfaceCall 4.625 ns/op -14.3% (better)
macOS BenchmarkRuntimeGetG 2.158 ns/op -16.3% (better)

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

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