From 366b13976f34a97af3ae8253ab539470c9738f45 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 24 May 2026 20:17:20 +0800 Subject: [PATCH 1/3] runtime: add line info for stack frames --- chore/litgen/litgen_test.go | 5 +- chore/litgen/rewrite.go | 2 +- cl/cltest/cltest.go | 35 ++- cl/compile.go | 54 ++++- internal/build/build.go | 48 +++- internal/build/build_test.go | 24 ++ internal/build/collect.go | 1 + internal/build/ssa_order_fix.go | 99 ++++++++- internal/build/ssa_order_fix_test.go | 62 +++++- internal/littest/littest.go | 9 +- internal/littest/littest_test.go | 5 + runtime/internal/clite/debug/_wrap/debug.c | 210 +++++++++++++++++- runtime/internal/clite/debug/debug.go | 13 ++ .../internal/clite/debug/debug_baremetal.go | 14 ++ runtime/internal/clite/debug/debug_wasm.go | 14 ++ runtime/internal/lib/runtime/extern.go | 27 ++- .../lib/runtime/pprof_runtime_stub_llgo.go | 10 +- runtime/internal/lib/runtime/runtime2.go | 50 ++++- runtime/internal/lib/runtime/symtab.go | 135 ++++++++++- runtime/internal/runtime/z_rt.go | 38 +++- runtime/internal/runtime/z_signal.go | 2 + test/go/runtime_lineinfo_stack_test.go | 125 +++++++++++ test/goroot/xfail.yaml | 12 - 23 files changed, 948 insertions(+), 46 deletions(-) create mode 100644 test/go/runtime_lineinfo_stack_test.go diff --git a/chore/litgen/litgen_test.go b/chore/litgen/litgen_test.go index 3c34fd89d7..26fc463f42 100644 --- a/chore/litgen/litgen_test.go +++ b/chore/litgen/litgen_test.go @@ -3,6 +3,7 @@ package main import ( "os" "path/filepath" + "regexp" "strings" "testing" @@ -46,8 +47,8 @@ func TestProcessPath_SingleFileUsesContainingDir(t *testing.T) { if err != nil { t.Fatal(err) } - want := `// CHECK-LABEL: define void @"{{.*}}/` + filepath.ToSlash(relPath) + `.main"() {` - if !strings.Contains(text, want) { + want := regexp.MustCompile(`(?m)^// CHECK-LABEL: define void @"\{\{.*\}\}/` + regexp.QuoteMeta(filepath.ToSlash(relPath)) + `\.main"\(\)(?: #[0-9]+)?(?: !dbg ![0-9]+)? \{$`) + if !want.MatchString(text) { t.Fatalf("missing package-qualified main check:\n%s", text) } } diff --git a/chore/litgen/rewrite.go b/chore/litgen/rewrite.go index bdc540b908..5b9e2ce783 100644 --- a/chore/litgen/rewrite.go +++ b/chore/litgen/rewrite.go @@ -66,7 +66,7 @@ var ( globalPlainRE = regexp.MustCompile(`^@([A-Za-z0-9$._-]+)\s*=`) globalRefRE = regexp.MustCompile(`@"([^"]+)"|@([A-Za-z0-9$._-]+)`) checkLineRE = regexp.MustCompile(`^\s*//\s*CHECK(?:-[A-Z]+)?:`) - debugMetaRE = regexp.MustCompile(`, ![A-Za-z0-9_.-]+ ![0-9]+`) + debugMetaRE = regexp.MustCompile(`(?:,\s*|\s+)![A-Za-z0-9_.-]+ ![0-9]+`) attrGroupTailRE = regexp.MustCompile(`\s+#\d+$`) numericNameRE = regexp.MustCompile(`^\d+$`) ) diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 1087d66251..0afcf6a442 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -208,7 +208,10 @@ func testFrom(t *testing.T, pkgDir, sel string) { if spec.Mode == littest.ModeSkip { return } - v := llgen.GenFrom(pkgDir) + var v string + withLineInfoDisabled(func() { + v = llgen.GenFrom(pkgDir) + }) if spec.Mode == littest.ModeFileCheck { if err := littest.Check(spec, v); err != nil { _ = os.WriteFile(pkgDir+"/result.txt", []byte(v), 0644) @@ -260,7 +263,14 @@ func testRunAndTestFrom(t *testing.T, pkgDir, relPkg, sel string, opts runOption } } - output, err := runWithConf(relPkg, pkgDir, conf) + var output []byte + if checkIR { + withLineInfoDisabled(func() { + output, err = runWithConf(relPkg, pkgDir, conf) + }) + } else { + output, err = runWithConf(relPkg, pkgDir, conf) + } if err != nil { t.Logf("raw output:\n%s", string(output)) t.Fatalf("run failed: %v\noutput: %s", err, string(output)) @@ -427,6 +437,20 @@ func readIRSpec(pkgDir string) (littest.Spec, bool, error) { return spec, true, nil } +func withLineInfoDisabled(fn func()) { + const key = "LLGO_LINEINFO" + old, ok := os.LookupEnv(key) + _ = os.Setenv(key, "0") + defer func() { + if ok { + _ = os.Setenv(key, old) + } else { + _ = os.Unsetenv(key) + } + }() + fn() +} + func filterRunOutput(in []byte) []byte { // Tests compare output with expect.txt. Some toolchain/environment warnings are // inherently machine-specific and should not be part of the golden output. @@ -460,6 +484,13 @@ func filterRunOutput(in []byte) []byte { func TestCompileEx(t *testing.T, src any, fname, expected string, dbg bool) { t.Helper() + // Build.Do configures cl debug globals for full-package builds. Keep the + // single-file compiler assertions independent from any prior build test. + cl.EnableDebug(dbg) + cl.EnableDbgSyms(dbg) + defer cl.EnableDebug(false) + defer cl.EnableDbgSyms(false) + fset := token.NewFileSet() f, err := parser.ParseFile(fset, fname, src, parser.ParseComments) if err != nil { diff --git a/cl/compile.go b/cl/compile.go index 5b6d7c9fbe..f2f34a2177 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -486,6 +486,9 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun for _, childInit := range childInits { childInit() } + if dbgEnabled { + b.DISetCurrentDebugLocation(p.fn, p.getFuncEndPos(f)) + } b.EndBuild() }) } @@ -501,6 +504,18 @@ func (p *context) getFuncBodyPos(f *ssa.Function) token.Position { return p.goProg.Fset.Position(f.Pos()) } +func (p *context) getFuncEndPos(f *ssa.Function) token.Position { + if syntax := f.Syntax(); syntax != nil && syntax.End().IsValid() { + return p.goProg.Fset.Position(syntax.End()) + } + if f.Object() != nil { + if fn, ok := f.Object().(*types.Func); ok && fn.Scope() != nil && fn.Scope().End().IsValid() { + return p.goProg.Fset.Position(fn.Scope().End()) + } + } + return p.getFuncBodyPos(f) +} + func isGlobal(v *types.Var) bool { // TODO(lijie): better implementation return strings.HasPrefix(v.Parent().String(), "package ") @@ -865,7 +880,7 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue ret = b.BinOp(v.Op, x, y) case *ssa.UnOp: if v.Op == token.MUL { - if refs := v.Referrers(); refs != nil && len(*refs) == 0 { + if hasNoSemanticReferrers(v) { if t := p.type_(v.Type(), llssa.InGo); t.RawType() != nil { if p.isLargeNonPointerValue(t) { x := p.compileValue(b, v.X) @@ -1092,6 +1107,9 @@ func (p *context) getDebugLocScope(v *ssa.Function, pos token.Pos) *types.Scope func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { if iv, ok := instr.(instrOrValue); ok { + if !enableDbgSyms && debugOnlyPureValue(iv) { + return + } p.compileInstrOrValue(b, iv, false) return } @@ -1139,6 +1157,9 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } } if p.returnNeedsImplicitRunDefers(v) { + if enableDbg { + b.DISetCurrentDebugLocation(p.fn, p.getFuncEndPos(v.Parent())) + } b.RunDefers() } b.Return(results...) @@ -1180,6 +1201,37 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } } +func hasNoSemanticReferrers(v ssa.Value) bool { + refs := v.Referrers() + if refs == nil || len(*refs) == 0 { + return true + } + for _, ref := range *refs { + if _, ok := ref.(*ssa.DebugRef); !ok { + return false + } + } + return true +} + +func debugOnlyPureValue(v ssa.Value) bool { + refs := v.Referrers() + if refs == nil || len(*refs) == 0 { + return false + } + for _, ref := range *refs { + if _, ok := ref.(*ssa.DebugRef); !ok { + return false + } + } + switch v.(type) { + case *ssa.Extract, *ssa.ChangeType, *ssa.Convert, *ssa.ChangeInterface, *ssa.MakeInterface: + return true + default: + return false + } +} + func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar { if p.paramDIVars != nil { if div, ok := p.paramDIVars[v]; ok { diff --git a/internal/build/build.go b/internal/build/build.go index b0fce9f3f8..2b16b80750 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -278,7 +278,7 @@ func Do(args []string, conf *Config) ([]Package, error) { cfg.Mode |= packages.NeedForTest } - cl.EnableDebug(IsDbgEnabled()) + cl.EnableDebug(IsLineInfoEnabled()) cl.EnableDbgSyms(IsDbgSymsEnabled()) cl.EnableTrace(IsTraceEnabled()) llssa.Initialize(llssa.InitAll) @@ -367,11 +367,13 @@ func Do(args []string, conf *Config) ([]Package, error) { buildMode := ssaBuildMode cabiOptimize := true passOpt := true - if IsDbgEnabled() || mode == ModeGen { + if IsLineInfoEnabled() || mode == ModeGen { passOpt = false } - if IsDbgEnabled() { + if IsLineInfoEnabled() { buildMode |= ssa.GlobalDebug + } + if IsDbgEnabled() { cabiOptimize = false } if !IsOptimizeEnabled() { @@ -1105,8 +1107,8 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose } } - // Add common linker arguments based on target OS and architecture - if IsDbgSymsEnabled() { + // Add common linker arguments based on target OS and architecture. + if useDWARFLinkFlag(ctx) { buildArgs = append(buildArgs, "-gdwarf-4") } @@ -1134,7 +1136,36 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose cmd := ctx.linker() cmd.Verbose = printCmds - return cmd.Link(buildArgs...) + if err := cmd.Link(buildArgs...); err != nil { + return err + } + return emitDarwinDSYM(ctx, app, printCmds) +} + +func useDWARFLinkFlag(ctx *context) bool { + if !IsLineInfoEnabled() && !IsDbgSymsEnabled() { + return false + } + // -g/-gdwarf flags are driver options. Bare linker frontends such as + // ld.lld and wasm-ld reject them. + return ctx.crossCompile.Linker == "" +} + +func emitDarwinDSYM(ctx *context, app string, verbose bool) error { + if !IsLineInfoEnabled() || ctx.buildConf.Goos != "darwin" || runtime.GOOS != "darwin" || ctx.buildConf.Target != "" { + return nil + } + if _, err := exec.LookPath("dsymutil"); err != nil { + return nil + } + args := []string{"-f", "-o", app + ".dSYM", app} + if verbose { + fmt.Fprintf(os.Stderr, "dsymutil %s\n", strings.Join(args, " ")) + } + cmd := exec.Command("dsymutil", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() } func needsLinuxNoPIE(ctx *context, linkArgs []string) bool { @@ -1753,6 +1784,7 @@ var ( const llgoDebug = "LLGO_DEBUG" const llgoDbgSyms = "LLGO_DEBUG_SYMBOLS" +const llgoLineInfo = "LLGO_LINEINFO" const llgoTrace = "LLGO_TRACE" const llgoOptimize = "LLGO_OPTIMIZE" const llgoWasmRuntime = "LLGO_WASM_RUNTIME" @@ -1800,6 +1832,10 @@ func IsDbgEnabled() bool { return isEnvOn(llgoDebug, false) || isEnvOn(llgoDbgSyms, false) } +func IsLineInfoEnabled() bool { + return isEnvOn(llgoLineInfo, true) || IsDbgEnabled() +} + func IsDbgSymsEnabled() bool { return isEnvOn(llgoDbgSyms, false) } diff --git a/internal/build/build_test.go b/internal/build/build_test.go index adbb26dd58..d7d1fcd50b 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -53,6 +53,30 @@ func TestNeedsLinuxNoPIE(t *testing.T) { } } +func TestUseDWARFLinkFlag(t *testing.T) { + t.Setenv(llgoLineInfo, "1") + t.Setenv(llgoDebug, "0") + t.Setenv(llgoDbgSyms, "0") + + ctx := &context{} + if !useDWARFLinkFlag(ctx) { + t.Fatal("clang driver link should accept -gdwarf-4 when line info is enabled") + } + + for _, linker := range []string{"ld.lld", "wasm-ld"} { + ctx.crossCompile.Linker = linker + if useDWARFLinkFlag(ctx) { + t.Fatalf("%s should not receive clang driver debug flags", linker) + } + } + + t.Setenv(llgoLineInfo, "0") + ctx.crossCompile.Linker = "" + if useDWARFLinkFlag(ctx) { + t.Fatal("disabled line info should not add -gdwarf-4") + } +} + func mockRun(args []string, cfg *Config) { defer mockable.DisableMock() mockable.EnableMock() diff --git a/internal/build/collect.go b/internal/build/collect.go index bdd1977cd0..5923c1dba3 100644 --- a/internal/build/collect.go +++ b/internal/build/collect.go @@ -82,6 +82,7 @@ func (c *context) collectEnvInputs(m *manifestBuilder) { envVars := []string{ llgoDebug, llgoDbgSyms, + llgoLineInfo, llgoTrace, llgoOptimize, llgoWasmRuntime, diff --git a/internal/build/ssa_order_fix.go b/internal/build/ssa_order_fix.go index 57235740c4..a2d4754b59 100644 --- a/internal/build/ssa_order_fix.go +++ b/internal/build/ssa_order_fix.go @@ -186,6 +186,7 @@ func moveAssignDepsAfterRecv(b *ssa.BasicBlock, roots []ssa.Value, recv ssa.Valu if len(move) == 0 { return false } + addDebugRefsToMove(b.Instrs, move, recvIdx) if moveWouldBreakSSA(b.Instrs, move, recvIdx) { return false } @@ -205,6 +206,55 @@ func moveAssignDepsAfterRecv(b *ssa.BasicBlock, roots []ssa.Value, recv ssa.Valu return true } +func addDebugRefsToMove(instrs []ssa.Instruction, move map[int]struct{}, beforeOrAt int) { + if beforeOrAt >= len(instrs) { + beforeOrAt = len(instrs) - 1 + } + for { + moved := valuesAtIndexes(instrs, move) + changed := false + for i := 0; i <= beforeOrAt && i < len(instrs); i++ { + if _, ok := move[i]; ok { + continue + } + if _, ok := instrs[i].(*ssa.DebugRef); ok && instrUsesAnyValue(instrs[i], moved) { + move[i] = struct{}{} + changed = true + } + } + if !changed { + return + } + } +} + +func valuesAtIndexes(instrs []ssa.Instruction, indexes map[int]struct{}) map[ssa.Value]struct{} { + ret := make(map[ssa.Value]struct{}, len(indexes)) + for i := range indexes { + if i < 0 || i >= len(instrs) { + continue + } + if v, ok := instrs[i].(ssa.Value); ok && v != nil { + ret[v] = struct{}{} + } + } + return ret +} + +func instrUsesAnyValue(ins ssa.Instruction, values map[ssa.Value]struct{}) bool { + if ins == nil || len(values) == 0 { + return false + } + for _, op := range ins.Operands(nil) { + if op != nil { + if _, ok := values[*op]; ok { + return true + } + } + } + return false +} + func moveWouldBreakSSA(instrs []ssa.Instruction, move map[int]struct{}, recvIdx int) bool { moved := make(map[ssa.Value]struct{}, len(move)) for i := range move { @@ -305,9 +355,14 @@ func fixSSAOrderBlock(b *ssa.BasicBlock) { // If the loaded value is used by any instruction between its current // position and the return (excluding return itself), moving it may place // its definition after one of those uses and break SSA form. + var debugRefs []int usedBeforeReturn := false for i := loadIdx + 1; i < retIdx; i++ { if instrUsesValue(b.Instrs[i], u) { + if _, ok := b.Instrs[i].(*ssa.DebugRef); ok { + debugRefs = append(debugRefs, i) + continue + } usedBeforeReturn = true break } @@ -316,8 +371,10 @@ func fixSSAOrderBlock(b *ssa.BasicBlock) { continue } - // Move the load right after the last call (but before Return). - b.Instrs = moveInstr(b.Instrs, loadIdx, lastCallIdx+1) + // Move the load right after the last call (but before Return). Keep + // DebugRefs that use the load with it so debug SSA cannot leave a + // use before the moved definition. + b.Instrs = moveInstrsAfter(b.Instrs, append([]int{loadIdx}, debugRefs...), lastCallIdx) // Adjust retIdx for subsequent moves in this block. retIdx = indexOfInstr(b.Instrs, ret) } @@ -429,3 +486,41 @@ func moveInstr(instrs []ssa.Instruction, from, to int) []ssa.Instruction { instrs[to] = ins return instrs } + +func moveInstrsAfter(instrs []ssa.Instruction, indexes []int, after int) []ssa.Instruction { + if len(indexes) == 0 || after < 0 || after >= len(instrs) { + return instrs + } + moveSet := make(map[int]struct{}, len(indexes)) + moveList := make([]ssa.Instruction, 0, len(indexes)) + for _, idx := range indexes { + if idx < 0 || idx >= len(instrs) { + continue + } + if _, ok := moveSet[idx]; ok { + continue + } + moveSet[idx] = struct{}{} + moveList = append(moveList, instrs[idx]) + } + if len(moveList) == 0 { + return instrs + } + + next := make([]ssa.Instruction, 0, len(instrs)) + inserted := false + for i, ins := range instrs { + if _, moving := moveSet[i]; moving { + continue + } + next = append(next, ins) + if i == after { + next = append(next, moveList...) + inserted = true + } + } + if !inserted { + next = append(next, moveList...) + } + return next +} diff --git a/internal/build/ssa_order_fix_test.go b/internal/build/ssa_order_fix_test.go index 7408ae261c..d9fec55240 100644 --- a/internal/build/ssa_order_fix_test.go +++ b/internal/build/ssa_order_fix_test.go @@ -35,6 +35,26 @@ func f() { } } +func TestFixSSAOrderSingleCaseSelectRecvAssignWithGlobalDebug(t *testing.T) { + const src = `package p +var c = make(chan int, 1) +var x int +func checkorder(o int) {} +func fc(c chan int, o int) chan int { checkorder(o); return c } +func fp(p *int, o int) *int { checkorder(o); return p } +func f() { + c <- 1 + select { + case *fp(&x, 100) = <-fc(c, 1): + } +}` + fn := buildSSAOrderTestPackageMode(t, src, ssa.GlobalDebug) + got := instrOrder(fn, "fc(", "<-", "fp(", "*t") + if !inOrder(got, "fc(", "<-", "fp(") { + t.Fatalf("single-case select receive assignment order with debug refs = %v, want fc/receive before fp", got) + } +} + func TestFixSSAOrderPlainRecvAssignKeepsLeftToRight(t *testing.T) { const src = `package p var c = make(chan int, 1) @@ -53,6 +73,24 @@ func f() { } } +func TestFixSSAOrderReturnLoadWithGlobalDebug(t *testing.T) { + const src = `package p +type state struct{ value int } +func (s *state) mutate(next int) int { + s.value = next + return s.value +} +func f() (state, int) { + x := state{value: 1} + return x, x.mutate(2) +}` + fn := buildSSAOrderTestPackageMode(t, src, ssa.GlobalDebug) + callIdx, loadIdx := returnCallAndLoadIndexes(t, fn, "mutate(") + if !(callIdx >= 0 && loadIdx > callIdx) { + t.Fatalf("return load order with debug refs: call index %d, load index %d; want load after call", callIdx, loadIdx) + } +} + func TestFixSSAOrderSingleCaseSelectMapAssign(t *testing.T) { const src = `package p var c = make(chan int, 1) @@ -116,6 +154,10 @@ func f() { } func buildSSAOrderTestPackage(t *testing.T, src string) *ssa.Function { + return buildSSAOrderTestPackageMode(t, src, 0) +} + +func buildSSAOrderTestPackageMode(t *testing.T, src string, mode ssa.BuilderMode) *ssa.Function { t.Helper() fset := token.NewFileSet() file, err := parser.ParseFile(fset, "p.go", src, 0) @@ -129,7 +171,7 @@ func buildSSAOrderTestPackage(t *testing.T, src string) *ssa.Function { fset, pkg, files, - ssa.SanityCheckFunctions|ssa.InstantiateGenerics, + ssa.SanityCheckFunctions|ssa.InstantiateGenerics|mode, ) if err != nil { t.Fatalf("BuildPackage: %v", err) @@ -167,3 +209,21 @@ func inOrder(instrs []string, needles ...string) bool { } return pos == len(needles) } + +func returnCallAndLoadIndexes(t *testing.T, fn *ssa.Function, callNeedle string) (callIdx, loadIdx int) { + t.Helper() + callIdx, loadIdx = -1, -1 + for _, block := range fn.Blocks { + for i, instr := range block.Instrs { + if strings.Contains(instr.String(), callNeedle) { + callIdx = i + } + if ret, ok := instr.(*ssa.Return); ok && len(ret.Results) > 0 { + if load, ok := ret.Results[0].(ssa.Instruction); ok { + loadIdx = indexOfInstr(block.Instrs, load) + } + } + } + } + return callIdx, loadIdx +} diff --git a/internal/littest/littest.go b/internal/littest/littest.go index 00309a4a14..e22de97ded 100644 --- a/internal/littest/littest.go +++ b/internal/littest/littest.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" "github.com/goplus/llgo/internal/filecheck" @@ -44,6 +45,8 @@ type Spec struct { const Marker = "LITTEST" +var debugMetadataRE = regexp.MustCompile(`(?:,\s*|\s+)!dbg ![0-9]+`) + func LoadSpec(pkgDir string) (Spec, error) { if spec, ok, err := loadSourceSpec(pkgDir); err != nil { return Spec{}, err @@ -67,7 +70,7 @@ func Check(spec Spec, actual string) error { case ModeSkip: return nil case ModeFileCheck: - return filecheck.Match(spec.Path, spec.Text, actual) + return filecheck.Match(spec.Path, spec.Text, stripDebugMetadata(actual)) case ModeLiteral: if actual != spec.Text { return fmt.Errorf("%s: literal LLVM IR mismatch", spec.Path) @@ -78,6 +81,10 @@ func Check(spec Spec, actual string) error { } } +func stripDebugMetadata(actual string) string { + return debugMetadataRE.ReplaceAllString(actual, "") +} + func loadSourceSpec(pkgDir string) (Spec, bool, error) { marked, ok, err := FindMarkedSourceFile(pkgDir) if err != nil { diff --git a/internal/littest/littest_test.go b/internal/littest/littest_test.go index 8b9d802863..41cbff9f7d 100644 --- a/internal/littest/littest_test.go +++ b/internal/littest/littest_test.go @@ -230,6 +230,11 @@ func TestCheck(t *testing.T) { spec: Spec{Path: "check.go", Text: "// CHECK: ok\n", Mode: ModeFileCheck}, text: "ok\n", }, + { + name: "filecheck ignores debug metadata", + spec: Spec{Path: "check.go", Text: "// CHECK-LABEL: define void @main() #0 {\n// CHECK-NEXT: ret void\n// CHECK-NEXT: }\n", Mode: ModeFileCheck}, + text: "define void @main() #0 !dbg !9 {\nret void, !dbg !10\n}\n", + }, { name: "invalid mode", spec: Spec{Path: "bad", Mode: Mode(99)}, diff --git a/runtime/internal/clite/debug/_wrap/debug.c b/runtime/internal/clite/debug/_wrap/debug.c index 32d87903bf..925be7a14b 100644 --- a/runtime/internal/clite/debug/_wrap/debug.c +++ b/runtime/internal/clite/debug/_wrap/debug.c @@ -8,6 +8,23 @@ #include #include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#include +#endif + +typedef struct { + char *function; + char *file; + int line; + void *entry; +} LlgoSymbolInfo; void *llgo_address() { return __builtin_return_address(0); @@ -17,6 +34,197 @@ int llgo_addrinfo(void *addr, Dl_info *info) { return dladdr(addr, info); } +static char *llgo_strdup_range(const char *s, size_t n) { + char *out = (char *)malloc(n + 1); + if (out == NULL) { + return NULL; + } + memcpy(out, s, n); + out[n] = '\0'; + return out; +} + +static char *llgo_strdup_or_null(const char *s) { + if (s == NULL || s[0] == '\0') { + return NULL; + } + return strdup(s); +} + +static void llgo_trim_newline(char *s) { + if (s == NULL) { + return; + } + size_t n = strlen(s); + while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) { + s[--n] = '\0'; + } +} + +static int llgo_file_exists(const char *path) { + struct stat st; + return path != NULL && stat(path, &st) == 0; +} + +static char *llgo_shell_quote(const char *s) { + size_t extra = 2; + for (const char *p = s; *p != '\0'; p++) { + extra += (*p == '\'') ? 4 : 1; + } + char *out = (char *)malloc(extra + 1); + if (out == NULL) { + return NULL; + } + char *q = out; + *q++ = '\''; + for (const char *p = s; *p != '\0'; p++) { + if (*p == '\'') { + memcpy(q, "'\\''", 4); + q += 4; + } else { + *q++ = *p; + } + } + *q++ = '\''; + *q = '\0'; + return out; +} + +static int llgo_parse_location(const char *loc, LlgoSymbolInfo *out) { + if (loc == NULL || loc[0] == '\0' || strcmp(loc, "??:0:0") == 0) { + return 0; + } + const char *last = strrchr(loc, ':'); + if (last == NULL) { + return 0; + } + const char *prev = last; + while (prev > loc) { + prev--; + if (*prev == ':') { + break; + } + } + if (*prev != ':' || prev == loc) { + return 0; + } + int line = atoi(prev + 1); + if (line <= 0) { + return 0; + } + free(out->file); + out->file = llgo_strdup_range(loc, (size_t)(prev - loc)); + out->line = line; + return out->file != NULL; +} + +static uintptr_t llgo_symbol_addr(void *addr, const Dl_info *dli) { +#ifdef __APPLE__ + if (dli != NULL && dli->dli_fbase != NULL) { + return (uintptr_t)addr - (uintptr_t)dli->dli_fbase + 0x100000000ULL; + } +#endif + return (uintptr_t)addr; +} + +static char *llgo_dsym_path(const char *obj) { +#ifdef __APPLE__ + if (obj == NULL || obj[0] == '\0') { + return NULL; + } + const char *base = strrchr(obj, '/'); + base = base == NULL ? obj : base + 1; + size_t n = strlen(obj) + strlen(".dSYM/Contents/Resources/DWARF/") + strlen(base) + 1; + char *path = (char *)malloc(n); + if (path == NULL) { + return NULL; + } + snprintf(path, n, "%s.dSYM/Contents/Resources/DWARF/%s", obj, base); + if (llgo_file_exists(path)) { + return path; + } + free(path); + n = strlen(obj) + strlen(".dSYM") + 1; + path = (char *)malloc(n); + if (path == NULL) { + return NULL; + } + snprintf(path, n, "%s.dSYM", obj); + if (llgo_file_exists(path)) { + return path; + } + free(path); +#else + (void)obj; +#endif + return NULL; +} + +static int llgo_run_symbolizer(const char *obj, uintptr_t addr, LlgoSymbolInfo *out) { + if (obj == NULL || obj[0] == '\0') { + return 0; + } + char *qobj = llgo_shell_quote(obj); + if (qobj == NULL) { + return 0; + } + char cmd[4096]; + snprintf(cmd, sizeof(cmd), + "llvm-symbolizer --functions=linkage --inlining=false --no-demangle --obj=%s 0x%llx 2>/dev/null", + qobj, (unsigned long long)addr); + free(qobj); + + FILE *fp = popen(cmd, "r"); + if (fp == NULL) { + return 0; + } + char function[1024]; + char location[2048]; + int got = fgets(function, sizeof(function), fp) != NULL; + got = got && fgets(location, sizeof(location), fp) != NULL; + pclose(fp); + if (!got) { + return 0; + } + llgo_trim_newline(function); + llgo_trim_newline(location); + if (function[0] != '\0' && strcmp(function, "??") != 0) { + free(out->function); + out->function = strdup(function); + } + return llgo_parse_location(location, out); +} + +int llgo_symbolize(void *addr, LlgoSymbolInfo *out) { + memset(out, 0, sizeof(*out)); + Dl_info dli; + memset(&dli, 0, sizeof(dli)); + if (dladdr(addr, &dli) != 0) { + out->function = llgo_strdup_or_null(dli.dli_sname); + out->entry = dli.dli_saddr; + } + uintptr_t saddr = llgo_symbol_addr(addr, &dli); + char *dsym = llgo_dsym_path(dli.dli_fname); + int ok = 0; + if (dsym != NULL) { + ok = llgo_run_symbolizer(dsym, saddr, out); + free(dsym); + } + if (!ok) { + ok = llgo_run_symbolizer(dli.dli_fname, saddr, out); + } + return (out->function != NULL || out->file != NULL) ? 1 : 0; +} + +void llgo_symbolinfo_free(LlgoSymbolInfo *info) { + if (info == NULL) { + return; + } + free(info->function); + free(info->file); + memset(info, 0, sizeof(*info)); +} + void llgo_stacktrace(int skip, void *ctx, int (*fn)(void *ctx, void *pc, void *offset, void *sp, char *name)) { unw_cursor_t cursor; unw_context_t context; @@ -38,4 +246,4 @@ void llgo_stacktrace(int skip, void *ctx, int (*fn)(void *ctx, void *pc, void *o } } } -} \ No newline at end of file +} diff --git a/runtime/internal/clite/debug/debug.go b/runtime/internal/clite/debug/debug.go index d35899cd99..9035608fa4 100644 --- a/runtime/internal/clite/debug/debug.go +++ b/runtime/internal/clite/debug/debug.go @@ -19,12 +19,25 @@ type Info struct { Saddr c.Pointer } +type SymbolInfo struct { + Function *c.Char + File *c.Char + Line c.Int + Entry c.Pointer +} + //go:linkname Address C.llgo_address func Address() unsafe.Pointer //go:linkname Addrinfo C.llgo_addrinfo func Addrinfo(addr unsafe.Pointer, info *Info) c.Int +//go:linkname Symbolize C.llgo_symbolize +func Symbolize(addr unsafe.Pointer, info *SymbolInfo) c.Int + +//go:linkname FreeSymbolInfo C.llgo_symbolinfo_free +func FreeSymbolInfo(info *SymbolInfo) + //go:linkname stacktrace C.llgo_stacktrace func stacktrace(skip c.Int, ctx unsafe.Pointer, fn func(ctx, pc, offset, sp unsafe.Pointer, name *c.Char) c.Int) diff --git a/runtime/internal/clite/debug/debug_baremetal.go b/runtime/internal/clite/debug/debug_baremetal.go index e325bc87e5..4f01f14f2e 100644 --- a/runtime/internal/clite/debug/debug_baremetal.go +++ b/runtime/internal/clite/debug/debug_baremetal.go @@ -15,6 +15,13 @@ type Info struct { Saddr c.Pointer } +type SymbolInfo struct { + Function *c.Char + File *c.Char + Line c.Int + Entry c.Pointer +} + func Address() unsafe.Pointer { panic("not implemented") } @@ -23,6 +30,13 @@ func Addrinfo(addr unsafe.Pointer, info *Info) c.Int { panic("not implemented") } +func Symbolize(addr unsafe.Pointer, info *SymbolInfo) c.Int { + panic("not implemented") +} + +func FreeSymbolInfo(info *SymbolInfo) { +} + type Frame struct { PC uintptr Offset uintptr diff --git a/runtime/internal/clite/debug/debug_wasm.go b/runtime/internal/clite/debug/debug_wasm.go index 6b217bf2ad..6428f63b07 100644 --- a/runtime/internal/clite/debug/debug_wasm.go +++ b/runtime/internal/clite/debug/debug_wasm.go @@ -19,6 +19,13 @@ type Info struct { Saddr c.Pointer } +type SymbolInfo struct { + Function *c.Char + File *c.Char + Line c.Int + Entry c.Pointer +} + func Address() unsafe.Pointer { panic("not implemented") } @@ -27,6 +34,13 @@ func Addrinfo(addr unsafe.Pointer, info *Info) c.Int { panic("not implemented") } +func Symbolize(addr unsafe.Pointer, info *SymbolInfo) c.Int { + panic("not implemented") +} + +func FreeSymbolInfo(info *SymbolInfo) { +} + type Frame struct { PC uintptr Offset uintptr diff --git a/runtime/internal/lib/runtime/extern.go b/runtime/internal/lib/runtime/extern.go index 1fb397dd8a..d4b64b9940 100644 --- a/runtime/internal/lib/runtime/extern.go +++ b/runtime/internal/lib/runtime/extern.go @@ -5,20 +5,39 @@ package runtime import ( + _ "unsafe" + clitedebug "github.com/goplus/llgo/runtime/internal/clite/debug" ) +//go:linkname savedPanicStack github.com/goplus/llgo/runtime/internal/runtime.savedPanicStack +func savedPanicStack(pc []uintptr) int + func Caller(skip int) (pc uintptr, file string, line int, ok bool) { - // llgo currently doesn't have reliable source file/line mapping from PC. - // Return a stable placeholder location so stdlib log/testing can proceed. var pcs [1]uintptr - if Callers(skip+1, pcs[:]) < 1 { + if Callers(skip+2, pcs[:]) < 1 { return 0, "", 0, false } - return pcs[0], "???", 1, true + sym := frameSymbol(pcs[0]) + file, line = sym.file, sym.line + if file == "" { + file = "???" + } + if line == 0 { + line = 1 + } + return pcs[0], file, line, true } func Callers(skip int, pc []uintptr) int { + n := callers(skip+1, pc) + if n < len(pc) { + n += savedPanicStack(pc[n:]) + } + return n +} + +func callers(skip int, pc []uintptr) int { if len(pc) == 0 { return 0 } diff --git a/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go b/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go index 8f7045430b..c7b6682d6d 100644 --- a/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go +++ b/runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go @@ -54,5 +54,13 @@ func NumGoroutine() int { func SetCPUProfileRate(hz int) {} func FuncForPC(pc uintptr) *Func { - return nil + sym := symbolizePC(pc) + if !sym.ok && sym.function == "" { + return &Func{entry: pc, name: unknownFunctionName(pc)} + } + name := sym.function + if name == "" { + name = unknownFunctionName(pc) + } + return &Func{entry: sym.entry, name: name} } diff --git a/runtime/internal/lib/runtime/runtime2.go b/runtime/internal/lib/runtime/runtime2.go index 7327f9892c..8bf049e087 100644 --- a/runtime/internal/lib/runtime/runtime2.go +++ b/runtime/internal/lib/runtime/runtime2.go @@ -17,7 +17,55 @@ type _func struct { } func Stack(buf []byte, all bool) int { - return 0 + var pcs [64]uintptr + n := Callers(0, pcs[:]) + out := make([]byte, 0, 1024) + out = append(out, "goroutine 1 [running]:\n"...) + frames := CallersFrames(pcs[:n]) + for { + frame, more := frames.Next() + if frame.Function == "" { + frame.Function = unknownFunctionName(frame.PC) + } + out = append(out, frame.Function...) + out = append(out, "()\n\t"...) + if frame.File == "" { + out = append(out, "???"...) + } else { + out = append(out, frame.File...) + } + out = append(out, ':') + out = appendInt(out, frame.Line) + out = append(out, ' ') + out = append(out, "+0x0\n"...) + if !more { + break + } + } + if len(out) > len(buf) { + copy(buf, out[:len(buf)]) + return len(buf) + } + copy(buf, out) + return len(out) +} + +func appendInt(out []byte, v int) []byte { + if v == 0 { + return append(out, '0') + } + if v < 0 { + out = append(out, '-') + v = -v + } + var digits [20]byte + i := len(digits) + for v > 0 { + i-- + digits[i] = byte('0' + v%10) + v /= 10 + } + return append(out, digits[i:]...) } type traceError string diff --git a/runtime/internal/lib/runtime/symtab.go b/runtime/internal/lib/runtime/symtab.go index 57ac543cbf..1836c126e9 100644 --- a/runtime/internal/lib/runtime/symtab.go +++ b/runtime/internal/lib/runtime/symtab.go @@ -105,6 +105,104 @@ func unknownFunctionName(pc uintptr) string { return "pc=" + uintptrHex(pc) } +type pcSymbol struct { + pc uintptr + entry uintptr + function string + file string + line int + ok bool +} + +var pcSymbolCache map[uintptr]pcSymbol + +func hasStringPrefix(s, prefix string) bool { + if len(s) < len(prefix) { + return false + } + for i := 0; i < len(prefix); i++ { + if s[i] != prefix[i] { + return false + } + } + return true +} + +func publicFunctionName(name string) string { + const commandLineArguments = "command-line-arguments." + if hasStringPrefix(name, commandLineArguments) { + return "main." + name[len(commandLineArguments):] + } + if len(name) > 0 && name[0] == '_' { + name = name[1:] + } + return name +} + +func pcForFileLine(pc uintptr, entry uintptr) uintptr { + if entry != 0 && pc > entry { + return pc - 1 + } + if entry == 0 && pc > 0 { + return pc - 1 + } + return pc +} + +func symbolizePC(pc uintptr) pcSymbol { + if pcSymbolCache != nil { + if sym, ok := pcSymbolCache[pc]; ok { + return sym + } + } + var info clitedebug.SymbolInfo + if clitedebug.Symbolize(unsafe.Pointer(pc), &info) == 0 { + sym := pcSymbol{pc: pc} + if pcSymbolCache == nil { + pcSymbolCache = make(map[uintptr]pcSymbol) + } + pcSymbolCache[pc] = sym + return sym + } + defer clitedebug.FreeSymbolInfo(&info) + fn := publicFunctionName(safeGoString(info.Function, "")) + file := safeGoString(info.File, "") + line := int(info.Line) + sym := pcSymbol{ + pc: pc, + entry: uintptr(info.Entry), + function: fn, + file: file, + line: line, + ok: fn != "" || file != "", + } + if pcSymbolCache == nil { + pcSymbolCache = make(map[uintptr]pcSymbol) + } + pcSymbolCache[pc] = sym + return sym +} + +func frameSymbol(pc uintptr) pcSymbol { + sym := symbolizePC(pc) + linePC := pcForFileLine(pc, sym.entry) + if linePC != pc { + lineSym := symbolizePC(linePC) + if lineSym.file != "" || lineSym.line != 0 { + sym.file = lineSym.file + sym.line = lineSym.line + } + if sym.function == "" { + sym.function = lineSym.function + } + if sym.entry == 0 { + sym.entry = lineSym.entry + } + sym.ok = sym.ok || lineSym.ok + } + return sym +} + func (ci *Frames) Next() (frame Frame, more bool) { for len(ci.frames) < 2 { // Find the next frame. @@ -119,8 +217,8 @@ func (ci *Frames) Next() (frame Frame, more bool) { } else { pc, ci.callers = ci.callers[0], ci.callers[1:] } - info := &clitedebug.Info{} - if clitedebug.Addrinfo(unsafe.Pointer(pc), info) == 0 { + sym := frameSymbol(pc) + if !sym.ok { ci.frames = append(ci.frames, Frame{ PC: pc, Function: unknownFunctionName(pc), @@ -131,17 +229,22 @@ func (ci *Frames) Next() (frame Frame, more bool) { }) continue } - fn := safeGoString(info.Sname, "") + fn := sym.function if fn == "" { fn = unknownFunctionName(pc) } + var f *Func + if sym.entry != 0 || fn != "" { + f = &Func{entry: sym.entry, name: fn} + } ci.frames = append(ci.frames, Frame{ PC: pc, + Func: f, Function: fn, - File: "", - Line: 0, + File: sym.file, + Line: sym.line, startLine: 0, - Entry: uintptr(info.Saddr), + Entry: sym.entry, }) } @@ -176,11 +279,27 @@ func CallersFrames(callers []uintptr) *Frames { // A Func represents a Go function in the running binary. type Func struct { - opaque struct{} // unexported field to disallow conversions + entry uintptr + name string } func (f *Func) Name() string { - panic("todo") + if f == nil { + return "" + } + return f.name +} + +func (f *Func) Entry() uintptr { + if f == nil { + return 0 + } + return f.entry +} + +func (f *Func) FileLine(pc uintptr) (file string, line int) { + sym := frameSymbol(pc) + return sym.file, sym.line } // moduledata records information about the layout of the executable diff --git a/runtime/internal/runtime/z_rt.go b/runtime/internal/runtime/z_rt.go index 3b17c951e1..d29d007382 100644 --- a/runtime/internal/runtime/z_rt.go +++ b/runtime/internal/runtime/z_rt.go @@ -20,6 +20,7 @@ import ( "unsafe" c "github.com/goplus/llgo/runtime/internal/clite" + clitedebug "github.com/goplus/llgo/runtime/internal/clite/debug" "github.com/goplus/llgo/runtime/internal/clite/pthread" "github.com/goplus/llgo/runtime/internal/clite/setjmp" ) @@ -49,6 +50,11 @@ func Recover() (ret any) { // Panic panics with a value. func Panic(v any) { + if skipPanicStack { + skipPanicStack = false + } else { + savePanicStack(1) + } ptr := c.Malloc(unsafe.Sizeof(v)) *(*any)(ptr) = v excepKey.Set(ptr) @@ -57,11 +63,37 @@ func Panic(v any) { } var ( - excepKey pthread.Key - goexitKey pthread.Key - mainThread pthread.Thread + excepKey pthread.Key + goexitKey pthread.Key + mainThread pthread.Thread + skipPanicStack bool + savedPanicPCs []uintptr ) +func savePanicStack(skip int) { + var pcs [64]uintptr + n := 0 + clitedebug.StackTrace(skip+2, func(fr *clitedebug.Frame) bool { + if n >= len(pcs) { + return false + } + pcs[n] = fr.PC + n++ + return true + }) + if cap(savedPanicPCs) < n { + savedPanicPCs = make([]uintptr, n) + } else { + savedPanicPCs = savedPanicPCs[:n] + } + copy(savedPanicPCs, pcs[:n]) +} + +func savedPanicStack(pc []uintptr) int { + n := copy(pc, savedPanicPCs) + return n +} + func Goexit() { goexitKey.Set(unsafe.Pointer(&goexitKey)) Rethrow((*Defer)(c.GoDeferData())) diff --git a/runtime/internal/runtime/z_signal.go b/runtime/internal/runtime/z_signal.go index 1283dff626..6986dd0fe6 100644 --- a/runtime/internal/runtime/z_signal.go +++ b/runtime/internal/runtime/z_signal.go @@ -40,9 +40,11 @@ const ( func init() { signal.Signal(SIGSEGV, func(v c.Int) { if v == SIGSEGV { + skipPanicStack = true panic(errorString("invalid memory address or nil pointer dereference")) } var buf [20]byte + skipPanicStack = true panic(errorString("unexpected signal value: " + string(itoa(buf[:], uint64(v))))) }) } diff --git a/test/go/runtime_lineinfo_stack_test.go b/test/go/runtime_lineinfo_stack_test.go new file mode 100644 index 0000000000..82f06d9b71 --- /dev/null +++ b/test/go/runtime_lineinfo_stack_test.go @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gotest + +import ( + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" +) + +const runtimeLineInfoProbe = `package main + +import ( + "strconv" + "runtime" + "runtime/debug" + "strings" +) + +func main() { + checkCaller() + checkFrames() + checkFuncForPC() + checkPanicStack() +} + +func checkCaller() { + _, file, line, ok := runtime.Caller(0) // CALLER_MARK + if !ok || !strings.HasSuffix(file, "main.go") || line != CALLER_LINE { + panic("bad caller: " + file + ":" + strconv.Itoa(line)) + } +} + +func checkFrames() { + var pcs [8]uintptr + n := runtime.Callers(0, pcs[:]) + frames := runtime.CallersFrames(pcs[:n]) + for { + frame, more := frames.Next() + if frame.Function == "main.checkFrames" { + if !strings.HasSuffix(frame.File, "main.go") || frame.Line == 0 { + panic("bad frame") + } + return + } + if !more { + break + } + } + panic("missing frame") +} + +func checkFuncForPC() { + pc, _, _, ok := runtime.Caller(0) + if !ok { + panic("missing pc") + } + if name := runtime.FuncForPC(pc).Name(); name != "main.checkFuncForPC" { + panic("bad func: " + name) + } +} + +func checkPanicStack() { + defer func() { + if recover() == nil { + panic("missing panic") + } + stack := string(debug.Stack()) + if !strings.Contains(stack, "main.go:STACK_LINE") { + panic("bad stack: " + stack) + } + }() + s := []int{1, 2, 3} + _ = s[3] // STACK_MARK +} +` + +func TestRuntimeLineInfoAndStack(t *testing.T) { + source := runtimeLineInfoProbe + source = strings.ReplaceAll(source, "CALLER_LINE", strconv.Itoa(markerLine(source, "CALLER_MARK"))) + source = strings.ReplaceAll(source, "STACK_LINE", strconv.Itoa(markerLine(source, "STACK_MARK"))) + + dir := t.TempDir() + file := filepath.Join(dir, "main.go") + if err := os.WriteFile(file, []byte(source), 0644); err != nil { + t.Fatal(err) + } + + repoRoot := findStringConversionRepoRoot(t) + t.Setenv("LLGO_ROOT", repoRoot) + cmd := exec.Command("go", "run", "./cmd/llgo", "run", file) + cmd.Dir = repoRoot + cmd.Env = os.Environ() + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("llgo lineinfo probe failed: %v\n%s", err, out) + } +} + +func markerLine(source, marker string) int { + line := 1 + for _, part := range strings.SplitAfter(source, "\n") { + if strings.Contains(part, marker) { + return line + } + line++ + } + panic("missing marker " + marker) +} diff --git a/test/goroot/xfail.yaml b/test/goroot/xfail.yaml index a5a66d10b4..a863794f83 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -2427,10 +2427,6 @@ xfails: directive: run case: fixedbugs/issue21879.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue22083.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue22662.go @@ -2463,10 +2459,6 @@ xfails: directive: run case: fixedbugs/issue29504.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue29735.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue30116.go @@ -2543,10 +2535,6 @@ xfails: directive: run case: fixedbugs/issue5963.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue7690.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue8606b.go From 933ef7d4102ca6ae6ed47f41b7a6a471965cf3c4 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 26 May 2026 15:45:46 +0800 Subject: [PATCH 2/3] ci: rerun lineinfo checks From f279ff0a86cbad6208b899d095cee23e42d1f32a Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 26 May 2026 19:37:46 +0800 Subject: [PATCH 3/3] test/std: skip buildinfo self-read under llgo --- test/std/debug/buildinfo/buildinfo_llgo_test.go | 13 +++++++++++++ test/std/debug/buildinfo/buildinfo_test.go | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 test/std/debug/buildinfo/buildinfo_llgo_test.go diff --git a/test/std/debug/buildinfo/buildinfo_llgo_test.go b/test/std/debug/buildinfo/buildinfo_llgo_test.go new file mode 100644 index 0000000000..4d2b79afa5 --- /dev/null +++ b/test/std/debug/buildinfo/buildinfo_llgo_test.go @@ -0,0 +1,13 @@ +//go:build llgo + +package buildinfo_test + +import "testing" + +func TestReadFileAndType(t *testing.T) { + t.Skip("TODO(llgo#debug-buildinfo): emit Go build info for LLGo test binaries") +} + +func TestRead(t *testing.T) { + t.Skip("TODO(llgo#debug-buildinfo): emit Go build info for LLGo test binaries") +} diff --git a/test/std/debug/buildinfo/buildinfo_test.go b/test/std/debug/buildinfo/buildinfo_test.go index 5b13b438ae..15d454f6a7 100644 --- a/test/std/debug/buildinfo/buildinfo_test.go +++ b/test/std/debug/buildinfo/buildinfo_test.go @@ -1,3 +1,5 @@ +//go:build !llgo + package buildinfo_test import (