Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ jobs:
with:
version: "4.0.21"

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

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

Expand All @@ -390,6 +395,21 @@ 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"
}

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
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"
file "$RUNNER_TEMP/runtime-js.wasm" "$RUNNER_TEMP/runtime-wasip1.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
27 changes: 15 additions & 12 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,6 @@ func resolveBuildConfig(input *Config) (*Config, 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 @@ -399,6 +396,9 @@ func Build(inv Invocation) ([]Package, error) {
if conf.Target != "" && export.GOARCH != "" {
conf.Goarch = export.GOARCH
}
if conf.AppExt == "" {
conf.AppExt = defaultAppExt(conf)
}
if err := validateLinkOptions(conf, &export); err != nil {
return nil, err
}
Expand Down Expand Up @@ -479,10 +479,7 @@ func Build(inv Invocation) ([]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 @@ -793,16 +790,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
26 changes: 25 additions & 1 deletion internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,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 @@ -342,6 +342,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
6 changes: 6 additions & 0 deletions internal/build/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ func defaultAppExt(conf *Config) string {
return ".so"
}
case BuildModeExe:
if conf.Goos == "js" && conf.OutFile != "" {
switch ext := filepath.Ext(conf.OutFile); ext {
case ".js", ".mjs":
return ext
}
}
// For executable mode, handle target-specific logic
if conf.Target != "" {
if strings.HasPrefix(conf.Target, "wasi") || strings.HasPrefix(conf.Target, "wasm") {
Expand Down
23 changes: 23 additions & 0 deletions internal/build/outputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,29 @@ func TestBuildOutFmtsWithTarget(t *testing.T) {
}
}

func TestDefaultAppExtJSExplicitGlueOutput(t *testing.T) {
tests := []struct {
out string
want string
}{
{out: "app.mjs", want: ".mjs"},
{out: "app.js", want: ".js"},
{out: "app.wasm", want: ".wasm"},
{want: ".wasm"},
}
for _, tt := range tests {
conf := &Config{
Goos: "js",
Goarch: "wasm",
BuildMode: BuildModeExe,
OutFile: tt.out,
}
if got := defaultAppExt(conf); got != tt.want {
t.Errorf("defaultAppExt(%q) = %q, want %q", tt.out, got, tt.want)
}
}
}

func TestBuildOutFmtsNativeTarget(t *testing.T) {
tests := []struct {
name string
Expand Down
37 changes: 24 additions & 13 deletions internal/build/source_patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,40 @@ import (
)

func TestWasmRuntimeSourcePatchTypeChecks(t *testing.T) {
for _, goos := range []string{"js", "wasip1"} {
t.Run(goos, func(t *testing.T) {
cfgEnv := append(os.Environ(), "GOOS="+goos, "GOARCH=wasm")
for _, test := range []struct {
name string
goos string
target string
buildFlags []string
}{
{name: "js Memory64", goos: "js"},
{name: "js wasm32 target", goos: "js", target: "wasm", buildFlags: []string{"-tags=tinygo.wasm"}},
{name: "WASI wasm32", goos: "wasip1"},
} {
t.Run(test.name, func(t *testing.T) {
cfgEnv := append(os.Environ(), "GOOS="+test.goos, "GOARCH=wasm")
goroot, goversion, err := env.GOROOTAndGOVERSIONWithEnv(cfgEnv)
if err != nil {
t.Fatal(err)
}
overlay, _, err := buildSourcePatchOverlayForGOROOT(nil, env.LLGoRuntimeDir(), goroot, sourcePatchBuildContext{
goos: goos,
goarch: "wasm",
goversion: goversion,
goos: test.goos,
goarch: "wasm",
goversion: goversion,
buildFlags: test.buildFlags,
})
if err != nil {
t.Fatal(err)
}

pkgs, err := packages.LoadEx(nil, func(types.Sizes, string, string) types.Sizes {
return &types.StdSizes{WordSize: 4, MaxAlign: 4}
pkgs, err := packages.LoadEx(nil, func(sizes types.Sizes, _ string, arch string) types.Sizes {
return effectiveTypeSizes(sizes, test.goos, arch, test.target)
}, &packages.Config{
Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile,
Env: cfgEnv,
Fset: token.NewFileSet(),
Overlay: overlay,
Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile,
Env: cfgEnv,
Fset: token.NewFileSet(),
Overlay: overlay,
BuildFlags: test.buildFlags,
}, "runtime")
if err != nil {
t.Fatal(err)
Expand All @@ -51,7 +62,7 @@ func TestWasmRuntimeSourcePatchTypeChecks(t *testing.T) {
}
if pkgs[0].IllTyped {
logPackageErrors(t, pkgs[0], make(map[string]bool))
t.Fatal("runtime did not type-check with wasm32 sizes")
t.Fatal("runtime did not type-check")
}
})
}
Expand Down
10 changes: 10 additions & 0 deletions internal/build/testdata/wasm-scheduler/abi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <stddef.h>
#include <stdlib.h>

size_t llgo_test_sizeof_long(void) {
return sizeof(long);
}

int llgo_test_scheduler_deadlock(void) {
return getenv("LLGO_WASM_SCHEDULER_DEADLOCK") != NULL;
}
11 changes: 11 additions & 0 deletions internal/build/testdata/wasm-scheduler/abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import _ "unsafe"

const LLGoFiles = "abi.c"

//go:linkname cLongSize C.llgo_test_sizeof_long
func cLongSize() uintptr

//go:linkname schedulerDeadlockMode C.llgo_test_scheduler_deadlock
func schedulerDeadlockMode() int32
Loading
Loading