Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f33786d
fix(build): keep DebugRefs with reordered return loads
cpunion Jul 21, 2026
b7f864f
docs(build): document instruction move fallback
cpunion Jul 28, 2026
1644341
fix(build): preserve DebugRefs in select order repair
cpunion Jul 25, 2026
ea9f005
fix(build): stop DebugRef scan before receive
cpunion Jul 29, 2026
cb22f32
build(debug): run LLVM passes with DWARF
cpunion Jul 21, 2026
a3a231d
fix(debug): ignore DebugRefs during static init
cpunion Jul 22, 2026
6491c94
fix(debug): preserve defer locations under LTO
cpunion Jul 21, 2026
102550c
test(lto): cover DWARF-preserved string values
cpunion Jul 22, 2026
6cb5389
docs(ssa): clarify DWARF optimized marker
cpunion Jul 28, 2026
18e9edc
cabi: preserve debug variable homes during lowering
cpunion Jul 21, 2026
efbfa72
fix(pclntab): preserve Darwin line sites with DWARF
cpunion Jul 22, 2026
bee8834
test(pclntab): cover DWARF runtime line sites
cpunion Jul 22, 2026
1ef373f
build(debug): match cmd/link DWARF defaults
cpunion Jul 21, 2026
c2e5974
test(cl): keep IR fixtures stable under DWARF defaults
cpunion Jul 22, 2026
6ebec08
fix(debug): resolve anonymous function lexical scopes
cpunion Jul 22, 2026
cb1db81
fix(debug): preserve deferred call locations under LTO
cpunion Jul 22, 2026
2359379
fix(debug): reanchor cross-function defer locations
cpunion Jul 22, 2026
79eae66
test(cl): keep metadata captures DWARF-free
cpunion Jul 27, 2026
e528e8a
ci: allow primary DWARF shards more time
cpunion Jul 30, 2026
86d7de0
fix(build): stabilize Darwin debug-map archive paths
cpunion Jul 28, 2026
e511f3c
debug: integrate Go-compatible DWARF defaults
cpunion Aug 2, 2026
3329ad8
docs(debug): isolate the Asyncify DWARF blocker
cpunion Aug 2, 2026
3fd2c66
docs(debug): update native DWARF defaults
cpunion Aug 2, 2026
25a8eef
ci(debug): stabilize integrated DWARF gates
cpunion Aug 2, 2026
5336d70
fix(debug): keep aggregate parameter homes authoritative
cpunion Aug 2, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/browser-debug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ jobs:
run: |
set -euo pipefail
package=./internal/debugabi/testdata/fixture
GOOS=js GOARCH=wasm llgo build -debug-artifact=embedded \
GOOS=js GOARCH=wasm llgo build -O0 -debug-artifact=embedded \
-o "${RUNNER_TEMP}/browser-embedded.wasm" "${package}"
GOOS=js GOARCH=wasm llgo build -debug-artifact=external \
GOOS=js GOARCH=wasm llgo build -O0 -debug-artifact=external \
-o "${RUNNER_TEMP}/browser-external.wasm" "${package}"
test -s "${RUNNER_TEMP}/browser-embedded.wasm"
test -s "${RUNNER_TEMP}/browser-external.wasm"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ jobs:
test:
name: test (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }}, shard ${{ matrix.shard }})
continue-on-error: ${{ matrix.lane == 'compatibility' }}
timeout-minutes: ${{ startsWith(matrix.os, 'macos') && 45 || 30 }}
timeout-minutes: ${{ (startsWith(matrix.os, 'macos') || matrix.lane == 'primary') && 45 || 30 }}
strategy:
matrix:
# Keep compatibility and primary toolchains pinned to exact patches.
Expand Down
1 change: 1 addition & 0 deletions cl/_testlto/defer_dwarf/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[2 1]
19 changes: 19 additions & 0 deletions cl/_testlto/defer_dwarf/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import "fmt"

func record(values *[]int, value int) {
*values = append(*values, value)
}

func deferredValues(addSecond bool) (values []int) {
defer record(&values, 1)
if addSecond {
defer record(&values, 2)
}
return
}

func main() {
fmt.Println(deferredValues(true))
}
8 changes: 8 additions & 0 deletions cl/cltest/cltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,11 @@ func withModuleCapture(conf *build.Config, pkgDir string) (*build.Config, *strin
conf = build.NewDefaultConf(build.ModeRun)
}
localConf := *conf
// Existing IR fixtures describe executable instructions, not debug records.
// Keep their snapshots stable unless a test explicitly requests DWARF.
if localConf.LinkOptions.DWARF == build.DWARFDefault {
localConf.LinkOptions.DWARF = build.DWARFOmit
}
var module string
var meta string
prevHook := localConf.ModuleHook
Expand Down Expand Up @@ -480,6 +485,9 @@ func withMetaCaptures(conf *build.Config, pkgDirs []string) (*build.Config, map[
conf = build.NewDefaultConf(build.ModeRun)
}
localConf := *conf
if localConf.LinkOptions.DWARF == build.DWARFDefault {
localConf.LinkOptions.DWARF = build.DWARFOmit
}
localConf.ForceRebuild = true
metas := make(map[string]*string, len(pkgDirs))
for _, pkgDir := range pkgDirs {
Expand Down
26 changes: 26 additions & 0 deletions cl/cltest/cltest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,34 @@ import (
"path/filepath"
"runtime"
"testing"

"github.com/goplus/llgo/internal/build"
)

func TestWithModuleCaptureDWARFMode(t *testing.T) {
for _, test := range []struct {
name string
mode build.DWARFMode
want build.DWARFMode
}{
{name: "default", mode: build.DWARFDefault, want: build.DWARFOmit},
{name: "preserve", mode: build.DWARFPreserve, want: build.DWARFPreserve},
{name: "omit", mode: build.DWARFOmit, want: build.DWARFOmit},
} {
t.Run(test.name, func(t *testing.T) {
conf := build.NewDefaultConf(build.ModeRun)
conf.LinkOptions.DWARF = test.mode
got, _, _ := withModuleCapture(conf, t.TempDir())
if got.LinkOptions.DWARF != test.want {
t.Fatalf("DWARF mode = %v, want %v", got.LinkOptions.DWARF, test.want)
}
if conf.LinkOptions.DWARF != test.mode {
t.Fatalf("input DWARF mode = %v, want %v", conf.LinkOptions.DWARF, test.mode)
}
})
}
}

func TestReadGoldenUsesToolchainVersion(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "expect.txt")
Expand Down
5 changes: 1 addition & 4 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1663,10 +1663,7 @@ func (p *context) jumpTo(v *ssa.Jump) llssa.BasicBlock {
}

func (p *context) getDebugLocScope(v *ssa.Function, pos token.Pos) *types.Scope {
if v.Object() == nil {
return nil
}
funcScope := v.Object().(*types.Func).Scope()
funcScope := debugFunctionScope(v)
if funcScope == nil {
return nil
}
Expand Down
46 changes: 46 additions & 0 deletions cl/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,24 @@ func TestRunAndTestFromTestlto(t *testing.T) {
cltest.RunAndTestFromDir(t, "", "./_testlto", ignore, cltest.WithRunConfig(conf))
}

func runTestltoDWARF(t *testing.T, test string) {
t.Helper()
t.Setenv("LLGO_BUILD_CACHE", "off")
conf := build.NewDefaultConf(build.ModeRun)
conf.LTO = lto.Full
conf.LinkOptions.DWARF = build.DWARFPreserve
cltest.RunAndTestFromDir(t, test, "./_testlto", nil,
cltest.WithRunConfig(conf), cltest.WithIRCheck(false))
}

func TestRunAndTestFromTestltoDWARF(t *testing.T) {
runTestltoDWARF(t, "reflectmk_runtime")
}

func TestRunAndTestFromTestltoDeferDWARF(t *testing.T) {
runTestltoDWARF(t, "defer_dwarf")
}

var testltoSymbolChecks = []string{
"globaldce_interface_matrix",
"globaldce_interface_slots",
Expand All @@ -245,6 +263,13 @@ var testltoLTOPluginTests = []string{
"globaldce_reflect_method_by_name_ltoplugin_switch",
}

var testltoLTOPluginDWARFTests = []string{
"globaldce_reflect_method_by_name_ltoplugin_concat",
"globaldce_reflect_method_by_name_ltoplugin_global_slice",
"globaldce_reflect_method_by_name_ltoplugin_param",
"globaldce_reflect_method_by_name_ltoplugin_slice",
}

func TestBuildAndCheckSymbolsFromTestlto(t *testing.T) {
if !buildenv.Dev {
t.Skip("globaldce symbol checks require dev build")
Expand Down Expand Up @@ -316,6 +341,14 @@ func TestRunAndTestFromTestltoLTOPlugin(t *testing.T) {
)
}

func TestRunAndTestFromTestltoLTOPluginDWARF(t *testing.T) {
t.Setenv("LLGO_BUILD_CACHE", "off")
conf := testltoLTOPluginConf(t, build.ModeRun)
conf.LinkOptions.DWARF = build.DWARFPreserve
cltest.RunAndTestFromDir(t, "ltoplugin_switch", "./_testlto", nil,
cltest.WithRunConfig(conf), cltest.WithIRCheck(false))
}

func TestBuildAndCheckSymbolsFromTestltoLTOPlugin(t *testing.T) {
buildConf := testltoLTOPluginConf(t, build.ModeBuild)
// See TestBuildAndCheckSymbolsFromTestlto: dynamic main.* exports retain
Expand Down Expand Up @@ -380,6 +413,19 @@ func TestBuildAndCheckSymbolsFromTestltoLTOPluginAggregateABI(t *testing.T) {
}
}

func TestBuildAndCheckSymbolsFromTestltoLTOPluginDWARF(t *testing.T) {
t.Setenv("LLGO_BUILD_CACHE", "off")
buildConf := testltoLTOPluginConf(t, build.ModeBuild)
buildConf.LinkOptions.DWARF = build.DWARFPreserve
// Match the non-DWARF symbol gate: Linux's embedded pclntab exports main.*
// dynamically, which retains the very methods this test expects LTO to
// remove and is independent of whether DWARF is enabled.
buildConf.PCLNMode = build.PCLNNone
cltest.BuildAndCheckSymbolsFromDir(t, "", "./_testlto", testltoLTOPluginDWARFTests,
cltest.WithRunConfig(buildConf),
)
}

func TestFilterEmulatorOutput(t *testing.T) {
tests := []struct {
name string
Expand Down
74 changes: 52 additions & 22 deletions cl/debug_compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,8 @@ func TestFrontendOptions(t *testing.T) {
}
}

func TestCompileDebugMetadata(t *testing.T) {
const source = `package debugcompile

type item struct { value int }

func inspect(items [2]item, seed int) int {
x := seed + 1
var local [1]item
if x > 0 {
items[0].value = x
local[0].value = x
}
return items[0].value + local[0].value
}

var anonymous = func(seed int) int {
value := seed + 1
return value
}
`
func compileDebugSource(t *testing.T, source string, level optlevel.Level) llssa.Package {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "debug_compile.go", source, 0)
if err != nil {
Expand All @@ -97,16 +79,40 @@ var anonymous = func(seed int) int {
prog := newLLSSAProgForTarget(t, &llssa.Target{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
OptLevel: optlevel.O0,
OptLevel: level,
})
defer prog.Dispose()
t.Cleanup(prog.Dispose)
pkg, _, err := newPackageEx(prog, nil, nil, nil, ssaPkg, []*ast.File{file}, nil, false, Options{
Debug: true,
DebugSymbols: true,
})
if err != nil {
t.Fatal(err)
}
return pkg
}

func TestCompileDebugMetadata(t *testing.T) {
const source = `package debugcompile

type item struct { value int }

func inspect(items [2]item, seed int) int {
x := seed + 1
var local [1]item
if x > 0 {
items[0].value = x
local[0].value = x
}
return items[0].value + local[0].value
}

var anonymous = func(seed int) int {
value := seed + 1
return value
}
`
pkg := compileDebugSource(t, source, optlevel.O0)
if err := llvm.VerifyModule(pkg.Module(), llvm.ReturnStatusAction); err != nil {
t.Fatalf("debug module is invalid: %v\n%s", err, pkg.Module().String())
}
Expand All @@ -127,3 +133,27 @@ var anonymous = func(seed int) int {
}
}
}

func TestCompileNestedRangeFuncDeferDebugMetadata(t *testing.T) {
const source = `package debugcompile

func outer(yield func(int) bool) { _ = yield(1) }

func inner(base int) func(func(int) bool) {
return func(yield func(int) bool) { _ = yield(base + 10) }
}

func f() {
for i := range outer {
defer func() { _ = i }()
for j := range inner(i) {
defer func(v int) { _ = v }(j)
}
}
}
`
pkg := compileDebugSource(t, source, optlevel.O2)
if err := llvm.VerifyModule(pkg.Module(), llvm.ReturnStatusAction); err != nil {
t.Fatalf("nested range defer debug module is invalid: %v\n%s", err, pkg.Module().String())
}
}
13 changes: 13 additions & 0 deletions cl/debug_scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,17 @@ var anonymous = func() int {
if root.Pos() < anonymous.Syntax().Pos() || root.End() > anonymous.Syntax().End() {
t.Fatalf("anonymous function scope %s is outside function %s", root, anonymous.Syntax())
}
lit, ok := anonymous.Syntax().(*ast.FuncLit)
if !ok || len(lit.Body.List) == 0 {
t.Fatal("anonymous function syntax not found")
}
ifStmt, ok := lit.Body.List[0].(*ast.IfStmt)
if !ok || len(ifStmt.Body.List) == 0 {
t.Fatal("anonymous function inner scope not found")
}
sourcePos := ifStmt.Body.List[0].Pos()
want := root.Innermost(sourcePos)
if got := (&context{}).getDebugLocScope(anonymous, sourcePos); got != want || got == nil {
t.Fatalf("anonymous instruction scope = %p, want %p", got, want)
}
}
2 changes: 1 addition & 1 deletion cl/instr.go
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,7 @@ func (p *context) emitPCLineLabel(b llssa.Builder, pos token.Pos) {
pushSection = ".pushsection __DATA,__llgo_pcl,regular,live_support"
recordSymbol = "l_llgo_pcline_rec_${:uid}:\n"
}
b.InlineAsm(
b.InlineAsmNoDebug(
asmLabel + ":\n" +
pushSection + "\n" +
".p2align " + align + "\n" +
Expand Down
Loading
Loading