Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
65d1b25
runtime/wasm: add single-worker Asyncify scheduler
cpunion Jul 27, 2026
822b4da
test(runtime): exercise wasm scheduler in Node
cpunion Jul 27, 2026
46f9a1d
build/wasm: distinguish Go Memory64 from wasm32 target
cpunion Jul 27, 2026
a4957d4
test(crosscompile): cover wasm target setup errors
cpunion Jul 27, 2026
0b99f6f
runtime/wasm: make scheduler invariant failures fatal
cpunion Jul 27, 2026
48a896d
runtime/wasm: add WASI single-worker scheduler
cpunion Jul 27, 2026
a2ff09a
test(runtime): exercise WASI Asyncify scheduler
cpunion Jul 27, 2026
a9df873
fix(runtime/wasm): initialize scheduler for minimal P1 mains
cpunion Jul 27, 2026
e7b44d9
ci: install Binaryen for wasm cache tests
cpunion Jul 27, 2026
874c18e
runtime/wasm: keep fiber host calls out of method roots
cpunion Jul 27, 2026
5b3a49d
build/wasm: centralize post-link output lifecycle
cpunion Jul 28, 2026
e06a7b4
ssa/wasm: use static defer continuation dispatch
cpunion Jul 28, 2026
4abf7ed
runtime/wasm: isolate scheduler state from native Gs
cpunion Jul 29, 2026
728ca07
runtime/wasm: encapsulate continuation storage
cpunion Jul 29, 2026
951475b
runtime/wasm: preserve explicit WASI thread fatal behavior
cpunion Jul 30, 2026
247ca99
runtime/wasm: define resumable frame dispatch contract
cpunion Jul 30, 2026
f3acf70
ssa/wasm: inventory resumable Go calls
cpunion Jul 30, 2026
f46e8c9
internal/wasmresume: number generated Go calls
cpunion Jul 30, 2026
645e814
internal/wasmresume: plan persistent frame values
cpunion Jul 30, 2026
bc51e9a
runtime/wasm: retain completed resume frames
cpunion Jul 30, 2026
51f54e7
internal/wasmresume: lay out persistent frames
cpunion Jul 30, 2026
fda516a
runtime/wasm: define generated frame descriptors
cpunion Jul 30, 2026
83197ce
internal/wasmresume: emit leaf resume entries
cpunion Jul 30, 2026
0922d33
internal/wasmresume: split call continuations safely
cpunion Jul 30, 2026
dfb14ba
internal/wasmresume: canonicalize live values in frames
cpunion Jul 30, 2026
98f9b11
internal/wasmresume: lower direct resumable calls
cpunion Jul 30, 2026
3533954
internal/wasmresume: exercise nested direct resumes
cpunion Jul 30, 2026
0e1f84e
internal/wasmresume: link descriptors across packages
cpunion Jul 30, 2026
6f53fe3
internal/wasmresume: lower indirect resumable calls
cpunion Jul 30, 2026
64e72cd
ssa/wasm: route function values through resume entries
cpunion Jul 30, 2026
bc42981
wasmresume: lower scheduler suspension points
cpunion Jul 30, 2026
9c7c291
runtime/wasm: scope resume frame storage per context
cpunion Jul 30, 2026
b6df811
internal/wasmresume: execute required wasm profiles
cpunion Jul 30, 2026
ad87eef
wasmresume: preserve synchronous runtime boundaries
cpunion Jul 30, 2026
6aaf878
internal/build: separate wasm exception translation from Asyncify
cpunion Jul 30, 2026
8efa13a
wasmresume: complete persistent frame lifecycle
cpunion Jul 30, 2026
fdc5dcb
ssa/wasm: emit resumable goroutine and defer state
cpunion Jul 30, 2026
30317bc
runtime/wasm: schedule resumable goroutines
cpunion Jul 30, 2026
622a896
ci/wasm: exercise resumable scheduler profiles
cpunion Jul 30, 2026
c7ffc2c
test/wasmresume: tolerate Node without table64
cpunion Jul 30, 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 @@ -33,6 +33,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
68 changes: 67 additions & 1 deletion .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,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 @@ -412,6 +415,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 @@ -428,6 +449,51 @@ 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"
}

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-runtime
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"
LLGO_WASM_RESUME=1 GOOS=js GOARCH=wasm llgo build \
-o "$RUNNER_TEMP/wasm-resume-scheduler-go.mjs" ./internal/build/testdata/wasm-scheduler
run_wasm_scheduler "$RUNNER_TEMP/wasm-resume-scheduler-go.mjs"
LLGO_WASM_RESUME=1 llgo build -target wasm \
-o "$RUNNER_TEMP/wasm-resume-scheduler.mjs" ./internal/build/testdata/wasm-scheduler
run_wasm_scheduler "$RUNNER_TEMP/wasm-resume-scheduler.mjs"
LLGO_WASM_RESUME=1 GOOS=wasip1 GOARCH=wasm llgo build \
-o "$RUNNER_TEMP/wasm-resume-scheduler-wasip1.wasm" ./internal/build/testdata/wasm-scheduler
run_wasi_scheduler "$RUNNER_TEMP/wasm-resume-scheduler-wasip1.wasm"
file "$RUNNER_TEMP/runtime-js.wasm" \
"$RUNNER_TEMP/runtime-wasip1.wasm" \
"$RUNNER_TEMP/runtime-wasip1-threads.wasm"
5 changes: 5 additions & 0 deletions .github/workflows/targets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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
53 changes: 37 additions & 16 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,6 @@ func Do(args []string, conf *Config) ([]Package, error) {
if conf.Goarch == "" {
conf.Goarch = runtime.GOARCH
}
if conf.AppExt == "" {
conf.AppExt = defaultAppExt(conf)
}
if conf.BuildMode == "" {
conf.BuildMode = BuildModeExe
}
Expand Down Expand Up @@ -339,6 +336,12 @@ func Do(args []string, conf *Config) ([]Package, error) {
if conf.Target != "" && export.GOARCH != "" {
conf.Goarch = export.GOARCH
}
if err := configureWasmResume(conf, &export); err != nil {
return nil, err
}
if conf.AppExt == "" {
conf.AppExt = defaultAppExt(conf)
}
if err := validateLinkOptions(conf, &export); err != nil {
return nil, err
}
Expand Down Expand Up @@ -393,6 +396,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
}

prog := llssa.NewProgram(target)
prog.EnableWasmResumeABI(IsWasmResumeEnabled())
prog.DisableBoundsChecks(conf.DisableBoundsChecks)
if conf.Mode != ModeGen {
// ModeGen callers (llgen and the golden suites) read LPkg.String()
Expand All @@ -418,10 +422,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
// final-PC sites for sidecar construction.
prog.EnableFuncInfoSites(shouldEnablePCLNSites(conf, funcInfo, emitDebugInfo))
sizes := func(sizes types.Sizes, compiler, arch string) types.Sizes {
if arch == "wasm" {
sizes = &types.StdSizes{WordSize: 4, MaxAlign: 4}
}
return prog.TypeSizes(sizes)
return prog.TypeSizes(effectiveTypeSizes(sizes, conf.Goos, arch, conf.Target))
}
dedup := packages.NewDeduper()
var syntaxErr error
Expand Down Expand Up @@ -727,16 +728,22 @@ func DefaultBuildTags(goarch, target string) string {

func defaultBuildTags(goarch, target string) string {
tags := "llgo,math_big_pure_go,purego"
// Raw GOOS/GOARCH wasm builds do not have a target configuration that
// selects a collector. BDWGC is not available in either wasm host, so use
// the supported collector-free runtime unless a named target supplies its
// own runtime configuration.
if goarch == "wasm" && target == "" {
// BDWGC is unavailable in both wasm hosts.
if goarch == "wasm" {
tags += ",nogc"
}
return tags
}

func effectiveTypeSizes(sizes types.Sizes, goos, goarch, target string) types.Sizes {
// Named wasm targets use the native wasm32 data model. The raw js/wasm
// entry point keeps Go's 64-bit word model and is emitted as Memory64.
if goarch == "wasm" && (target != "" || goos != "js") {
return &types.StdSizes{WordSize: 4, MaxAlign: 4}
}
return sizes
}

func allowMissingFunctionBodies(initial []*packages.Package) {
for _, pkg := range initial {
hasMissingBody := false
Expand Down Expand Up @@ -1327,6 +1334,9 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa
pcLineInfo: pcLineInfo,
funcInfoStubs: funcInfoStubs,
})
if err := lowerWasmResumeModule(ctx, entryPkg.LPkg.Module()); err != nil {
return fmt.Errorf("entry main: %w", err)
}
entryObjFile, err := exportObject(ctx, "entry_main", entryPkg.ExportFile, entryPkg.LPkg)
if err != nil {
return err
Expand Down Expand Up @@ -1359,12 +1369,15 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa
}
linkArgs = append(linkArgs, cSharedExportArgs(ctx, linkedOrder)...)

err = linkObjFiles(ctx, outputPath, linkInputs, linkArgs, verbose)
linkOutput, err := prepareWasmLinkOutput(ctx.buildConf, &ctx.crossCompile, outputPath)
if err != nil {
return err
}

return nil
defer cleanupWasmLinkOutput(linkOutput, outputPath)
if err := linkObjFiles(ctx, linkOutput, linkInputs, linkArgs, verbose); err != nil {
return err
}
return publishWasmLinkOutput(ctx, linkOutput, outputPath, verbose)
}

func linkedModuleGlobals(pkgs []Package) map[string]none {
Expand Down Expand Up @@ -1722,6 +1735,9 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
return nil
}

if err := lowerWasmResumeModule(ctx, ret.Module()); err != nil {
return fmt.Errorf("%s: %w", pkgPath, err)
}
ctx.cTransformer.SetSkipFuncs(cabiSkipFuncsForPlan9Asm(ctx, pkgPath, ret.Module()))
llabi.LowerLargeAggregates(ctx.prog.TargetData(), ret.Module())
ctx.cTransformer.TransformModule(ret.Path(), ret.Module())
Expand Down Expand Up @@ -2282,6 +2298,7 @@ const llgoFuncInfoSites = "LLGO_FUNCINFO_SITES"
const llgoTrace = "LLGO_TRACE"
const llgoOptimize = "LLGO_OPTIMIZE"
const llgoWasmRuntime = "LLGO_WASM_RUNTIME"
const llgoWasmResume = "LLGO_WASM_RESUME"
const llgoWasiThreads = "LLGO_WASI_THREADS"
const llgoStdioNobuf = "LLGO_STDIO_NOBUF"
const llgoFullRpath = "LLGO_FULL_RPATH"
Expand Down Expand Up @@ -2360,7 +2377,11 @@ func llvmPassPipeline(level optlevel.Level, ltoMode lto.Mode) string {
}

func IsWasiThreadsEnabled() bool {
return isEnvOn(llgoWasiThreads, true)
return isEnvOn(llgoWasiThreads, false)
}

func IsWasmResumeEnabled() bool {
return isEnvOn(llgoWasmResume, false)
}

func IsFullRpathEnabled() bool {
Expand Down
37 changes: 36 additions & 1 deletion internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestDefaultBuildTags(t *testing.T) {
}{
{name: "native", goarch: "arm64", want: base},
{name: "raw wasm", goarch: "wasm", want: base + ",nogc"},
{name: "configured wasm target", goarch: "wasm", target: "wasip1", want: base},
{name: "configured wasm target", goarch: "wasm", target: "wasip1", want: base + ",nogc"},
} {
t.Run(test.name, func(t *testing.T) {
if got := defaultBuildTags(test.goarch, test.target); got != test.want {
Expand All @@ -119,6 +119,30 @@ func TestDefaultBuildTags(t *testing.T) {
}
}

func TestEffectiveWasmTypeSizes(t *testing.T) {
goSizes := types.SizesFor("gc", "wasm")
for _, test := range []struct {
name string
goos string
target string
want int64
}{
{name: "Go js wasm", goos: "js", want: 8},
{name: "configured wasm", goos: "js", target: "wasm", want: 4},
{name: "WASI compatibility", goos: "wasip1", want: 4},
} {
t.Run(test.name, func(t *testing.T) {
got := effectiveTypeSizes(goSizes, test.goos, "wasm", test.target)
if size := got.Sizeof(types.Typ[types.Uintptr]); size != test.want {
t.Fatalf("uintptr size = %d, want %d", size, test.want)
}
})
}
if got := effectiveTypeSizes(goSizes, "linux", "amd64", ""); got != goSizes {
t.Fatal("native type sizes changed")
}
}

func TestWasmRuntimeAvoidsNativeHostDependencies(t *testing.T) {
runtimeDir := filepath.Join(env.LLGoRuntimeDir(), "internal", "lib", "runtime")
for _, goos := range []string{"js", "wasip1"} {
Expand Down Expand Up @@ -836,6 +860,17 @@ func TestApplyBuildModeCompileFlags(t *testing.T) {
applyBuildModeCompileFlags(BuildModeCShared, nil)
}

func TestWASIThreadsAreOptIn(t *testing.T) {
t.Setenv(llgoWasiThreads, "")
if IsWasiThreadsEnabled() {
t.Fatal("WASI threads are enabled by default")
}
t.Setenv(llgoWasiThreads, "1")
if !IsWasiThreadsEnabled() {
t.Fatal("WASI threads opt-in was ignored")
}
}

func TestCHeaderPackagesExcludesStandardRuntime(t *testing.T) {
prog := llssa.NewProgram(nil)
defer prog.Dispose()
Expand Down
1 change: 1 addition & 0 deletions internal/build/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (c *context) collectEnvInputs(m *manifestBuilder) {
llgoTrace,
llgoOptimize,
llgoWasmRuntime,
llgoWasmResume,
llgoWasiThreads,
llgoStdioNobuf,
llgoFullRpath,
Expand Down
Loading
Loading