Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
32670f9
runtime/wasm: add single-worker Asyncify scheduler
cpunion Jul 27, 2026
8b69b75
test(runtime): exercise wasm scheduler in Node
cpunion Jul 27, 2026
fe0d36d
build/wasm: distinguish Go Memory64 from wasm32 target
cpunion Jul 27, 2026
76f2b78
test(crosscompile): cover wasm target setup errors
cpunion Jul 27, 2026
0e631e5
runtime/wasm: make scheduler invariant failures fatal
cpunion Jul 27, 2026
6c7c3a7
runtime/wasm: add WASI single-worker scheduler
cpunion Jul 27, 2026
dfe6bcb
test(runtime): exercise WASI Asyncify scheduler
cpunion Jul 27, 2026
2cdb3cb
fix(runtime/wasm): initialize scheduler for minimal P1 mains
cpunion Jul 27, 2026
16feeaa
ci: install Binaryen for wasm cache tests
cpunion Jul 27, 2026
e3e8ea8
runtime/wasm: keep fiber host calls out of method roots
cpunion Jul 27, 2026
d3dc940
runtime: support sync source patches with Go 1.24
cpunion Jul 27, 2026
61a5252
runtime/pthread: correct attribute ABI signatures
cpunion Jul 27, 2026
d49f8f9
runtime/wasm: add blocking primitive scheduler hooks
cpunion Jul 27, 2026
c45e243
test(runtime): cover wasm blocking primitives
cpunion Jul 27, 2026
22f3cef
runtime: isolate non-moving GC platform hooks
cpunion Jul 28, 2026
f362bdf
runtime/wasm: add opt-in non-moving collector
cpunion Jul 28, 2026
1dfae34
ci: exercise opt-in wasm collector
cpunion Jul 28, 2026
abbc8b7
test: size wasm heap growth probe dynamically
cpunion Jul 28, 2026
8bb5f64
build/wasm: reject GC with threaded WASI
cpunion Jul 28, 2026
b7e6cfa
ci: select single-worker P1 for wasm GC
cpunion Jul 28, 2026
64cef9a
runtime: preserve ReadMemStats nil panic
cpunion Jul 28, 2026
3af38d6
runtime/wasm: add host event timer queue
cpunion Jul 28, 2026
3e775cd
ssa/wasm: use static defer continuation dispatch
cpunion Jul 28, 2026
fe3dc83
cl/wasm: lower go:wasmimport directives
cpunion Jul 28, 2026
fa2728b
runtime/wasm: update host runtime contracts
cpunion Jul 28, 2026
8c0ec5f
runtime/wasm: drive timers from host wakeups
cpunion Jul 28, 2026
cf0377e
build/wasm: centralize post-link output lifecycle
cpunion Jul 28, 2026
e3ed3c3
Merge branch 'codex/wasm-wasi-single-worker' into codex/wasm-blocking…
cpunion Jul 28, 2026
45ef9c9
Merge branch 'codex/wasm-blocking-primitives' into codex/wasm-timers-…
cpunion Jul 28, 2026
95fb9a7
ci: allow wasm runtime macOS tests to finish
cpunion Jul 28, 2026
7261611
Merge branch 'codex/wasm-single-worker-asyncify-scheduler' into codex…
cpunion Jul 28, 2026
3f31ec5
Merge branch 'codex/wasm-single-worker-asyncify-scheduler' into codex…
cpunion Jul 28, 2026
e1368d4
Merge branch 'codex/wasm-wasi-single-worker' into codex/wasm-blocking…
cpunion Jul 28, 2026
13ce085
Merge branch 'codex/wasm-blocking-primitives' into codex/wasm-timers-…
cpunion Jul 28, 2026
2724adb
Merge remote-tracking branch 'origin/codex/tinygogc-wasm' into codex/…
cpunion Jul 28, 2026
4080b93
ssa: add explicit compiler GC root frames
cpunion Jul 28, 2026
14b09ed
cl: publish safepoint-live wasm GC roots
cpunion Jul 28, 2026
7571bad
runtime/wasm: preserve roots across scheduler contexts
cpunion Jul 28, 2026
574ffd5
runtime/wasm: restore GC roots across panic unwinds
cpunion Jul 28, 2026
bfa7c15
test/wasm: cover suspended and recovered GC roots
cpunion Jul 28, 2026
b2481ee
Merge remote-tracking branch 'upstream/main' into codex/wasm-single-w…
cpunion Jul 28, 2026
10b459e
Merge branch 'codex/wasm-single-worker-asyncify-scheduler' into codex…
cpunion Jul 28, 2026
fcbd964
Merge branch 'codex/wasm-wasi-single-worker' into codex/wasm-blocking…
cpunion Jul 28, 2026
669f46f
test(build): adapt wasm sync patch coverage
cpunion Jul 28, 2026
45a4c50
Merge branch 'codex/wasm-single-worker-asyncify-scheduler' into codex…
cpunion Jul 28, 2026
0ac88a0
Merge branch 'codex/wasm-wasi-single-worker' into codex/tinygogc-wasm
cpunion Jul 29, 2026
d0fd0db
Merge branch 'codex/wasm-blocking-primitives' into codex/wasm-timers-…
cpunion Jul 29, 2026
75cb04b
Merge branch 'codex/wasm-blocking-primitives' into codex/wasm-safepoi…
cpunion Jul 29, 2026
07ea95f
Merge branch 'codex/tinygogc-wasm' into codex/wasm-safepoints-roots-v2
cpunion Jul 29, 2026
c7d37eb
Merge remote-tracking branch 'upstream/main' into codex/wasm-single-w…
cpunion Jul 29, 2026
1b9ad7a
Merge branch 'codex/wasm-single-worker-asyncify-scheduler' into codex…
cpunion Jul 29, 2026
93eb05e
ssa/wasm: use static defer continuation dispatch
cpunion Jul 28, 2026
0da6714
Merge branch 'codex/wasm-static-defer-dispatch' into codex/wasm-wasi-…
cpunion Jul 29, 2026
cbbb130
Merge branch 'codex/wasm-wasi-single-worker' into codex/wasm-blocking…
cpunion Jul 29, 2026
3345423
Merge branch 'codex/wasm-wasi-single-worker' into codex/tinygogc-wasm
cpunion Jul 29, 2026
3832c73
Merge branch 'codex/wasm-blocking-primitives' into codex/wasm-timers-…
cpunion Jul 29, 2026
b9e1386
Merge branch 'codex/wasm-blocking-primitives' into codex/wasm-safepoi…
cpunion Jul 29, 2026
d379f27
Merge branch 'codex/tinygogc-wasm' into codex/wasm-safepoints-roots-v2
cpunion Jul 29, 2026
1b0163e
Merge #2204 timers into cooperative safepoint base
cpunion Jul 29, 2026
fe053d0
ssa: apply type patches to nested aggregate fields
cpunion Jul 29, 2026
6262d0b
compiler: add cooperative safepoint polls
cpunion Jul 29, 2026
9632927
runtime/wasm: schedule through bounded safepoint polls
cpunion Jul 29, 2026
cc6daa4
build: enable GC for single-worker wasm by default
cpunion Jul 29, 2026
0ba9981
runtime/wasm: add bounded Web worker scheduling
cpunion Jul 29, 2026
42b3a89
test/wasm: stress bounded worker execution
cpunion Jul 29, 2026
ce2b187
compiler/wasm: make GC root ownership thread-local
cpunion Jul 29, 2026
caf3667
runtime/wasm: stop all workers for garbage collection
cpunion Jul 29, 2026
579f158
test/wasm: cover multi-worker garbage collection
cpunion Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/actions/setup-binaryen/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: "Setup Binaryen"
description: "Install a pinned Binaryen release"
inputs:
version:
description: "Binaryen release version"
required: false
default: "131"

runs:
using: "composite"
steps:
- name: Install Binaryen
shell: bash
run: |
set -euo pipefail

version="${{ inputs.version }}"
case "$(uname -s):$(uname -m)" in
Linux:x86_64) platform="x86_64-linux" ;;
Linux:aarch64|Linux:arm64) platform="aarch64-linux" ;;
Darwin:x86_64) platform="x86_64-macos" ;;
Darwin:arm64) platform="arm64-macos" ;;
*)
echo "Unsupported Binaryen host: $(uname -s) $(uname -m)" >&2
exit 1
;;
esac

archive="binaryen-version_${version}-${platform}.tar.gz"
base_url="https://github.com/WebAssembly/binaryen/releases/download/version_${version}"
cd "$RUNNER_TEMP"
curl --retry 3 --retry-all-errors -fsSLO "${base_url}/${archive}"
curl --retry 3 --retry-all-errors -fsSLO "${base_url}/${archive}.sha256"
if command -v sha256sum >/dev/null; then
sha256sum --check "${archive}.sha256"
else
shasum -a 256 --check "${archive}.sha256"
fi
tar -xzf "$archive" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/binaryen-version_${version}/bin" >> "$GITHUB_PATH"
3 changes: 3 additions & 0 deletions .github/workflows/build-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
- name: Set up Go
uses: ./.github/actions/setup-go

- name: Set up Binaryen
uses: ./.github/actions/setup-binaryen

- name: Install wamr (for wasm tests)
if: startsWith(matrix.os, 'macos')
run: |
Expand Down
132 changes: 130 additions & 2 deletions .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ jobs:
test:
name: test (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }}, shard ${{ matrix.shard }})
continue-on-error: ${{ matrix.lane == 'compatibility' }}
timeout-minutes: 30
# macOS runs the full package set in one shard and is normally just over
# 30 minutes once the wasm runtime sources are present.
timeout-minutes: 40
strategy:
matrix:
# Keep compatibility and primary toolchains pinned to exact patches.
Expand Down Expand Up @@ -366,6 +368,9 @@ jobs:
- name: Set up Go for building llgo
uses: ./.github/actions/setup-go

- name: Set up Binaryen
uses: ./.github/actions/setup-binaryen

- name: Install wamr
run: |
git clone --branch WAMR-2.4.4 --depth 1 https://github.com/bytecodealliance/wasm-micro-runtime.git
Expand Down Expand Up @@ -414,6 +419,24 @@ jobs:
with:
version: "4.0.21"

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: "25"

- name: Set up Binaryen
uses: ./.github/actions/setup-binaryen

- name: Set up Wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "39.0.1"

- name: Set up wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
with:
version: "1.243.0"

- name: Set up Go for building llgo
uses: ./.github/actions/setup-go

Expand All @@ -430,6 +453,111 @@ jobs:
- name: Build standard runtime for wasm
shell: bash
run: |
run_wasm_scheduler() {
local module="$1"
local output
node --input-type=module -e "import Module from '$module'; await Module();"
if output=$(node --input-type=module -e "import Module from '$module'; await Module({preRun: [module => { module.ENV.LLGO_WASM_SCHEDULER_DEADLOCK = '1'; }]});" 2>&1); then
echo "deadlock scheduler fixture unexpectedly succeeded"
return 1
fi
grep -Fq "fatal error: all goroutines are asleep - deadlock!" <<<"$output"
}

run_wasi_scheduler() {
local module="$1"
local output
wasm-tools validate --features all "$module"
output=$(wasmtime run -W exceptions=y "$module" 2>&1)
grep -Fq "wasm scheduler ok" <<<"$output"
if output=$(wasmtime run -W exceptions=y \
--env LLGO_WASM_SCHEDULER_DEADLOCK=1 "$module" 2>&1); then
echo "deadlock scheduler fixture unexpectedly succeeded"
return 1
fi
grep -Fq "fatal error: all goroutines are asleep - deadlock!" <<<"$output"
}

run_wasm_timers() {
local output
output=$(node --input-type=module -e "import Module from '$1'; await Module();" 2>&1)
test "$output" = "wasm timers ok"
}

run_wasm_workers() {
local expected="$2"
local output
output=$(node -e "import('$1').then(module => module.default()).catch(error => { console.error(error); process.exit(1); });" 2>&1)
grep -Fxq "$expected" <<<"$output"
}

run_wasi_timers() {
local output
wasm-tools validate --features all "$1"
output=$(wasmtime run -W exceptions=y "$1" 2>&1)
test "$output" = "wasm timers ok"
}

GOOS=js GOARCH=wasm llgo build -o "$RUNNER_TEMP/runtime-js.wasm" ./internal/build/testdata/wasm-runtime
GOOS=wasip1 GOARCH=wasm llgo build -o "$RUNNER_TEMP/runtime-wasip1.wasm" ./internal/build/testdata/wasm-runtime
file "$RUNNER_TEMP/runtime-js.wasm" "$RUNNER_TEMP/runtime-wasip1.wasm"
LLGO_WASI_THREADS=1 GOOS=wasip1 GOARCH=wasm llgo build \
-o "$RUNNER_TEMP/runtime-wasip1-threads.wasm" ./internal/build/testdata/wasm-blocking
test "$(wasmtime run -W exceptions=y "$RUNNER_TEMP/runtime-wasip1.wasm" 2>&1)" = "wasip1"
GOOS=js GOARCH=wasm llgo build -o "$RUNNER_TEMP/wasm-scheduler-go.mjs" ./internal/build/testdata/wasm-scheduler
run_wasm_scheduler "$RUNNER_TEMP/wasm-scheduler-go.mjs"
llgo build -target wasm -o "$RUNNER_TEMP/wasm-scheduler.mjs" ./internal/build/testdata/wasm-scheduler
run_wasm_scheduler "$RUNNER_TEMP/wasm-scheduler.mjs"
GOOS=wasip1 GOARCH=wasm llgo build -o "$RUNNER_TEMP/wasm-scheduler-wasip1.wasm" ./internal/build/testdata/wasm-scheduler
run_wasi_scheduler "$RUNNER_TEMP/wasm-scheduler-wasip1.wasm"
GOOS=js GOARCH=wasm llgo build -o "$RUNNER_TEMP/wasm-timers-go.mjs" ./internal/build/testdata/wasm-timers
run_wasm_timers "$RUNNER_TEMP/wasm-timers-go.mjs"
llgo build -target wasm -o "$RUNNER_TEMP/wasm-timers.mjs" ./internal/build/testdata/wasm-timers
run_wasm_timers "$RUNNER_TEMP/wasm-timers.mjs"
GOOS=wasip1 GOARCH=wasm llgo build -o "$RUNNER_TEMP/wasm-timers-wasip1.wasm" ./internal/build/testdata/wasm-timers
run_wasi_timers "$RUNNER_TEMP/wasm-timers-wasip1.wasm"
LLGO_WASM_WORKERS=2 GOOS=js GOARCH=wasm llgo build \
-o "$RUNNER_TEMP/wasm-workers-go.mjs" ./internal/build/testdata/wasm-workers
run_wasm_workers "$RUNNER_TEMP/wasm-workers-go.mjs" "wasm workers ok"
LLGO_WASM_WORKERS=2 llgo build -target wasm \
-o "$RUNNER_TEMP/wasm-workers.mjs" ./internal/build/testdata/wasm-workers
run_wasm_workers "$RUNNER_TEMP/wasm-workers.mjs" "wasm workers ok"
LLGO_WASM_WORKERS=2 GOOS=js GOARCH=wasm llgo build \
-o "$RUNNER_TEMP/wasm-gc-workers-go.mjs" ./internal/build/testdata/wasm-gc
run_wasm_workers "$RUNNER_TEMP/wasm-gc-workers-go.mjs" "wasm gc ok"
LLGO_WASM_WORKERS=2 llgo build -target wasm \
-o "$RUNNER_TEMP/wasm-gc-workers.mjs" ./internal/build/testdata/wasm-gc
run_wasm_workers "$RUNNER_TEMP/wasm-gc-workers.mjs" "wasm gc ok"
cp ./internal/build/testdata/wasm-workers/browser.html "$RUNNER_TEMP/browser.html"
node ./internal/build/testdata/wasm-workers/server.mjs "$RUNNER_TEMP" 8123 &
browser_server=$!
trap 'kill "$browser_server" 2>/dev/null || true' EXIT
for attempt in {1..50}; do
if curl -fsS http://127.0.0.1:8123/ >/dev/null; then
break
fi
sleep 0.1
done
curl -fsS http://127.0.0.1:8123/ >/dev/null
browser="$(command -v google-chrome || command -v google-chrome-stable || command -v chromium || true)"
test -n "$browser"
for module in wasm-timers.mjs wasm-workers.mjs wasm-workers-go.mjs \
wasm-gc-workers.mjs wasm-gc-workers-go.mjs; do
html=$("$browser" --headless=new --no-sandbox --disable-gpu \
--disable-dev-shm-usage --virtual-time-budget=15000 --dump-dom \
"http://127.0.0.1:8123/browser.html?module=$module")
grep -Fq 'data-result="pass"' <<<"$html"
done
kill "$browser_server"
wait "$browser_server" || true
trap - EXIT
GOOS=js GOARCH=wasm llgo build -o "$RUNNER_TEMP/wasm-gc-go.mjs" ./internal/build/testdata/wasm-gc
node --input-type=module -e "import Module from '$RUNNER_TEMP/wasm-gc-go.mjs'; await Module();"
llgo build -target wasm -o "$RUNNER_TEMP/wasm-gc.mjs" ./internal/build/testdata/wasm-gc
node --input-type=module -e "import Module from '$RUNNER_TEMP/wasm-gc.mjs'; await Module();"
GOOS=wasip1 GOARCH=wasm LLGO_WASI_THREADS=0 llgo build -o "$RUNNER_TEMP/wasm-gc-wasip1.wasm" ./internal/build/testdata/wasm-gc
wasm-tools validate --features all "$RUNNER_TEMP/wasm-gc-wasip1.wasm"
test "$(wasmtime run -W exceptions=y "$RUNNER_TEMP/wasm-gc-wasip1.wasm" 2>&1)" = "wasm gc ok"
file "$RUNNER_TEMP/runtime-js.wasm" \
"$RUNNER_TEMP/runtime-wasip1.wasm" \
"$RUNNER_TEMP/runtime-wasip1-threads.wasm" \
"$RUNNER_TEMP/wasm-gc-wasip1.wasm"
5 changes: 5 additions & 0 deletions .github/workflows/targets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ jobs:
with:
llvm-version: ${{matrix.llvm}}

- name: Set up Emscripten
uses: emscripten-core/setup-emsdk@v15
with:
version: "4.0.21"

- name: Set up Go for build
uses: ./.github/actions/setup-go

Expand Down
86 changes: 86 additions & 0 deletions benchmark/wasm_workers/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"sync"
"time"
)

const (
lifecycleIterations = 10_000
channelIterations = 100_000
cpuIterations = 20_000_000
)

var cpuResult uint64

func main() {
report("WasmGoroutineLifecycle", lifecycleIterations, benchmarkLifecycle())
report("WasmChannelRoundTrip", channelIterations, benchmarkChannel())
cpuResult = cpuWork(0)
report("WasmOneCPUJob", 1, benchmarkCPUJobs(1))
report("WasmTwoCPUJobs", 2, benchmarkCPUJobs(2))
if cpuResult == 0 {
panic("unexpected CPU benchmark result")
}
}

func benchmarkLifecycle() time.Duration {
done := make(chan struct{}, 1)
start := time.Now()
for range lifecycleIterations {
go func() {
done <- struct{}{}
}()
<-done
}
return time.Since(start)
}

func benchmarkChannel() time.Duration {
request := make(chan struct{})
response := make(chan struct{})
go func() {
for range channelIterations {
<-request
response <- struct{}{}
}
}()

start := time.Now()
for range channelIterations {
request <- struct{}{}
<-response
}
return time.Since(start)
}

func benchmarkCPUJobs(jobs int) time.Duration {
var wg sync.WaitGroup
results := make([]uint64, jobs)
wg.Add(jobs)
start := time.Now()
for i := range results {
go func() {
results[i] = cpuWork(uint64(i + 1))
wg.Done()
}()
}
wg.Wait()
cpuResult = 0
for _, result := range results {
cpuResult ^= result
}
return time.Since(start)
}

//go:noinline
func cpuWork(value uint64) uint64 {
for i := uint64(0); i < cpuIterations; i++ {
value = value*1664525 + 1013904223 + i
}
return value
}

func report(name string, iterations int, elapsed time.Duration) {
println("Benchmark"+name+"-1", iterations, elapsed.Nanoseconds()/int64(iterations), "ns/op")
}
Loading
Loading