Skip to content
Closed
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
16 changes: 12 additions & 4 deletions .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,10 @@ jobs:
}

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 "wasm workers ok" <<<"$output"
grep -Fxq "$expected" <<<"$output"
}

run_wasi_timers() {
Expand Down Expand Up @@ -516,10 +517,16 @@ jobs:
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"
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"
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=$!
Expand All @@ -533,7 +540,8 @@ jobs:
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; do
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")
Expand Down
23 changes: 23 additions & 0 deletions cl/gcroot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,29 @@ func keep(p *int) *int { return p }
}
}

func TestCompileThreadLocalGCRoots(t *testing.T) {
const src = `package main

func use(*int)

func keep(p *int) *int {
use(p)
return p
}
`
ir := cltest.CompileIREx(t, src, "gcroot_tls.go", false, func(prog llssa.Program) {
prog.EnableGCRoots(true)
prog.EnableThreadLocalGCRoots(true)
})
if !strings.Contains(ir, `thread_local`) ||
!strings.Contains(ir, `github.com/goplus/llgo/runtime/internal/gcroot.currentRootChain`) {
t.Fatalf("thread-local compiler root chain is missing:\n%s", ir)
}
if strings.Contains(ir, `@llvm_gc_root_chain`) {
t.Fatalf("thread-local roots also emitted the single-worker chain:\n%s", ir)
}
}

func TestCompileGCRootPlanning(t *testing.T) {
const pure = `package main
func keep(p *int) *int { return p }
Expand Down
11 changes: 3 additions & 8 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func Build(inv Invocation) ([]Package, error) {
if err != nil {
return nil, err
}
wasmGC, err := configureWasmGC(conf, &export, wasmWorkers.Enabled())
wasmGC, err := configureWasmGC(conf, &export)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -464,6 +464,7 @@ func Build(inv Invocation) ([]Package, error) {
prog.EnableGoGlobalDCE(conf.goGlobalDCEEnabled())
prog.EnableDeadcodeDrop(conf.deadcodeDropEnabled())
prog.EnableGCRoots(wasmGC)
prog.EnableThreadLocalGCRoots(wasmGC && wasmWorkers.Enabled())
prog.EnableCooperativeSafepoints(wasmGC || wasmWorkers.Enabled())
if conf.PthreadStackSize > 0 {
prog.SetPthreadStackSize(uint64(conf.PthreadStackSize))
Expand Down Expand Up @@ -820,7 +821,7 @@ func configureWasmWorkers(conf *Config, export *crosscompile.Export) (wasmworker
return config, nil
}

func configureWasmGC(conf *Config, export *crosscompile.Export, wasmWorkers bool) (bool, error) {
func configureWasmGC(conf *Config, export *crosscompile.Export) (bool, error) {
explicit := hasBuildTag(conf.Tags, "llgo_wasm_gc")
if conf.Goarch != "wasm" {
if explicit {
Expand All @@ -830,12 +831,6 @@ func configureWasmGC(conf *Config, export *crosscompile.Export, wasmWorkers bool
}
switch conf.Goos {
case "js":
if wasmWorkers {
if explicit {
return false, errors.New("llgo_wasm_gc does not yet support multiple WebAssembly workers")
}
return false, nil
}
if !slices.Contains(export.LDFLAGS, "-sMALLOC=none") {
export.LDFLAGS = append(export.LDFLAGS, "-sMALLOC=none")
}
Expand Down
27 changes: 20 additions & 7 deletions internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func TestConfigureWasmGC(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
export := crosscompile.Export{}
enabled, err := configureWasmGC(&test.conf, &export, false)
enabled, err := configureWasmGC(&test.conf, &export)
if (err != nil) != test.err {
t.Fatalf("configureWasmGC error = %v, want error %v", err, test.err)
}
Expand All @@ -365,15 +365,15 @@ func TestConfigureWasmGC(t *testing.T) {
func TestConfigureWasmGCRejectsWASIThreads(t *testing.T) {
t.Setenv("LLGO_WASI_THREADS", "1")
conf := Config{Goos: "wasip1", Goarch: "wasm", Tags: "llgo_wasm_gc"}
if _, err := configureWasmGC(&conf, &crosscompile.Export{}, false); err == nil {
if _, err := configureWasmGC(&conf, &crosscompile.Export{}); err == nil {
t.Fatal("expected llgo_wasm_gc with WASI threads to fail")
}
}

func TestConfigureWasmGCLeavesWASIThreadsDisabled(t *testing.T) {
t.Setenv("LLGO_WASI_THREADS", "1")
conf := Config{Goos: "wasip1", Goarch: "wasm"}
enabled, err := configureWasmGC(&conf, &crosscompile.Export{}, false)
enabled, err := configureWasmGC(&conf, &crosscompile.Export{})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -435,10 +435,23 @@ func TestConfigureWasmWorkersRejectsUnsupportedTarget(t *testing.T) {
}
}

func TestConfigureWasmWorkersRejectsCurrentGC(t *testing.T) {
conf := Config{Goos: "js", Goarch: "wasm", Tags: "llgo_wasm_gc"}
if _, err := configureWasmGC(&conf, &crosscompile.Export{}, true); err == nil {
t.Fatal("multi-worker wasm GC configuration succeeded before M2")
func TestConfigureWasmWorkersEnableGC(t *testing.T) {
t.Setenv(llgoWasmWorkers, "2")
conf := Config{Goos: "js", Goarch: "wasm"}
export := crosscompile.Export{}
workers, err := configureWasmWorkers(&conf, &export)
if err != nil || !workers.Enabled() {
t.Fatalf("multi-worker wasm configuration = %+v, %v", workers, err)
}
if enabled, err := configureWasmGC(&conf, &export); err != nil || !enabled {
t.Fatalf("multi-worker wasm GC configuration = %v, %v", enabled, err)
}
if !hasBuildTag(conf.Tags, "llgo_wasm_gc") ||
!slices.Contains(export.BuildTags, "llgo.wasm_workers") {
t.Fatalf("multi-worker wasm GC tags = %q, %v", conf.Tags, export.BuildTags)
}
if !slices.Contains(export.LDFLAGS, "-sMALLOC=none") {
t.Fatalf("multi-worker wasm GC linker flags = %v", export.LDFLAGS)
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/build/testdata/wasm-gc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func main() {
testRecoveredRootChain()
testReclamation()
testHeapGrowth()
testMultiWorkerGC()
println("wasm gc ok")
}

Expand Down
103 changes: 103 additions & 0 deletions internal/build/testdata/wasm-gc/workers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//go:build llgo.wasm_workers

package main

import (
"runtime"
"sync/atomic"
_ "unsafe"
)

//go:linkname schedulerProcID github.com/goplus/llgo/runtime/internal/runtime.SchedulerProcID
func schedulerProcID() int

func testMultiWorkerGC() {
testRemoteWorkerGC()
testConcurrentWorkerAllocation()
}

func testRemoteWorkerGC() {
const want = uint64(0x76543210)
live := &payload{value: want}

for {
start := make(chan bool)
ready := make(chan int)
var (
done atomic.Bool
result atomic.Uint64
)
go func() {
ready <- schedulerProcID()
if !<-start {
done.Store(true)
return
}
remoteLive := &payload{value: want}
for range 8 {
runtime.GC()
for i := range 512 {
garbage = &payload{value: uint64(i)}
}
garbage = nil
}
result.Store(remoteLive.value)
done.Store(true)
}()

if proc := <-ready; proc == 0 {
start <- false
for !done.Load() {
}
continue
}
start <- true
for !done.Load() {
if live.value != want {
panic("remote GC lost an active worker root")
}
}
if result.Load() != want || live.value != want {
panic("remote worker GC lost a live root")
}
return
}
}

func testConcurrentWorkerAllocation() {
const (
goroutines = 4
iterations = 4096
liveCount = 32
)
start := make(chan struct{})
done := make(chan int, goroutines)
for id := range goroutines {
go func() {
<-start
var live [liveCount]*payload
for i := range iterations {
slot := i % len(live)
live[slot] = &payload{value: uint64(id*iterations + i + 1)}
if i%1024 == 0 {
runtime.GC()
}
}
for _, value := range live {
if value == nil || value.value == 0 {
panic("concurrent allocation lost a live object")
}
}
done <- schedulerProcID()
}()
}
close(start)

workers := make(map[int]bool)
for range goroutines {
workers[<-done] = true
}
if len(workers) < 2 {
panic("concurrent GC did not cover multiple workers")
}
}
5 changes: 5 additions & 0 deletions internal/build/testdata/wasm-gc/workers_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !llgo.wasm_workers

package main

func testMultiWorkerGC() {}
7 changes: 4 additions & 3 deletions runtime/internal/gcroot/current_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package gcroot

import "unsafe"

var currentRootChain unsafe.Pointer
var (
currentRootChain uintptr
activeContext uintptr
)
8 changes: 5 additions & 3 deletions runtime/internal/gcroot/current_wasm.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//go:build llgo && wasm && llgo_wasm_gc
//go:build llgo && wasm && llgo_wasm_gc && !llgo.wasm_workers

package gcroot

import "unsafe"
import _ "unsafe"

//go:linkname currentRootChain llvm_gc_root_chain
var currentRootChain unsafe.Pointer
var currentRootChain uintptr

var activeContext uintptr
12 changes: 12 additions & 0 deletions runtime/internal/gcroot/current_wasm_workers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build llgo && js && wasm && llgo_wasm_gc && llgo.wasm_workers

package gcroot

// The compiler uses the fully qualified currentRootChain symbol as its
// thread-local root-chain slot in multi-worker builds.
//
//llgo:tls
var (
currentRootChain uintptr
activeContext uintptr
)
Loading
Loading