diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index 3e998d6b3b..275da6b2d3 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -380,3 +380,42 @@ jobs: # Run the wasm binary using llgo_wasm iwasm --stack-size=819200000 --heap-size=800000000 hello.wasm + + wasm-runtime: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + go: ["1.24.2", "1.26.5"] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Install dependencies + uses: ./.github/actions/setup-deps + with: + llvm-version: 19 + + - name: Set up Emscripten + uses: emscripten-core/setup-emsdk@v15 + with: + version: "4.0.21" + + - name: Set up Go for building llgo + uses: ./.github/actions/setup-go + + - name: Install llgo + run: | + go install ./... + echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Set up Go for testing + uses: ./.github/actions/setup-go + with: + go-version: ${{matrix.go}} + + - name: Build standard runtime for wasm + shell: bash + run: | + 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" diff --git a/internal/build/build.go b/internal/build/build.go index 2de1f96536..0591932018 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -322,7 +322,7 @@ func Do(args []string, conf *Config) ([]Package, error) { verbose := conf.Verbose patterns := args - tags := "llgo,math_big_pure_go,purego" + tags := defaultBuildTags(conf.Goarch, conf.Target) if conf.PCLNMode == PCLNExternal { // Select the optional runtime loader as part of the normal package // cache key. Embedded and none builds do not compile any loader or @@ -654,6 +654,18 @@ func applyBuildModeCompileFlags(mode BuildMode, export *crosscompile.Export) { } } +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 == "" { + tags += ",nogc" + } + return tags +} + func allowMissingFunctionBodies(initial []*packages.Package) { for _, pkg := range initial { hasMissingBody := false diff --git a/internal/build/build_test.go b/internal/build/build_test.go index c26241cc20..5ee0b99669 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -8,6 +8,7 @@ import ( "debug/macho" "fmt" "go/ast" + gobuild "go/build" "go/parser" "go/token" "io" @@ -16,11 +17,13 @@ import ( "path/filepath" "runtime" "slices" + "strconv" "strings" "testing" "github.com/goplus/llgo/internal/buildenv" "github.com/goplus/llgo/internal/crosscompile" + "github.com/goplus/llgo/internal/env" "github.com/goplus/llgo/internal/lto" "github.com/goplus/llgo/internal/mockable" "github.com/goplus/llgo/internal/packages" @@ -59,6 +62,74 @@ func TestNeedsLinuxNoPIE(t *testing.T) { } } +func TestDefaultBuildTags(t *testing.T) { + const base = "llgo,math_big_pure_go,purego" + for _, test := range []struct { + name string + goarch string + target string + want string + }{ + {name: "native", goarch: "arm64", want: base}, + {name: "raw wasm", goarch: "wasm", want: base + ",nogc"}, + {name: "configured wasm target", goarch: "wasm", target: "wasip1", want: base}, + } { + t.Run(test.name, func(t *testing.T) { + if got := defaultBuildTags(test.goarch, test.target); got != test.want { + t.Fatalf("defaultBuildTags(%q, %q) = %q, want %q", test.goarch, test.target, got, test.want) + } + }) + } +} + +func TestWasmRuntimeAvoidsNativeHostDependencies(t *testing.T) { + runtimeDir := filepath.Join(env.LLGoRuntimeDir(), "internal", "lib", "runtime") + for _, goos := range []string{"js", "wasip1"} { + t.Run(goos, func(t *testing.T) { + ctx := gobuild.Default + ctx.GOOS = goos + ctx.GOARCH = "wasm" + ctx.BuildTags = []string{"llgo", "nogc"} + pkg, err := ctx.ImportDir(runtimeDir, 0) + if err != nil { + t.Fatal(err) + } + + selected := make(map[string]bool) + for _, name := range append(pkg.GoFiles, pkg.CgoFiles...) { + selected[name] = true + file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(runtimeDir, name), nil, parser.ImportsOnly) + if err != nil { + t.Fatal(err) + } + for _, spec := range file.Imports { + path, err := strconv.Unquote(spec.Path.Value) + if err != nil { + t.Fatal(err) + } + switch path { + case "github.com/goplus/llgo/runtime/internal/clite/libuv", + "github.com/goplus/llgo/runtime/internal/clite/bdwgc": + t.Fatalf("wasm selected %s, which imports native host dependency %s", name, path) + } + } + } + + for _, name := range []string{ + "mfinal_nogc.go", + "runtime_baremetal.go", + "signal_baremetal_llgo.go", + "time_wasm_llgo.go", + "unwind_wasm_llgo.go", + } { + if !selected[name] { + t.Errorf("wasm runtime did not select %s", name) + } + } + }) + } +} + func TestNeedsLinuxExportDynamic(t *testing.T) { t.Setenv(llgoFuncInfo, "") ctx := &context{buildConf: &Config{Goos: "linux"}} diff --git a/internal/build/plan9asm.go b/internal/build/plan9asm.go index 10e8913c16..57938f22c5 100644 --- a/internal/build/plan9asm.go +++ b/internal/build/plan9asm.go @@ -25,6 +25,9 @@ import ( // NOTE: golang.org/x/tools/go/packages.Package does not expose SFiles, so we // query `go list -json` here to get the exact filtered set for GOOS/GOARCH. func compilePkgSFiles(ctx *context, aPkg *aPackage, pkg *packages.Package, verbose bool) ([]string, error) { + if llruntime.SourcePatchReplacesAsmForGOARCH(pkg.PkgPath, ctx.buildConf.Goarch) { + return nil, nil + } sfiles, err := pkgSFiles(ctx, pkg) if err != nil { return nil, err diff --git a/internal/build/source_patch.go b/internal/build/source_patch.go index ca59deeaf5..0ce9686080 100644 --- a/internal/build/source_patch.go +++ b/internal/build/source_patch.go @@ -150,6 +150,28 @@ func applySourcePatchForPkg(base, current map[string][]byte, runtimeDir, goroot, } } + if llruntime.SourcePatchReplacesAsmForGOARCH(pkgPath, ctx.goarch) { + for _, entry := range srcEntries { + if entry.IsDir() { + continue + } + name := entry.Name() + if !strings.HasSuffix(name, ".s") || strings.HasSuffix(name, "_test.s") { + continue + } + match, err := buildCtx.MatchFile(srcDir, name) + if err != nil { + return false, nil, fmt.Errorf("match stdlib assembly file %s: %w", filepath.Join(srcDir, name), err) + } + if !match { + continue + } + ensureOverlay() + out[filepath.Join(srcDir, name)] = []byte("// replaced by LLGo source patch\n") + changed = true + } + } + if skipAll { for _, entry := range srcEntries { if entry.IsDir() { diff --git a/internal/build/source_patch_test.go b/internal/build/source_patch_test.go index 319d6b8b2e..4e913720a7 100644 --- a/internal/build/source_patch_test.go +++ b/internal/build/source_patch_test.go @@ -4,6 +4,7 @@ import ( "go/ast" "go/parser" "go/token" + "go/types" "io/fs" "os" "path/filepath" @@ -13,9 +14,140 @@ import ( "testing" "github.com/goplus/llgo/internal/env" + "github.com/goplus/llgo/internal/packages" llruntime "github.com/goplus/llgo/runtime" ) +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") + 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, + }) + 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} + }, &packages.Config{ + Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile, + Env: cfgEnv, + Fset: token.NewFileSet(), + Overlay: overlay, + }, "runtime") + if err != nil { + t.Fatal(err) + } + if len(pkgs) != 1 { + t.Fatalf("loaded %d runtime packages, want 1", len(pkgs)) + } + if pkgs[0].IllTyped { + logPackageErrors(t, pkgs[0], make(map[string]bool)) + t.Fatal("runtime did not type-check with wasm32 sizes") + } + }) + } +} + +func logPackageErrors(t *testing.T, pkg *packages.Package, seen map[string]bool) { + t.Helper() + if pkg == nil || seen[pkg.ID] { + return + } + seen[pkg.ID] = true + for _, err := range pkg.Errors { + t.Log(err) + } + for _, imported := range pkg.Imports { + if imported.IllTyped { + logPackageErrors(t, imported, seen) + } + } +} + +func TestWasmBytealgSourcePatchReplacesAsm(t *testing.T) { + for _, pkgPath := range []string{"internal/bytealg", "internal/chacha8rand", "internal/runtime/atomic"} { + if !llruntime.HasSourcePatchPkg(pkgPath) { + t.Fatalf("%s should be registered as a source patch package", pkgPath) + } + if !llruntime.SourcePatchReplacesAsmForGOARCH(pkgPath, "wasm") { + t.Fatalf("%s wasm assembly should be replaced by its source patch", pkgPath) + } + if llruntime.SourcePatchReplacesAsmForGOARCH(pkgPath, "arm64") { + t.Fatalf("%s native assembly should remain enabled", pkgPath) + } + } + + overlay, err := buildSourcePatchOverlayForGOROOT(nil, env.LLGoRuntimeDir(), runtime.GOROOT(), sourcePatchBuildContext{ + goos: "js", + goarch: "wasm", + goversion: runtime.Version(), + }) + if err != nil { + t.Fatal(err) + } + for _, file := range []string{ + "internal/bytealg/compare_wasm.s", + "internal/bytealg/equal_wasm.s", + "internal/bytealg/indexbyte_wasm.s", + "internal/chacha8rand/chacha8_stub.s", + "internal/runtime/atomic/atomic_wasm.s", + } { + path := filepath.Join(runtime.GOROOT(), "src", filepath.FromSlash(file)) + if got := string(overlay[path]); got != "// replaced by LLGo source patch\n" { + t.Fatalf("overlay[%q] = %q, want assembly replacement", path, got) + } + } +} + +func TestCompilePkgSFilesSkipsSourcePatchedAssembly(t *testing.T) { + got, err := compilePkgSFiles( + &context{buildConf: &Config{Goarch: "wasm"}}, + nil, + &packages.Package{PkgPath: "internal/bytealg"}, + false, + ) + if err != nil { + t.Fatal(err) + } + if got != nil { + t.Fatalf("compilePkgSFiles returned %v, want no object files", got) + } +} + +func TestSourcePatchAssemblyMatchError(t *testing.T) { + goroot := t.TempDir() + runtimeDir := t.TempDir() + const pkgPath = "internal/bytealg" + srcDir := filepath.Join(goroot, "src", filepath.FromSlash(pkgPath)) + patchDir := filepath.Join(runtimeDir, "_patch", filepath.FromSlash(pkgPath)) + + if err := os.MkdirAll(filepath.Join(srcDir, "adir"), 0755); err != nil { + t.Fatal(err) + } + mustWriteFile(t, filepath.Join(srcDir, "bad_wasm.s"), "//go:build (\n") + mustWriteFile(t, filepath.Join(patchDir, "bytealg_wasm.go"), `//go:build wasm + +package bytealg +`) + + _, _, err := applySourcePatchForPkg(nil, nil, runtimeDir, goroot, pkgPath, sourcePatchBuildContext{ + goos: "js", + goarch: "wasm", + }) + if err == nil || !strings.Contains(err.Error(), "match stdlib assembly file") { + t.Fatalf("applySourcePatchForPkg error = %v, want assembly match error", err) + } +} + func TestBuildSourcePatchOverlayForIter(t *testing.T) { overlay, err := buildSourcePatchOverlayForGOROOT(nil, env.LLGoRuntimeDir(), runtime.GOROOT(), sourcePatchBuildContext{}) if err != nil { diff --git a/internal/build/testdata/wasm-runtime/main.go b/internal/build/testdata/wasm-runtime/main.go new file mode 100644 index 0000000000..af4a422b48 --- /dev/null +++ b/internal/build/testdata/wasm-runtime/main.go @@ -0,0 +1,7 @@ +package main + +import "runtime" + +func main() { + println(runtime.GOOS) +} diff --git a/runtime/_patch/internal/bytealg/bytealg_wasm.go b/runtime/_patch/internal/bytealg/bytealg_wasm.go new file mode 100644 index 0000000000..da3de44c88 --- /dev/null +++ b/runtime/_patch/internal/bytealg/bytealg_wasm.go @@ -0,0 +1,76 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasm + +package bytealg + +import _ "unsafe" // for go:linkname + +func Compare(a, b []byte) int { + l := len(a) + if len(b) < l { + l = len(b) + } + for i := 0; i < l; i++ { + if a[i] < b[i] { + return -1 + } + if a[i] > b[i] { + return 1 + } + } + if len(a) < len(b) { + return -1 + } + if len(a) > len(b) { + return 1 + } + return 0 +} + +func CompareString(a, b string) int { + return runtime_cmpstring(a, b) +} + +//go:linkname runtime_cmpstring runtime.cmpstring +func runtime_cmpstring(a, b string) int { + l := len(a) + if len(b) < l { + l = len(b) + } + for i := 0; i < l; i++ { + if a[i] < b[i] { + return -1 + } + if a[i] > b[i] { + return 1 + } + } + if len(a) < len(b) { + return -1 + } + if len(a) > len(b) { + return 1 + } + return 0 +} + +func IndexByte(b []byte, c byte) int { + for i, value := range b { + if value == c { + return i + } + } + return -1 +} + +func IndexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} diff --git a/runtime/_patch/internal/chacha8rand/chacha8_wasm.go b/runtime/_patch/internal/chacha8rand/chacha8_wasm.go new file mode 100644 index 0000000000..7552843d2a --- /dev/null +++ b/runtime/_patch/internal/chacha8rand/chacha8_wasm.go @@ -0,0 +1,11 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasm + +package chacha8rand + +func block(seed *[4]uint64, blocks *[32]uint64, counter uint32) { + block_generic(seed, blocks, counter) +} diff --git a/runtime/_patch/internal/runtime/atomic/atomic_wasm.go b/runtime/_patch/internal/runtime/atomic/atomic_wasm.go new file mode 100644 index 0000000000..a806a3fb67 --- /dev/null +++ b/runtime/_patch/internal/runtime/atomic/atomic_wasm.go @@ -0,0 +1,14 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasm + +package atomic + +import "unsafe" + +// StorepNoWB performs *ptr = val without a write barrier. +func StorepNoWB(ptr unsafe.Pointer, val unsafe.Pointer) { + *(*uintptr)(ptr) = uintptr(val) +} diff --git a/runtime/_patch/runtime/hash32_wasm_pre_go126.go b/runtime/_patch/runtime/hash32_wasm_pre_go126.go new file mode 100644 index 0000000000..769ce83649 --- /dev/null +++ b/runtime/_patch/runtime/hash32_wasm_pre_go126.go @@ -0,0 +1,58 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasm && !go1.26 + +package runtime + +import "unsafe" + +func memhash32Fallback(p unsafe.Pointer, seed uintptr) uintptr { + a, b := mix32(uint32(seed), uint32(4^hashkey[0])) + t := readUnaligned32(p) + a ^= t + b ^= t + a, b = mix32(a, b) + a, b = mix32(a, b) + return uintptr(a ^ b) +} + +func memhash64Fallback(p unsafe.Pointer, seed uintptr) uintptr { + a, b := mix32(uint32(seed), uint32(8^hashkey[0])) + a ^= readUnaligned32(p) + b ^= readUnaligned32(add(p, 4)) + a, b = mix32(a, b) + a, b = mix32(a, b) + return uintptr(a ^ b) +} + +func memhashFallback(p unsafe.Pointer, seed, s uintptr) uintptr { + a, b := mix32(uint32(seed), uint32(s^hashkey[0])) + if s == 0 { + return uintptr(a ^ b) + } + for ; s > 8; s -= 8 { + a ^= readUnaligned32(p) + b ^= readUnaligned32(add(p, 4)) + a, b = mix32(a, b) + p = add(p, 8) + } + if s >= 4 { + a ^= readUnaligned32(p) + b ^= readUnaligned32(add(p, s-4)) + } else { + t := uint32(*(*byte)(p)) + t |= uint32(*(*byte)(add(p, s>>1))) << 8 + t |= uint32(*(*byte)(add(p, s-1))) << 16 + b ^= t + } + a, b = mix32(a, b) + a, b = mix32(a, b) + return uintptr(a ^ b) +} + +func mix32(a, b uint32) (uint32, uint32) { + c := uint64(a^uint32(hashkey[1])) * uint64(b^uint32(hashkey[2])) + return uint32(c), uint32(c >> 32) +} diff --git a/runtime/_patch/runtime/malloc_wasm.go b/runtime/_patch/runtime/malloc_wasm.go new file mode 100644 index 0000000000..e3e8dd2abb --- /dev/null +++ b/runtime/_patch/runtime/malloc_wasm.go @@ -0,0 +1,11 @@ +//go:build wasm && !go1.26 + +package runtime + +// LLGo uses native wasm32 pointers, while the Go wasm port models uintptr as +// 64 bits. Keep the GC heap geometry in the wasm32 address space. +const ( + heapAddrBits = 32 + maxAlloc = (1 << heapAddrBits) - 1 + logHeapArenaBytes = 22 +) diff --git a/runtime/_patch/runtime/malloc_wasm_go126.go b/runtime/_patch/runtime/malloc_wasm_go126.go new file mode 100644 index 0000000000..f646c50492 --- /dev/null +++ b/runtime/_patch/runtime/malloc_wasm_go126.go @@ -0,0 +1,11 @@ +//go:build wasm && go1.26 + +package runtime + +// Go 1.26 reduced wasm heap arenas to 512 KiB. LLGo still needs the heap +// address and allocation limits adjusted for native wasm32 pointers. +const ( + heapAddrBits = 32 + maxAlloc = (1 << heapAddrBits) - 1 + logHeapArenaBytes = 19 +) diff --git a/runtime/build.go b/runtime/build.go index 0764eadce5..179e2d7323 100644 --- a/runtime/build.go +++ b/runtime/build.go @@ -82,9 +82,24 @@ func SourcePatchPkgPaths() []string { return paths } +func SourcePatchReplacesAsmForGOARCH(path, goarch string) bool { + goarchs, ok := sourcePatchAsmPkgs[path] + return ok && hasGoarch(goarchs, goarch) +} + var sourcePatchPkgs = map[string]struct{}{ "crypto/internal/constanttime": {}, + "internal/bytealg": {}, + "internal/chacha8rand": {}, + "internal/runtime/atomic": {}, "internal/sync": {}, "iter": {}, + "runtime": {}, "runtime/metrics": {}, } + +var sourcePatchAsmPkgs = map[string]map[string]struct{}{ + "internal/bytealg": {"wasm": {}}, + "internal/chacha8rand": {"wasm": {}}, + "internal/runtime/atomic": {"wasm": {}}, +} diff --git a/runtime/internal/clite/debug/debug_wasm.go b/runtime/internal/clite/debug/debug_wasm.go index 6b217bf2ad..e9fc9b89bb 100644 --- a/runtime/internal/clite/debug/debug_wasm.go +++ b/runtime/internal/clite/debug/debug_wasm.go @@ -20,11 +20,17 @@ type Info struct { } func Address() unsafe.Pointer { - panic("not implemented") + return nil } func Addrinfo(addr unsafe.Pointer, info *Info) c.Int { - panic("not implemented") + _, _ = addr, info + return 0 +} + +func Symbol(name *c.Char) unsafe.Pointer { + _ = name + return nil } type Frame struct { @@ -35,7 +41,7 @@ type Frame struct { } func StackTrace(skip int, fn func(fr *Frame) bool) { - panic("not implemented") + _, _ = skip, fn } func PrintStack(skip int) { diff --git a/runtime/internal/lib/runtime/mfinal.go b/runtime/internal/lib/runtime/mfinal.go index 7ed607e65f..a62305c8a2 100644 --- a/runtime/internal/lib/runtime/mfinal.go +++ b/runtime/internal/lib/runtime/mfinal.go @@ -1,3 +1,5 @@ +//go:build !nogc + // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/runtime/internal/lib/runtime/mfinal_nogc.go b/runtime/internal/lib/runtime/mfinal_nogc.go new file mode 100644 index 0000000000..c85111ea65 --- /dev/null +++ b/runtime/internal/lib/runtime/mfinal_nogc.go @@ -0,0 +1,9 @@ +//go:build nogc + +package runtime + +// SetFinalizer is a no-op when garbage collection is disabled because objects +// are never discovered as unreachable. +func SetFinalizer(obj any, finalizer any) { + _, _ = obj, finalizer +} diff --git a/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go b/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go index c74b6d1421..fc85393f5f 100644 --- a/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go +++ b/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go @@ -1,4 +1,4 @@ -//go:build darwin || linux +//go:build darwin || linux || wasm package runtime diff --git a/runtime/internal/lib/runtime/runtime_baremetal.go b/runtime/internal/lib/runtime/runtime_baremetal.go index 372b5da9bd..ec1bcd1511 100644 --- a/runtime/internal/lib/runtime/runtime_baremetal.go +++ b/runtime/internal/lib/runtime/runtime_baremetal.go @@ -1,4 +1,4 @@ -//go:build baremetal +//go:build baremetal || wasm package runtime diff --git a/runtime/internal/lib/runtime/runtime_default.go b/runtime/internal/lib/runtime/runtime_default.go index 914a34e5be..4aa1ef0e7c 100644 --- a/runtime/internal/lib/runtime/runtime_default.go +++ b/runtime/internal/lib/runtime/runtime_default.go @@ -1,4 +1,4 @@ -//go:build !baremetal +//go:build !baremetal && !wasm package runtime diff --git a/runtime/internal/lib/runtime/signal_baremetal_llgo.go b/runtime/internal/lib/runtime/signal_baremetal_llgo.go index a0449a5e95..0323c40253 100644 --- a/runtime/internal/lib/runtime/signal_baremetal_llgo.go +++ b/runtime/internal/lib/runtime/signal_baremetal_llgo.go @@ -1,8 +1,8 @@ -//go:build baremetal +//go:build baremetal || wasm package runtime -// Baremetal targets do not provide OS signal delivery. +// Baremetal and wasm targets do not provide POSIX signal delivery. func signal_enable(sig uint32) { _ = sig diff --git a/runtime/internal/lib/runtime/signal_llgo.go b/runtime/internal/lib/runtime/signal_llgo.go index 7a9a2b606e..d474c500e6 100644 --- a/runtime/internal/lib/runtime/signal_llgo.go +++ b/runtime/internal/lib/runtime/signal_llgo.go @@ -1,4 +1,4 @@ -//go:build !baremetal +//go:build !baremetal && !wasm package runtime @@ -9,7 +9,7 @@ import ( latomic "github.com/goplus/llgo/runtime/internal/lib/sync/atomic" ) -// Minimal signal support for stdlib os/signal on llgo/darwin. +// Minimal signal support for stdlib os/signal on hosted native targets. type sigState struct { handle libuv.Signal diff --git a/runtime/internal/lib/runtime/time_llgo.go b/runtime/internal/lib/runtime/time_llgo.go index bc4586dd77..a332e80161 100644 --- a/runtime/internal/lib/runtime/time_llgo.go +++ b/runtime/internal/lib/runtime/time_llgo.go @@ -1,5 +1,5 @@ -//go:build !go1.23 && !baremetal -// +build !go1.23,!baremetal +//go:build !go1.23 && !baremetal && !wasm +// +build !go1.23,!baremetal,!wasm package runtime diff --git a/runtime/internal/lib/runtime/time_llgo_go123.go b/runtime/internal/lib/runtime/time_llgo_go123.go index 85661dd122..7192ce7adb 100644 --- a/runtime/internal/lib/runtime/time_llgo_go123.go +++ b/runtime/internal/lib/runtime/time_llgo_go123.go @@ -1,5 +1,5 @@ -//go:build go1.23 && !baremetal -// +build go1.23,!baremetal +//go:build go1.23 && !baremetal && !wasm +// +build go1.23,!baremetal,!wasm package runtime diff --git a/runtime/internal/lib/runtime/time_wasm_llgo.go b/runtime/internal/lib/runtime/time_wasm_llgo.go new file mode 100644 index 0000000000..500e7e0c99 --- /dev/null +++ b/runtime/internal/lib/runtime/time_wasm_llgo.go @@ -0,0 +1,116 @@ +//go:build wasm && go1.23 + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + ct "github.com/goplus/llgo/runtime/internal/clite/time" +) + +// Minimal timer hooks for wasm builds. Host-backed asynchronous timers need +// scheduler integration; until that is available, keep runtime and time +// linkable without pulling the native libuv event loop into wasm binaries. + +type runtimeTimer struct { + pp uintptr + when int64 + period int64 + f func(any, uintptr, int64) + arg any + seq uintptr + nextwhen int64 + status uint32 +} + +type timeTimer struct { + c unsafe.Pointer + init bool + r runtimeTimer +} + +func startRuntimeTimer(r *runtimeTimer) { + if r == nil || r.f == nil { + return + } + if r.period == 0 && r.when <= runtimeNano() { + r.f(r.arg, r.seq, runtimeNano()) + } +} + +func stopRuntimeTimer(r *runtimeTimer) bool { + return r != nil +} + +func resetRuntimeTimer(r *runtimeTimer, when, period int64, f func(any, uintptr, int64), arg any, seq uintptr) bool { + if r == nil { + return false + } + r.when = when + r.period = period + r.f = f + r.arg = arg + r.seq = seq + startRuntimeTimer(r) + return true +} + +//go:linkname time_now time.now +func time_now() (sec int64, nsec int32, mono int64) { + tv := (*ct.Timespec)(c.Alloca(unsafe.Sizeof(ct.Timespec{}))) + ct.ClockGettime(ct.CLOCK_REALTIME, tv) + return int64(tv.Sec), int32(tv.Nsec), runtimeNano() +} + +//go:linkname time_runtimeNow time.runtimeNow +func time_runtimeNow() (sec int64, nsec int32, mono int64) { + return time_now() +} + +//go:linkname time_runtimeNano time.runtimeNano +func time_runtimeNano() int64 { + return runtimeNano() +} + +//go:linkname time_runtimeIsBubbled time.runtimeIsBubbled +func time_runtimeIsBubbled() bool { + return false +} + +//go:linkname timeSleep time.Sleep +func timeSleep(ns int64) { + if ns <= 0 { + return + } + deadline := runtimeNano() + ns + for runtimeNano() < deadline { + } +} + +//go:linkname newTimer time.newTimer +func newTimer(when, period int64, f func(any, uintptr, int64), arg any, cp unsafe.Pointer) *timeTimer { + t := &timeTimer{c: cp, init: true} + t.r.when = when + t.r.period = period + t.r.f = f + t.r.arg = arg + startRuntimeTimer(&t.r) + return t +} + +//go:linkname stopTimer time.stopTimer +func stopTimer(t *timeTimer) bool { + if t == nil { + return false + } + return stopRuntimeTimer(&t.r) +} + +//go:linkname resetTimer time.resetTimer +func resetTimer(t *timeTimer, when, period int64) bool { + if t == nil { + return false + } + return resetRuntimeTimer(&t.r, when, period, t.r.f, t.r.arg, t.r.seq) +} diff --git a/runtime/internal/lib/runtime/unwind_wasm_llgo.go b/runtime/internal/lib/runtime/unwind_wasm_llgo.go new file mode 100644 index 0000000000..fb59d5e063 --- /dev/null +++ b/runtime/internal/lib/runtime/unwind_wasm_llgo.go @@ -0,0 +1,12 @@ +//go:build wasm + +package runtime + +func fpCallers(skip int, pc []uintptr) int { + _, _ = skip, pc + return 0 +} + +func fpUnwindAvailable() bool { + return false +} diff --git a/runtime/internal/lib/runtime/zgoos_wasip1.go b/runtime/internal/lib/runtime/zgoos_wasip1.go new file mode 100644 index 0000000000..a784efbc86 --- /dev/null +++ b/runtime/internal/lib/runtime/zgoos_wasip1.go @@ -0,0 +1,24 @@ +//go:build wasip1 + +package runtime + +const GOOS = `wasip1` + +const IsAix = 0 +const IsAndroid = 0 +const IsDarwin = 0 +const IsDragonfly = 0 +const IsFreebsd = 0 +const IsHurd = 0 +const IsIllumos = 0 +const IsIos = 0 +const IsJs = 0 +const IsLinux = 0 +const IsNacl = 0 +const IsNetbsd = 0 +const IsOpenbsd = 0 +const IsPlan9 = 0 +const IsSolaris = 0 +const IsWasip1 = 1 +const IsWindows = 0 +const IsZos = 0