Skip to content

[Based on #100] runtime/wasm: add experimental Go-style resumable core - #127

Closed
cpunion wants to merge 25 commits into
codex/fork-wasm-wasi-single-worker-20260801from
codex/fork-wasm-resumable-core-20260802
Closed

[Based on #100] runtime/wasm: add experimental Go-style resumable core#127
cpunion wants to merge 25 commits into
codex/fork-wasm-wasi-single-worker-20260801from
codex/fork-wasm-resumable-core-20260802

Conversation

@cpunion

@cpunion cpunion commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Depends on #100.

Tracks the K feasibility milestone in xgo-dev#2152.

Problem

The J32/J64/P1 single-worker backend in #100 preserves each goroutine through Asyncify. That is deployable, but every resident G carries stack/Asyncify state and every suspendable path depends on whole-module post-link rewriting.

This draft provides an opt-in compiler-owned resumable ABI so LLGo can measure and validate a non-Asyncify continuation backend against the same scheduler boundary. Without it, the proposal has no executable baseline for frame memory, dispatch cost, panic/defer behavior, or later GC/worker integration.

Design

LLGO_WASM_RESUME=1 selects the experiment for J32, J64, or P1. Default wasm, native, embedded, and explicit P1 pthread builds are unchanged.

  • ssa marks resumable Go functions and calls while retaining synchronous C/runtime boundaries.
  • internal/wasmresume inventories calls, plans persistent values, emits {parent, descriptor, pc} frames, and lowers direct/indirect calls to explicit state machines.
  • Generated descriptors carry resume entry, frame size/alignment, and defer-unwind metadata.
  • Function values keep LLGo's existing two-word layout and point to generated start entries when needed.
  • Each runtime Context owns a lazy segmented frame arena plus the active/returned frame chain. Normal dispatch is an O(1) PC switch with no parent-chain search.
  • Panic and Goexit use one scheduler catch and compiler-recorded defer-owner state; a resumable defer does not retain a native sigjmp_buf.
  • P1 runs only Binaryen exception translation (--translate-to-exnref); Asyncify is disabled.

The runtime core covers normal return, Gosched, park/ready, panic/defer/recover, Goexit, deadlock diagnostics, and single-worker G/M/P ownership. Blocking, timers, precise roots, safepoints, multi-worker STW, and complete toolchain acceptance intentionally remain in the next integration PR based on #125.

Scope

Independent delta over #100:

  • 25 ordered commits
  • 60 files
  • +6,627/-49
  • implementation: +3,021/-45
  • tests and CI: +3,606/-4

The lowering and runtime frame machinery are isolated in internal/wasmresume and runtime/internal/wasmresume; existing build and SSA packages only contain selection and integration hooks.

Validation

All local heavy work used GOMAXPROCS=2, GOMEMLIMIT=4GiB, and -p=1.

  • macOS arm64, Go 1.26.5, LLVM 19:
    • full go test ./internal/build: pass (1343.203s)
    • full go test ./ssa: pass; coverage 93.3%, all K SSA helpers 100%
    • internal/wasmresume: pass, coverage 94.7%
    • runtime/internal/wasmresume: pass, coverage 91.9%
    • full runtime module go test ./...: pass
  • J32 and J64 execute the scheduler fixture under Node; P1 validates with wasm-tools and executes under Wasmtime 39.0.1.
  • Go 1.26.5 and Go 1.24.11 both pass J32/J64/P1 normal execution and deadlock acceptance, including 5,000 goroutine lifecycles, park/ready, panic/recover, Goexit, and G/M/P state.
  • J32 passes -O0, -O3, ThinLTO, and FullLTO with the same normal/deadlock behavior.
  • Ubuntu 24.04/amd64, Go 1.26.5, LLVM 19.1.7, 4 GiB/2 CPU container: lowering, SSA K integration, build integration, and runtime frame/storage tests pass.
  • Default-path comparison against runtime/wasm: add WASI single-worker scheduler (based on xgo-dev #2192, #2208) #100:
    • J32/J64/P1 wasm binaries are byte-identical.
    • Cortex-M4 ELF is byte-identical.
    • native text/data sizes and symbols are identical; Mach-O differs only in generated UUID/build metadata (58 bytes).
  • Runtime helper microbenchmarks from the same logical K implementation: dispatch 3.346-3.369 ns/op; hot frame-arena allocate/release 3.873-3.918 ns/op; both 0 B/op, 0 allocs/op.

The full fork CI matrix passes on 49cb0c795, including both Go versions and J32/J64/P1 wasm-runtime lanes.

The old upstream experimental PR xgo-dev#2236 validated this logical delta before its base became stale. This fork draft rebuilds it exactly on the refreshed #100 stack; it does not add integration changes from the production Asyncify chain.

cpunion added 25 commits August 2, 2026 10:04
@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

49cb0c795041 | 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) 369.830 ms -10.3% (better) 1.456 ms -6.2% (better)
Linux fmtprintf 2217960 B +0.2% (worse) 3.540 s -8.2% (better) 2.650 ms -19.4% (better)
Linux println 71584 B -1.5% (better) 361.922 ms -9.5% (better) 1.671 ms -0.1% (better)
macOS cprintf 84672 B +0.0% 325.661 ms -21.4% (better) 2.392 ms -33.3% (better)
macOS fmtprintf 2361520 B +0.7% (worse) 2.781 s -29.4% (better) 17.701 ms -24.3% (better)
macOS println 125712 B -1.0% (better) 311.568 ms -24.9% (better) 3.262 ms -55.1% (better)
Core language and compiler benchmarks
Platform Benchmark ns/op vs main
Linux BenchmarkLookupPCRandom 13.680 ns/op +1.5% (worse)
Linux BenchmarkMergeCompilerFlags 160.500 ns/op +3.9% (worse)
Linux BenchmarkMergeLinkerFlags 103.500 ns/op +2.2% (worse)
Linux BenchmarkChannelBuffered 35.670 ns/op +2.6% (worse)
Linux BenchmarkChannelHandoff 29609 ns/op -7.6% (better)
Linux BenchmarkDefer 45.980 ns/op -18.3% (better)
Linux BenchmarkDirectCall 1.566 ns/op +0.2% (worse)
Linux BenchmarkGlobalRead 1.883 ns/op +20.7% (worse)
Linux BenchmarkGlobalWrite 2.487 ns/op -0.3% (better)
Linux BenchmarkGoroutine 32635 ns/op -56.8% (better)
Linux BenchmarkInterfaceCall 8.126 ns/op +4.3% (worse)
Linux BenchmarkRuntimeGetG 2.200 ns/op -58.5% (better)
macOS BenchmarkLookupPCRandom 10.860 ns/op -12.7% (better)
macOS BenchmarkMergeCompilerFlags 100.500 ns/op -19.3% (better)
macOS BenchmarkMergeLinkerFlags 63.710 ns/op -8.1% (better)
macOS BenchmarkChannelBuffered 24.170 ns/op -12.8% (better)
macOS BenchmarkChannelHandoff 11128 ns/op +58.8% (worse)
macOS BenchmarkDefer 33.980 ns/op -9.2% (better)
macOS BenchmarkDirectCall 1.272 ns/op -12.5% (better)
macOS BenchmarkGlobalRead 1.055 ns/op -12.9% (better)
macOS BenchmarkGlobalWrite 1.268 ns/op -14.7% (better)
macOS BenchmarkGoroutine 26718 ns/op -15.6% (better)
macOS BenchmarkInterfaceCall 5.346 ns/op -1.0% (better)
macOS BenchmarkRuntimeGetG 2.373 ns/op -7.9% (better)

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

Warning

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

@cpunion

cpunion commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #134, which rebases the resumable backend onto the complete H runtime baseline, adds the B/C/D/S/H integration, and passes the full fork CI matrix (39 required checks plus one expected release skip).

@cpunion cpunion closed this Aug 2, 2026
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