Skip to content
Draft
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
6 changes: 3 additions & 3 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,11 +588,9 @@ func Build(inv Invocation) ([]Package, error) {
prog.SetPython(func() *types.Package { return pythonPackage })

buildMode := ssaBuildMode
cabiOptimize := true
passOpt := shouldRunLLVMPasses(mode)
if emitDebugInfo {
buildMode |= ssa.GlobalDebug
cabiOptimize = false
}
if !IsOptimizeEnabled() {
buildMode |= ssa.NaiveForm
Expand All @@ -610,6 +608,8 @@ func Build(inv Invocation) ([]Package, error) {
frontendOptions.PreloadedSyntax = true

output := conf.OutFile != ""
cTransformer := cabi.NewTransformer(prog, export.LLVMTarget, export.TargetABI, conf.AbiMode, true)
cTransformer.SetPreserveDebugPointerHomes(emitDebugInfo)
ctx := &context{conf: cfg, progSSA: progSSA, prog: prog, dedup: dedup,
patches: patches, callerTracking: cl.NewCallerTracking(),
built: make(map[string]none), initial: initial, mode: mode,
Expand All @@ -622,7 +622,7 @@ func Build(inv Invocation) ([]Package, error) {
crossCompile: export,
commands: commands,
frontendOptions: frontendOptions,
cTransformer: cabi.NewTransformer(prog, export.LLVMTarget, export.TargetABI, conf.AbiMode, cabiOptimize),
cTransformer: cTransformer,
}
defer ctx.closePackageMetas()
defer ctx.closePackageArchiveBuffers()
Expand Down
54 changes: 35 additions & 19 deletions internal/cabi/cabi.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ type Transformer struct {
sys TypeInfoSys
mode Mode
optimize bool
skipFns map[string]struct{}
// preserveDebugPointerHomes keeps addressable homes for aggregate
// parameters whose debug location is represented by a dereferenced
// address. LLVM 19's Linux LLDB cannot reliably materialize such values
// from a by-reference ABI parameter after the home is RAUW'd.
preserveDebugPointerHomes bool
skipFns map[string]struct{}
}

func (p *Transformer) isCFunc(name string) bool {
Expand All @@ -81,6 +86,18 @@ func (p *Transformer) SetSkipFuncs(names []string) {
}
}

// SetPreserveDebugPointerHomes keeps aggregate parameter homes when debug
// information is emitted. Scalar/short aggregate lowering remains optimized;
// pointer-shaped ABI parameters retain their source home so all supported
// LLDB targets can evaluate composite values consistently.
func (p *Transformer) SetPreserveDebugPointerHomes(enabled bool) {
p.preserveDebugPointerHomes = enabled
}

func (p *Transformer) shouldReplaceParameterHome(kind AttrKind) bool {
return p.optimize && !(p.preserveDebugPointerHomes && kind == AttrPointer)
}

func (p *Transformer) shouldSkipFunc(name string) bool {
if name == "" || len(p.skipFns) == 0 {
return false
Expand Down Expand Up @@ -447,7 +464,7 @@ func (p *Transformer) transformFuncBody(m llvm.Module, ctx llvm.Context, info *F
// store %typ %1, ptr %2, align 4
nv = b.CreateLoad(ti.Type, params[index], "")
// replace %0 to %2
if p.optimize {
if p.shouldReplaceParameterHome(ti.Kind) {
replaceAllocaInstrs(fn.Param(i), params[index])
}
case AttrWidthType:
Expand Down Expand Up @@ -833,27 +850,26 @@ func replaceAllocaInstrs(param llvm.Value, nv llvm.Value) {
}
for _, instr := range storeInstrs {
if alloc := instr.Operand(1).IsAAllocaInst(); !alloc.IsNil() {
skips := make(map[llvm.Value]bool)
type operandUse struct {
instr llvm.Value
index int
}
var preserved []operandUse
next := llvm.NextInstruction(alloc)
for !next.IsNil() && next != instr {
skips[next] = true
next = llvm.NextInstruction(next)
}
var uses []llvm.Value
u := alloc.FirstUse()
for !u.IsNil() {
if v := u.User(); !skips[v] {
uses = append(uses, v)
}
u = u.NextUse()
}
for _, use := range uses {
n := use.OperandsCount()
for i := 0; i < n; i++ {
if use.Operand(i) == alloc {
use.SetOperand(i, nv)
for i := 0; i < next.OperandsCount(); i++ {
if next.Operand(i) == alloc {
preserved = append(preserved, operandUse{next, i})
}
}
next = llvm.NextInstruction(next)
}

// RAUW updates instruction operands and LLVM debug records. Restore
// setup instructions before the parameter store to the original alloca.
alloc.ReplaceAllUsesWith(nv)
for _, use := range preserved {
use.instr.SetOperand(use.index, alloc)
}
}
}
Expand Down
99 changes: 99 additions & 0 deletions internal/cabi/cabi_debug_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//go:build !llgo

package cabi

import (
"strings"
"testing"

"github.com/goplus/llgo/internal/debuginfo"
"github.com/xgo-dev/llvm"
)

func TestReplaceAllocaInstrsUpdatesDebugDeclare(t *testing.T) {
ctx := llvm.NewContext()
defer ctx.Dispose()
mod := ctx.NewModule("cabi-debug")
defer mod.Dispose()

di := debuginfo.New(mod, debuginfo.Config{Producer: "LLGo"})
cu := di.CompileUnit("cabi.go", "/src")
file := di.File("/src/cabi.go")
intType := di.CreateBasicType(llvm.DIBasicType{Name: "int", SizeInBits: 64, Encoding: 5})
subroutine := di.CreateSubroutineType(llvm.DISubroutineType{File: file})
subprogram := di.CreateFunction(cu, llvm.DIFunction{
Name: "cabi",
LinkageName: "cabi",
File: file,
Line: 1,
ScopeLine: 1,
Type: subroutine,
IsDefinition: true,
})
variable := di.CreateAutoVariable(subprogram, llvm.DIAutoVariable{
Name: "value",
File: file,
Line: 1,
Type: intType,
AlwaysPreserve: true,
})

int64Type := ctx.Int64Type()
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{int64Type, llvm.PointerType(int64Type, 0)}, false)
fn := llvm.AddFunction(mod, "cabi", fnType)
fn.SetSubprogram(subprogram)
param := fn.Param(0)
param.SetName("param")
replacement := fn.Param(1)
replacement.SetName("replacement")
builder := ctx.NewBuilder()
defer builder.Dispose()
block := llvm.AddBasicBlock(fn, "entry")
builder.SetInsertPointAtEnd(block)
home := builder.CreateAlloca(int64Type, "home")
di.InsertDeclareAtEnd(home, variable, di.CreateExpression(nil), llvm.DebugLoc{Line: 1, Scope: subprogram}, block)
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
setup := builder.CreateGEP(int64Type, home, []llvm.Value{zero}, "setup")
builder.CreateStore(param, home)
loaded := builder.CreateLoad(int64Type, home, "loaded")
builder.CreateStore(loaded, replacement)
builder.CreateRetVoid()

replaceAllocaInstrs(param, replacement)
di.Finalize()
if err := llvm.VerifyModule(mod, llvm.ReturnStatusAction); err != nil {
t.Fatalf("rewritten module is invalid: %v\n%s", err, mod.String())
}
if setup.Operand(0) != home {
t.Fatalf("setup operand was rewritten to the ABI home:\n%s", mod.String())
}
ir := mod.String()
if !strings.Contains(ir, "#dbg_declare(ptr %replacement") {
t.Fatalf("dbg.declare did not follow the ABI home:\n%s", ir)
}
if !strings.Contains(ir, "%loaded = load i64, ptr %replacement") {
t.Fatalf("executable alloca use did not follow the ABI home:\n%s", ir)
}
}

func TestPreserveDebugPointerHomesPolicy(t *testing.T) {
tr := &Transformer{optimize: true}
tests := []struct {
name string
preserve bool
kind AttrKind
replaceOK bool
}{
{name: "optimized pointer", kind: AttrPointer, replaceOK: true},
{name: "debug pointer", preserve: true, kind: AttrPointer},
{name: "debug scalar", preserve: true, kind: AttrWidthType, replaceOK: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tr.SetPreserveDebugPointerHomes(tc.preserve)
if got := tr.shouldReplaceParameterHome(tc.kind); got != tc.replaceOK {
t.Fatalf("shouldReplaceParameterHome(%v) = %v, want %v", tc.kind, got, tc.replaceOK)
}
})
}
}
16 changes: 14 additions & 2 deletions test/go/large_array_return_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ func largeArrayLLGo(t *testing.T) string {
}

func TestLargeArrayReturnAllABIModes(t *testing.T) {
testLargeArrayReturn(t, nil, 0, 1, 2)
}

func TestLargeArrayReturnDWARF(t *testing.T) {
testLargeArrayReturn(t, []string{"-ldflags=-w=false"}, 2)
}

func testLargeArrayReturn(t *testing.T, buildFlags []string, modes ...int) {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largearray\n\ngo 1.21\n"), 0o644); err != nil {
t.Fatal(err)
Expand All @@ -91,9 +100,12 @@ func TestLargeArrayReturnAllABIModes(t *testing.T) {
t.Fatal(err)
}
bin := largeArrayLLGo(t)
for mode := 0; mode <= 2; mode++ {
for _, mode := range modes {
t.Run(fmt.Sprintf("abi%d", mode), func(t *testing.T) {
cmd := exec.Command(bin, "run", fmt.Sprintf("-abi=%d", mode), ".")
args := []string{"run", fmt.Sprintf("-abi=%d", mode)}
args = append(args, buildFlags...)
args = append(args, ".")
cmd := exec.Command(bin, args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
Expand Down
Loading