Skip to content

[Based on #2197] runtime/wasm: add experimental Go-style resumable ABI - #2236

Draft
cpunion wants to merge 40 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-resumable-abi-spike
Draft

[Based on #2197] runtime/wasm: add experimental Go-style resumable ABI#2236
cpunion wants to merge 40 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-resumable-abi-spike

Conversation

@cpunion

@cpunion cpunion commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Depends on #2197.

Tracks the K feasibility work in #2152.

Problem

The current J32/J64/P1 single-worker backend preserves each goroutine through Asyncify. That is deployable, but each G carries a separate stack and Asyncify save area, and every suspendable path depends on whole-module post-link rewriting.

This draft adds an opt-in Go-style resumable ABI so LLGo can evaluate a compiler-owned continuation backend against the same runtime scheduler. Without this work, the proposal has no executable non-Asyncify baseline for comparing per-G memory, dispatch cost, code size, panic/defer behavior, or future GC 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 definitions and actual Go calls. C/runtime boundaries remain synchronous leaves.
  • internal/wasmresume performs one post-SSA inventory/liveness/layout pass and lowers calls to explicit frame state machines.
  • Each generated frame starts with {parent, descriptor, pc}. Function descriptors carry resume entry, frame size/alignment, and optional defer-unwind metadata.
  • Direct calls allocate typed child frames; indirect calls and function values use generated start entries while retaining the existing two-word Go function-value layout.
  • Context owns top, returned, and a lazy segmented frame arena. Dispatch is an O(1) PC switch and does not search the parent chain on normal calls.
  • SuspendCurrent stores the continuation PC and returns to the scheduler without retaining the native operand stack.
  • Panic and Goexit use one scheduler catch plus compiler-recorded defer-owner metadata. Resumable defer frames do not carry a native sigjmp_buf; unwind drops child frames and redirects the owner to its generated defer state.
  • P1 keeps LLVM SjLj but runs only the exception-encoding post-link step (--translate-to-exnref); Asyncify remains disabled.

The runtime backend uses the existing G/M/P and run-queue boundary from #2197. It covers normal return, Gosched, park/ready, panic/recover, panic after suspension, Goexit, deadlock, and G/M/P ownership.

Scope

Logical K delta over the rebased #2197 implementation:

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

GitHub shows the stacked #2197 commits until that dependency merges.

This remains experimental. Blocking primitives, timers, precise suspended-G roots, safepoints, multi-worker scheduling, variadic indirect calls, generated inline allocation fast paths, LTO/debug acceptance, and an Asyncify comparison remain follow-up work.

Validation

Resource controls used locally: GOMAXPROCS=2, -p=1, Node old-space 1 GiB; Ubuntu containers were limited to 15 GiB/2 CPU (the local Docker VM enforced 7.75 GiB).

  • macOS arm64, Go 1.26.5, LLVM 19:
    • full go test ./ssa: pass, 92.5% statements with all new SSA K helpers at 100%
    • full go test ./internal/build: pass; K configure/main/post-link paths are 100%, lowering wrapper 100% in focused coverage
    • go test ./internal/wasmresume: pass, 94.7%
    • runtime go test ./internal/wasmresume: pass, 91.9%; Context.Unwind 100%
  • J32 and J64 execute the complete wasm scheduler fixture under Node with a 1 GiB heap cap.
  • P1 validates with wasm-tools --features all and executes under Wasmtime 39.0.1.
  • All three profiles pass normal execution and deadlock acceptance, including 5,000 goroutine lifecycles, park/ready, panic/recover, suspension followed by panic, Goexit, and G/M/P state.
  • Post-rebase Apple M4 Max native helper microbenchmarks: 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. These isolate runtime bookkeeping rather than end-to-end wasm switch latency.
  • Ubuntu 24.04/amd64, Go 1.26.5, LLVM 19, 2 CPU/resource-limited container: core lowering, SSA K integration, build K integration, and runtime frame/storage tests pass.
  • Rebase onto xgo-dev/main at 4b2c78ede followed by full macOS ssa and internal/build reruns: pass; peak RSS about 1.78 GiB with no swap.
  • GitHub CI for c7ffc2cd9 passes every required Ubuntu/macOS check, Go 1.24/1.26 lane, J32/J64/P1 wasm-runtime lane, release artifact test, benchmark, and codecov/patch. The PR-only release publication step is skipped as designed.

cpunion added 30 commits July 30, 2026 23:43
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