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
35 changes: 34 additions & 1 deletion .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,36 @@ jobs:
LLGO_WASM_WORKERS=2 llgo build -target wasm \
-o "$RUNNER_TEMP/wasm-hardening-workers.mjs" ./internal/build/testdata/wasm-hardening
run_wasm_hardening "$RUNNER_TEMP/wasm-hardening-workers.mjs" "hardening-workers-j32"
LLGO_WASM_RESUME=1 LLGO_WASM_WORKERS=2 GOOS=js GOARCH=wasm llgo build -p=1 \
-o "$RUNNER_TEMP/wasm-resume-workers-go.mjs" ./internal/build/testdata/wasm-workers
run_wasm_workers "$RUNNER_TEMP/wasm-resume-workers-go.mjs" "wasm workers ok"
LLGO_WASM_RESUME=1 LLGO_WASM_WORKERS=2 llgo build -p=1 -target wasm \
-o "$RUNNER_TEMP/wasm-resume-workers.mjs" ./internal/build/testdata/wasm-workers
run_wasm_workers "$RUNNER_TEMP/wasm-resume-workers.mjs" "wasm workers ok"
LLGO_WASM_RESUME=1 LLGO_WASM_WORKERS=2 GOOS=js GOARCH=wasm llgo build -p=1 \
-o "$RUNNER_TEMP/wasm-resume-gc-workers-go.mjs" ./internal/build/testdata/wasm-gc
run_wasm_workers "$RUNNER_TEMP/wasm-resume-gc-workers-go.mjs" "wasm gc ok"
LLGO_WASM_RESUME=1 LLGO_WASM_WORKERS=2 llgo build -p=1 -target wasm \
-o "$RUNNER_TEMP/wasm-resume-gc-workers.mjs" ./internal/build/testdata/wasm-gc
run_wasm_workers "$RUNNER_TEMP/wasm-resume-gc-workers.mjs" "wasm gc ok"
LLGO_WASM_RESUME=1 LLGO_WASM_WORKERS=2 GOOS=js GOARCH=wasm llgo build -p=1 \
-o "$RUNNER_TEMP/wasm-resume-hardening-workers-go.mjs" ./internal/build/testdata/wasm-hardening
run_wasm_hardening "$RUNNER_TEMP/wasm-resume-hardening-workers-go.mjs" "resume-hardening-workers-j64"
LLGO_WASM_RESUME=1 LLGO_WASM_WORKERS=2 llgo build -p=1 -target wasm \
-o "$RUNNER_TEMP/wasm-resume-hardening-workers.mjs" ./internal/build/testdata/wasm-hardening
run_wasm_hardening "$RUNNER_TEMP/wasm-resume-hardening-workers.mjs" "resume-hardening-workers-j32"
for module in \
"$RUNNER_TEMP/wasm-resume-workers-go.wasm" \
"$RUNNER_TEMP/wasm-resume-workers.wasm" \
"$RUNNER_TEMP/wasm-resume-gc-workers-go.wasm" \
"$RUNNER_TEMP/wasm-resume-gc-workers.wasm" \
"$RUNNER_TEMP/wasm-resume-hardening-workers-go.wasm" \
"$RUNNER_TEMP/wasm-resume-hardening-workers.wasm"; do
if strings "$module" | grep -Eiq 'asyncify|emscripten_fiber|runtime/internal/wasmcontext'; then
echo "resumable worker artifact retained Asyncify/Fiber code: $module"
exit 1
fi
done
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 @@ -572,7 +602,10 @@ jobs:
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 wasm-hardening-workers.mjs \
wasm-hardening-workers-go.mjs; do
wasm-hardening-workers-go.mjs wasm-resume-workers.mjs \
wasm-resume-workers-go.mjs wasm-resume-gc-workers.mjs \
wasm-resume-gc-workers-go.mjs wasm-resume-hardening-workers.mjs \
wasm-resume-hardening-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
11 changes: 11 additions & 0 deletions internal/build/testdata/wasm-workers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func main() {
testCrossWorkerSynchronization()
testInterleavedWorkerLocality()
testCrossWorkerTimerWake()
testGoroutineGoexit()
println("wasm workers ok")
}

Expand Down Expand Up @@ -242,3 +243,13 @@ func testCrossWorkerTimerWake() {
panic("timer did not wake a worker")
}
}

func testGoroutineGoexit() {
done := make(chan struct{})
go func() {
defer close(done)
runtime.Goexit()
panic("Goexit returned")
}()
<-done
}
39 changes: 39 additions & 0 deletions internal/build/wasm_resume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,45 @@ func TestConfigureWasmResume(t *testing.T) {
}
}

func TestConfigureWasmResumeWorkers(t *testing.T) {
t.Setenv(llgoWasmResume, "1")
t.Setenv(llgoWasmWorkers, "2")
t.Setenv(llgoWasiThreads, "")
conf := &Config{Goos: "js", Goarch: "wasm"}
export := crosscompile.Export{
LDFLAGS: []string{"-sASYNCIFY=1"},
WasmPostLink: crosscompile.WasmPostLink{
Asyncify: true,
},
}
if err := configureWasmResume(conf, &export); err != nil {
t.Fatal(err)
}
workers, err := configureWasmWorkers(conf, &export)
if err != nil || workers.Count != 2 {
t.Fatalf("worker configuration = %+v, %v", workers, err)
}
if enabled, err := configureWasmGC(conf, &export); err != nil || !enabled {
t.Fatalf("GC configuration = %v, %v", enabled, err)
}
for _, tag := range []string{wasmResumeBuildTag, "llgo.wasm_workers"} {
if !slices.Contains(export.BuildTags, tag) {
t.Fatalf("build tags do not contain %q: %v", tag, export.BuildTags)
}
}
if !hasBuildTag(conf.Tags, "llgo_wasm_gc") {
t.Fatalf("GC build tag is missing from %q", conf.Tags)
}
for _, flag := range []string{"-pthread", "-sPTHREAD_POOL_SIZE=2", "-sMALLOC=none"} {
if !slices.Contains(export.LDFLAGS, flag) {
t.Fatalf("linker flags do not contain %q: %v", flag, export.LDFLAGS)
}
}
if export.WasmPostLink.Asyncify || slices.Contains(export.LDFLAGS, "-sASYNCIFY=1") {
t.Fatalf("resumable worker build retained Asyncify: %+v", export)
}
}

func TestConfigureWasmResumeRejectsUnsupportedModes(t *testing.T) {
t.Setenv(llgoWasmResume, "1")
for _, test := range []struct {
Expand Down
54 changes: 41 additions & 13 deletions internal/wasmresume/boundary.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,32 @@ package wasmresume
import "strings"

const (
runtimeResumePrefix = "github.com/goplus/llgo/runtime/internal/wasmresume."
runtimeGCRootPrefix = "github.com/goplus/llgo/runtime/internal/gcroot."
runtimeTinyGCPrefix = "github.com/goplus/llgo/runtime/internal/runtime/tinygogc."
runtimeAllocRoot = "github.com/goplus/llgo/runtime/internal/runtime.AllocRoot"
runtimeFreeRoot = "github.com/goplus/llgo/runtime/internal/runtime.FreeRoot"
runtimeRunWasmMain = "github.com/goplus/llgo/runtime/internal/runtime.RunWasmMain"
runtimeRunWasmResumeContext = "github.com/goplus/llgo/runtime/internal/runtime.runWasmResumeContext"
runtimeFrameAlloc = "__llgo_wasm_resume_alloc"
runtimeDynamicAlloc = "__llgo_wasm_resume_alloc_dynamic"
runtimeFrameFree = "__llgo_wasm_resume_free"
runtimeCompatEnter = "__llgo_wasm_resume_compat_enter"
runtimeCompatLeave = "__llgo_wasm_resume_compat_leave"
runtimeResumePrefix = "github.com/goplus/llgo/runtime/internal/wasmresume."
runtimeGCRootPrefix = "github.com/goplus/llgo/runtime/internal/gcroot."
runtimeTinyGCPrefix = "github.com/goplus/llgo/runtime/internal/runtime/tinygogc."
runtimeWasmWorkersPrefix = "github.com/goplus/llgo/runtime/internal/wasmworkers."
runtimeAllocRoot = "github.com/goplus/llgo/runtime/internal/runtime.AllocRoot"
runtimeFreeRoot = "github.com/goplus/llgo/runtime/internal/runtime.FreeRoot"
runtimeCurrentWasmWorker = "github.com/goplus/llgo/runtime/internal/runtime.currentWasmWorker"
runtimeCurrentResumeOwners = "github.com/goplus/llgo/runtime/internal/runtime.currentWasmResumeOwners"
runtimeRunWasmG = "github.com/goplus/llgo/runtime/internal/runtime.runWasmG"
runtimeRunWasmMain = "github.com/goplus/llgo/runtime/internal/runtime.RunWasmMain"
runtimeRunWasmResumeContext = "github.com/goplus/llgo/runtime/internal/runtime.runWasmResumeContext"
runtimeRunWasmWorker = "github.com/goplus/llgo/runtime/internal/runtime.runWasmWorker"
runtimeRunWasmWorkerContext = "github.com/goplus/llgo/runtime/internal/runtime.runWasmWorkerContext"
runtimeSetCurrentWasmWorker = "github.com/goplus/llgo/runtime/internal/runtime.setCurrentWasmWorker"
runtimeWasmWorkerStart = "github.com/goplus/llgo/runtime/internal/runtime.wasmWorkerStart"
runtimeWasmGCAllocatorYield = "github.com/goplus/llgo/runtime/internal/runtime.wasmGCAllocatorYield"
runtimeWasmGCWorldOwner = "github.com/goplus/llgo/runtime/internal/runtime.wasmGCWorldOwner"
runtimeWasmPublishGCRoot = "github.com/goplus/llgo/runtime/internal/runtime.publishWasmGCRoot"
runtimeWasmWorkerStopForGC = "github.com/goplus/llgo/runtime/internal/runtime.wasmWorkerStopForGC"
runtimeWasmSyncLockNoSuspend = "github.com/goplus/llgo/runtime/internal/wasmsync.(*Mutex).LockNoSuspend"
runtimeWasmSyncUnlock = "github.com/goplus/llgo/runtime/internal/wasmsync.(*Mutex).Unlock"
runtimeFrameAlloc = "__llgo_wasm_resume_alloc"
runtimeDynamicAlloc = "__llgo_wasm_resume_alloc_dynamic"
runtimeFrameFree = "__llgo_wasm_resume_free"
runtimeCompatEnter = "__llgo_wasm_resume_compat_enter"
runtimeCompatLeave = "__llgo_wasm_resume_compat_leave"
)

// IsRuntimeABIImplementation reports functions which implement the resumable
Expand All @@ -43,7 +57,8 @@ func IsRuntimeABIImplementation(name string) bool {
// callable without allocating a resumable frame.
func IsNonSuspendingBoundary(name string) bool {
if strings.HasPrefix(name, runtimeGCRootPrefix) ||
strings.HasPrefix(name, runtimeTinyGCPrefix) {
strings.HasPrefix(name, runtimeTinyGCPrefix) ||
strings.HasPrefix(name, runtimeWasmWorkersPrefix) {
return true
}
switch name {
Expand All @@ -67,8 +82,21 @@ func IsNonSuspendingBoundary(name string) bool {
return (IsRuntimeABIImplementation(name) && name != SuspendSymbol) ||
name == runtimeAllocRoot ||
name == runtimeFreeRoot ||
name == runtimeCurrentWasmWorker ||
name == runtimeCurrentResumeOwners ||
name == runtimeRunWasmG ||
name == runtimeRunWasmMain ||
name == runtimeRunWasmResumeContext ||
name == runtimeRunWasmWorker ||
name == runtimeRunWasmWorkerContext ||
name == runtimeSetCurrentWasmWorker ||
name == runtimeWasmWorkerStart ||
name == runtimeWasmGCAllocatorYield ||
name == runtimeWasmGCWorldOwner ||
name == runtimeWasmPublishGCRoot ||
name == runtimeWasmWorkerStopForGC ||
name == runtimeWasmSyncLockNoSuspend ||
name == runtimeWasmSyncUnlock ||
name == runtimeFrameAlloc ||
name == runtimeDynamicAlloc ||
name == runtimeFrameFree ||
Expand Down
14 changes: 14 additions & 0 deletions internal/wasmresume/boundary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,26 @@ func TestRuntimeBoundaries(t *testing.T) {
for _, name := range []string{
runtimeAllocRoot,
runtimeFreeRoot,
runtimeCurrentWasmWorker,
runtimeCurrentResumeOwners,
runtimeRunWasmG,
runtimeRunWasmMain,
runtimeRunWasmWorker,
runtimeRunWasmWorkerContext,
runtimeFrameAlloc,
runtimeDynamicAlloc,
runtimeFrameFree,
runtimeCompatEnter,
runtimeCompatLeave,
runtimeRunWasmResumeContext,
runtimeSetCurrentWasmWorker,
runtimeWasmWorkerStart,
runtimeWasmGCAllocatorYield,
runtimeWasmGCWorldOwner,
runtimeWasmPublishGCRoot,
runtimeWasmWorkerStopForGC,
runtimeWasmSyncLockNoSuspend,
runtimeWasmSyncUnlock,
"github.com/goplus/llgo/runtime/internal/runtime.AllocU",
"github.com/goplus/llgo/runtime/internal/runtime.AllocZ",
"github.com/goplus/llgo/runtime/internal/runtime.GetThreadDefer",
Expand All @@ -44,6 +57,7 @@ func TestRuntimeBoundaries(t *testing.T) {
runtimeGCRootPrefix + "RestoreChain",
runtimeTinyGCPrefix + "Alloc",
runtimeTinyGCPrefix + "GC",
runtimeWasmWorkersPrefix + "Wait",
} {
if !IsNonSuspendingBoundary(name) {
t.Fatalf("%q is not a non-suspending boundary", name)
Expand Down
13 changes: 10 additions & 3 deletions runtime/internal/gcroot/gcroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ type stackEntry struct {
m *frameMap
}

var (
contexts *Context
)
var contexts *Context

// CurrentChain returns the active execution owner's compiler root chain.
func CurrentChain() unsafe.Pointer {
Expand Down Expand Up @@ -99,6 +97,15 @@ func SwitchAtBoundary(next *Context) {
}
}

// ClearSuspendedChain drops the saved chain of an inactive context when its
// target-specific backend owns that context's root lifetime separately.
func ClearSuspendedChain(ctx *Context) {
if ctx == nil || uintptr(unsafe.Pointer(ctx)) == activeContext {
panic("gcroot: invalid suspended context")
}
ctx.chain = nil
}

// AdoptCurrent marks next active after a target-specific stack switch has
// already restored currentRootChain.
func AdoptCurrent(next *Context) {
Expand Down
17 changes: 17 additions & 0 deletions runtime/internal/gcroot/gcroot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ func TestPublishAndSwitchToSystem(t *testing.T) {
}
}

func TestClearSuspendedChain(t *testing.T) {
resetForTest()
t.Cleanup(resetForTest)

var active, suspended Context
RegisterActive(&active)
Register(&suspended)
chain := unsafe.Pointer(uintptr(0x55))
suspended.chain = chain
ClearSuspendedChain(&suspended)
if suspended.chain != nil {
t.Fatalf("suspended chain = %p, want nil", suspended.chain)
}
assertPanics(t, func() { ClearSuspendedChain(nil) })
assertPanics(t, func() { ClearSuspendedChain(&active) })
}

func assertPanics(t *testing.T, fn func()) {
t.Helper()
defer func() {
Expand Down
Loading
Loading