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
27 changes: 27 additions & 0 deletions .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,33 @@ jobs:
--env LLGO_WASM_BLOCKED_G=1000 \
"$RUNNER_TEMP/wasm-resume-hardening-wasip1.wasm" resume-hardening-p1 2>&1)
grep -Fxq "wasm hardening ok" <<<"$output"
LLGO_WASM_RESUME=1 GOOS=js GOARCH=wasm llgo build -p=1 \
-o "$RUNNER_TEMP/wasm-resume-acceptance-go.mjs" ./internal/build/testdata/wasm-resume-acceptance
run_wasm_workers "$RUNNER_TEMP/wasm-resume-acceptance-go.mjs" "wasm resume acceptance ok"
LLGO_WASM_RESUME=1 llgo build -p=1 -target wasm \
-o "$RUNNER_TEMP/wasm-resume-acceptance.mjs" ./internal/build/testdata/wasm-resume-acceptance
run_wasm_workers "$RUNNER_TEMP/wasm-resume-acceptance.mjs" "wasm resume acceptance ok"
LLGO_WASM_RESUME=1 GOOS=wasip1 GOARCH=wasm llgo build -p=1 \
-o "$RUNNER_TEMP/wasm-resume-acceptance-wasip1.wasm" ./internal/build/testdata/wasm-resume-acceptance
wasm-tools validate --features all "$RUNNER_TEMP/wasm-resume-acceptance-wasip1.wasm"
test "$(wasmtime run -W exceptions=y "$RUNNER_TEMP/wasm-resume-acceptance-wasip1.wasm" 2>&1)" = \
"wasm resume acceptance ok"
LLGO_WASM_RESUME=1 GOOS=js GOARCH=wasm llgo build -p=1 -O0 -ldflags=-w=false \
-o "$RUNNER_TEMP/wasm-resume-debug-go.mjs" ./internal/build/testdata/wasm-resume-debug
run_wasm_workers "$RUNNER_TEMP/wasm-resume-debug-go.mjs" "wasm resume debug ok"
llvm-dwarfdump --verify "$RUNNER_TEMP/wasm-resume-debug-go.wasm"
LLGO_WASM_RESUME=1 llgo build -p=1 -O0 -ldflags=-w=false -target wasm \
-o "$RUNNER_TEMP/wasm-resume-debug.mjs" ./internal/build/testdata/wasm-resume-debug
run_wasm_workers "$RUNNER_TEMP/wasm-resume-debug.mjs" "wasm resume debug ok"
llvm-dwarfdump --verify "$RUNNER_TEMP/wasm-resume-debug.wasm"
LLGO_WASM_RESUME=1 GOOS=wasip1 GOARCH=wasm llgo build -p=1 -O0 -ldflags=-w=false \
-o "$RUNNER_TEMP/wasm-resume-debug-wasip1.wasm" ./internal/build/testdata/wasm-resume-debug
wasm-tools validate --features all "$RUNNER_TEMP/wasm-resume-debug-wasip1.wasm"
test "$(wasmtime run -W exceptions=y "$RUNNER_TEMP/wasm-resume-debug-wasip1.wasm" 2>&1)" = \
"wasm resume debug ok"
wasm_sections=$(llvm-readobj --sections "$RUNNER_TEMP/wasm-resume-debug-wasip1.wasm")
grep -Fq '.debug_info' <<<"$wasm_sections"
grep -Fq '.debug_line' <<<"$wasm_sections"
file "$RUNNER_TEMP/runtime-js.wasm" \
"$RUNNER_TEMP/runtime-wasip1.wasm" \
"$RUNNER_TEMP/runtime-wasip1-threads.wasm" \
Expand Down
38 changes: 38 additions & 0 deletions cl/caller_frame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,14 @@ func TestCallerFrameTrackingEligibility(t *testing.T) {
track bool
targetName string
goarch string
wasmResume bool
want bool
}{
{name: "enabled user package", pkgPath: "example.com/foo", track: true, want: true},
{name: "disabled flag", pkgPath: "example.com/foo", want: false},
{name: "named target", pkgPath: "example.com/foo", track: true, targetName: "esp32", want: false},
{name: "wasm", pkgPath: "example.com/foo", track: true, goarch: "wasm", want: false},
{name: "resumable wasm", pkgPath: "example.com/foo", track: true, goarch: "wasm", wasmResume: true, want: true},
{name: "stdlib", pkgPath: "fmt", track: true, want: true},
{name: "runtime", pkgPath: "runtime", track: true, want: false},
{name: "llgo runtime", pkgPath: llssa.PkgRuntime, track: true, want: false},
Expand All @@ -438,6 +440,7 @@ func f() { runtime.Caller(0) }
if tt.goarch != "" {
prog.Target().GOARCH = tt.goarch
}
prog.EnableWasmResumeABI(tt.wasmResume)
pkg := prog.NewPackage("foo", tt.pkgPath)
fn := pkg.NewFunc("f", llssa.NoArgsNoRet, llssa.InGo)
goFn := ssapkg.Func("f")
Expand Down Expand Up @@ -824,6 +827,41 @@ func f() { _ = runtime.FuncForPC(0) }
}
}

func TestCompileRuntimeCallerFrameInstrumentationForWasm(t *testing.T) {
ssapkg, files := buildCallerFrameSSAPackage(t, "example.com/foo", `package foo
import "runtime"

func f() {
runtime.Caller(0)
}
`)
for _, tt := range []struct {
name string
wasmResume bool
wantTracking bool
}{
{name: "default"},
{name: "resumable", wasmResume: true, wantTracking: true},
} {
t.Run(tt.name, func(t *testing.T) {
prog := newLLSSAProg(t)
prog.Target().GOOS = "js"
prog.Target().GOARCH = "wasm"
prog.EnableWasmResumeABI(tt.wasmResume)
pkg, err := NewPackage(prog, ssapkg, files)
if err != nil {
t.Fatal(err)
}
ir := pkg.Module().String()
for _, symbol := range []string{"PushCallerLocationFrame", "RecordCallerLocation", "PopCallerLocationFrame"} {
if got := strings.Contains(ir, symbol); got != tt.wantTracking {
t.Fatalf("wasm runtime.Caller tracking %s = %v, want %v:\n%s", symbol, got, tt.wantTracking, ir)
}
}
})
}
}

func TestCompileRuntimeCallerLocationOnlyForRuntimePaths(t *testing.T) {
old := emitShadowStackInstrumentation
emitShadowStackInstrumentation = true
Expand Down
5 changes: 5 additions & 0 deletions cl/debug_compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func inspect(items [2]item, seed int) int {
return items[0].value + local[0].value
}

func inspectDefer(seed int) (result int) {
defer func() { result++ }()
return seed
}

var anonymous = func(seed int) int {
value := seed + 1
return value
Expand Down
31 changes: 27 additions & 4 deletions cl/gcroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ func (p *context) prepareGCRoots(fn *ssa.Function, hasClosureContext bool) {
case *ssa.FreeVar:
return false
}
typ := p.type_(value.Type(), llssa.InGo)
return p.prog.GCRootCount(typ) != 0
return p.gcRootCount(value) != 0
}, p.isGCSafepoint)
if p.safepointEntry {
for _, param := range fn.Params {
Expand All @@ -54,8 +53,7 @@ func (p *context) prepareGCRoots(fn *ssa.Function, hasClosureContext bool) {
if _, ok := planned[value]; !ok {
return
}
typ := p.type_(value.Type(), llssa.InGo)
if n := p.prog.GCRootCount(typ); n != 0 {
if n := p.gcRootCount(value); n != 0 {
counts[value] = n
total += n
}
Expand Down Expand Up @@ -99,6 +97,31 @@ func (p *context) prepareGCRoots(fn *ssa.Function, hasClosureContext bool) {
}
}

func (p *context) gcRootCount(value ssa.Value) int {
if call, ok := value.(*ssa.Call); ok {
if fn, ok := call.Call.Value.(*ssa.Function); ok {
_, name, kind := p.funcName(fn)
if kind == llgoInstr && llgoInstrs[name] == llgoFuncAddr {
return 0
}
}
}
if next, ok := value.(*ssa.Next); ok {
if next.IsString {
return 0
}
if iter, ok := next.Iter.(*ssa.Range); ok {
if typ, ok := types.Unalias(iter.X.Type()).Underlying().(*types.Map); ok {
key := p.type_(typ.Key(), llssa.InGo)
elem := p.type_(typ.Elem(), llssa.InGo)
return p.prog.GCRootCount(key) + p.prog.GCRootCount(elem)
}
}
}
typ := p.type_(value.Type(), llssa.InGo)
return p.prog.GCRootCount(typ)
}

func (p *context) initGCRoots(b llssa.Builder, fn *ssa.Function) {
if len(p.gcRoots) == 0 && p.gcClosureRoot.IsNil() {
return
Expand Down
69 changes: 69 additions & 0 deletions cl/gcroot_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
"go/parser"
"go/token"
"go/types"
"runtime"
"testing"

llssa "github.com/goplus/llgo/ssa"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
Expand Down Expand Up @@ -132,6 +134,73 @@ func classify(p *int) *int { return p }
}
}

func TestGCRootCountUsesConcreteRangeNextType(t *testing.T) {
fn := buildGCRootSSAFunction(t, `package p
func classify(values map[int]*int, text string, pointer *int) {
for range values {}
for range text {}
}`)
prog := llssa.NewProgram(nil)
defer prog.Dispose()
prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH))
ctx := context{prog: prog}
if got := ctx.gcRootCount(fn.Params[2]); got != 1 {
t.Fatalf("pointer parameter root count = %d, want 1", got)
}

seenMap, seenString := false, false
for _, block := range fn.Blocks {
for _, instr := range block.Instrs {
next, ok := instr.(*ssa.Next)
if !ok {
continue
}
got := ctx.gcRootCount(next)
if next.IsString {
seenString = true
if got != 0 {
t.Fatalf("string range Next root count = %d, want 0", got)
}
} else {
seenMap = true
if got != 1 {
t.Fatalf("map range Next root count = %d, want 1", got)
}
}
}
}
if !seenMap || !seenString {
t.Fatalf("range Next instructions: map=%v string=%v", seenMap, seenString)
}
}

func TestGCRootCountExcludesFunctionAddressIntrinsic(t *testing.T) {
fn := buildGCRootSSAFunction(t, `package p
import "unsafe"
func intrinsic(any) unsafe.Pointer
func classify(fn func()) unsafe.Pointer { return intrinsic(fn) }
`)
prog := llssa.NewProgram(nil)
defer prog.Dispose()
prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH))
prog.SetLinkname("gcroot.intrinsic", "llgo.funcAddr")
ctx := context{prog: prog, goTyps: fn.Pkg.Pkg}

for _, block := range fn.Blocks {
for _, instr := range block.Instrs {
call, ok := instr.(*ssa.Call)
if !ok {
continue
}
if got := ctx.gcRootCount(call); got != 0 {
t.Fatalf("function address root count = %d, want 0", got)
}
return
}
}
t.Fatal("function address call not found")
}

func buildGCRootSSAFunction(t *testing.T, src string) *ssa.Function {
t.Helper()
fset := token.NewFileSet()
Expand Down
19 changes: 14 additions & 5 deletions cl/instr.go
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,9 @@ func (p *context) shouldTrackCallerFrames() bool {
return false
}
if target := p.prog.Target(); target != nil && (target.Target != "" || target.GOARCH == "wasm") {
return false
if !p.prog.WasmResumeABIEnabled() {
return false
}
}
return canTrackCallerFramesForPackage(p.pkg.Path())
}
Expand Down Expand Up @@ -1485,12 +1487,19 @@ func (p *context) runtimeCallerFrameName() string {
// (PushCallerLocationFrame / RecordCallerLocation / RecordPanicLocation).
// The FP-chain unwinder supersedes them: physical pcs resolve through the
// prebuilt ftab and pcline labels, so tracked functions keep only noinline,
// no-tail-call and the label records. The emitters stay for one release as
// an escape hatch (LLGO_SHADOW_STACK=1).
// no-tail-call and the label records. The resumable wasm ABI has no FP
// unwinder, so packages that use runtime stack APIs retain this fallback.
var emitShadowStackInstrumentation = os.Getenv("LLGO_SHADOW_STACK") == "1"

func (p *context) shouldEmitShadowStackInstrumentation() bool {
if emitShadowStackInstrumentation {
return true
}
return p.prog.WasmResumeABIEnabled()
}

func (p *context) pushCallerLocationFrame(b llssa.Builder, fn *ssa.Function) {
if !emitShadowStackInstrumentation {
if !p.shouldEmitShadowStackInstrumentation() {
return
}
if fn == nil {
Expand All @@ -1516,7 +1525,7 @@ func (p *context) recordPanicLocation(b llssa.Builder, pos token.Pos) {
}

func (p *context) recordRuntimeLocation(b llssa.Builder, pos token.Pos, fn string) {
if !emitShadowStackInstrumentation || !p.shouldTrackCallerFrames() {
if !p.shouldEmitShadowStackInstrumentation() || !p.shouldTrackCallerFrames() {
return
}
position := p.fset.Position(pos)
Expand Down
5 changes: 3 additions & 2 deletions internal/build/source_patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ func logPackageErrors(t *testing.T, pkg *packages.Package, seen map[string]bool)
}
}

func TestWasmBytealgSourcePatchReplacesAsm(t *testing.T) {
for _, pkgPath := range []string{"internal/bytealg", "internal/chacha8rand", "internal/runtime/atomic"} {
func TestWasmSourcePatchReplacesAsm(t *testing.T) {
for _, pkgPath := range []string{"internal/bytealg", "internal/chacha8rand", "internal/runtime/atomic", "math"} {
if !llruntime.HasSourcePatchPkg(pkgPath) {
t.Fatalf("%s should be registered as a source patch package", pkgPath)
}
Expand All @@ -111,6 +111,7 @@ func TestWasmBytealgSourcePatchReplacesAsm(t *testing.T) {
"internal/bytealg/indexbyte_wasm.s",
"internal/chacha8rand/chacha8_stub.s",
"internal/runtime/atomic/atomic_wasm.s",
"math/floor_wasm.s",
} {
path := filepath.Join(runtime.GOROOT(), "src", filepath.FromSlash(file))
if got := string(overlay[path]); got != "// replaced by LLGo source patch\n" {
Expand Down
7 changes: 7 additions & 0 deletions internal/build/testdata/wasm-resume-acceptance/abi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <stdint.h>

extern int32_t wasm_acceptance_export(int32_t value);

int32_t llgo_call_wasm_acceptance_export(int32_t value) {
return wasm_acceptance_export(value);
}
8 changes: 8 additions & 0 deletions internal/build/testdata/wasm-resume-acceptance/abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

import _ "unsafe"

const LLGoFiles = "abi.c"

//go:linkname callWasmAcceptanceExport C.llgo_call_wasm_acceptance_export
func callWasmAcceptanceExport(int32) int32
Loading
Loading