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
67 changes: 67 additions & 0 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ type context struct {
rewrites map[string]string
embedMap goembed.VarMap
embedInits []embedInit

trackCallerFrames bool
}

func (p *context) rewriteValue(name string) (string, bool) {
Expand All @@ -206,6 +208,63 @@ func (p *context) rewriteValue(name string) (string, bool) {
return val, ok
}

func filesUseRuntimeCaller(files []*ast.File) bool {
for _, file := range files {
runtimeNames := make(map[string]struct{})
var dotRuntime bool
for _, imp := range file.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil || path != "runtime" {
continue
}
name := "runtime"
if imp.Name != nil {
switch imp.Name.Name {
case ".":
dotRuntime = true
continue
case "_":
continue
default:
name = imp.Name.Name
}
}
runtimeNames[name] = struct{}{}
}
if len(runtimeNames) == 0 && !dotRuntime {
continue
}
found := false
ast.Inspect(file, func(n ast.Node) bool {
if found {
return false
}
switch n := n.(type) {
case *ast.SelectorExpr:
if !isRuntimeCallerName(n.Sel.Name) {
return true
}
if ident, ok := n.X.(*ast.Ident); ok {
if _, ok := runtimeNames[ident.Name]; ok {
found = true
return false
}
}
case *ast.Ident:
if dotRuntime && isRuntimeCallerName(n.Name) {
found = true
return false
}
}
return true
})
if found {
return true
}
}
return false
}

// isStringPtrType checks if typ is a pointer to the basic string type (*string).
// This is used to validate that -ldflags -X can only rewrite variables of type *string,
// not derived string types like "type T string".
Expand Down Expand Up @@ -552,6 +611,9 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do
var instrs = block.Instrs[n:]
var ret = fn.Block(block.Index)
b.SetBlock(ret)
if block.Index == 0 && p.shouldTrackCallerFrames() {
p.pushCallerFrame(b, block.Parent())
}
if block.Index == 0 && enableCallTracing && !strings.HasPrefix(fn.Name(), "github.com/goplus/llgo/runtime/internal/runtime.Print") {
b.Printf("call " + fn.Name() + "\n\x00")
}
Expand Down Expand Up @@ -1122,6 +1184,9 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
if p.returnNeedsImplicitRunDefers(v) {
b.RunDefers()
}
if p.shouldTrackCallerFrames() {
p.popCallerFrame(b)
}
b.Return(results...)
case *ssa.If:
fn := p.fn
Expand Down Expand Up @@ -1452,6 +1517,8 @@ func newPackageEx(prog llssa.Program, patches Patches, rewrites map[string]strin
},
cgoSymbols: make([]string, 0, 128),
rewrites: rewrites,

trackCallerFrames: filesUseRuntimeCaller(files) || packageUsesRuntimeCaller(pkg),
}
if embedMap != nil {
ctx.embedMap = *embedMap
Expand Down
173 changes: 160 additions & 13 deletions cl/instr.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"go/types"
"log"
"os"
"path/filepath"
"regexp"
"strings"

Expand Down Expand Up @@ -791,6 +792,151 @@ func (p *context) sourceLine(filename string, line int) (string, bool) {
return lines[line-1], true
}

func (p *context) shouldTrackCallerFrames() bool {
if p == nil || p.pkg == nil || p.fn == nil || !p.trackCallerFrames {
return false
}
if target := p.prog.Target(); target != nil && (target.Target != "" || target.GOARCH == "wasm") {
return false
}
pkgPath := p.pkg.Path()
return canTrackCallerFramesForPackage(pkgPath)
}

func canTrackCallerFramesForPackage(pkgPath string) bool {
return pkgPath != llssa.PkgRuntime &&
pkgPath != "runtime" &&
!isStandardLibraryPackage(pkgPath) &&
!strings.HasPrefix(pkgPath, "github.com/goplus/llgo/runtime/internal/")
}

func isStandardLibraryPackage(pkgPath string) bool {
return pkgPath != "command-line-arguments" && !strings.Contains(pkgPath, ".")
}

func packageUsesRuntimeCaller(pkg *ssa.Package) bool {
if pkg == nil {
return false
}
for _, member := range pkg.Members {
fn, ok := member.(*ssa.Function)
if ok && fnUsesRuntimeCaller(fn) {
return true
}
}
return false
}

func fnUsesRuntimeCaller(fn *ssa.Function) bool {
if fn == nil {
return false
}
for _, block := range fn.Blocks {
for _, instr := range block.Instrs {
call, ok := instr.(ssa.CallInstruction)
if !ok {
continue
}
if isRuntimeCallerFunc(call.Common().StaticCallee()) {
return true
}
}
}
for _, anon := range fn.AnonFuncs {
if fnUsesRuntimeCaller(anon) {
return true
}
}
return false
}

func isRuntimeCallerFunc(fn *ssa.Function) bool {
if fn == nil || fn.Pkg == nil || fn.Pkg.Pkg == nil {
return false
}
if fn.Pkg.Pkg.Path() != "runtime" {
return false
}
return isRuntimeCallerName(fn.Name())
}

func isRuntimeCallerName(name string) bool {
switch name {
case "Caller", "Callers", "CallersFrames", "FuncForPC":
return true
default:
return false
}
}

func (p *context) pushCallerFrame(b llssa.Builder, fn *ssa.Function) {
if fn == nil {
return
}
pos := p.fset.Position(fn.Pos())
entry := b.Convert(p.prog.Uintptr(), p.fn.Expr)
b.Call(
p.pkg.RuntimeFunc("PushCallerFrame"),
entry,
b.Str(runtimeFrameName(p.fn.Name())),
b.Str(p.callerFrameFile(fn.Pos(), pos.Filename)),
p.prog.IntVal(uint64(pos.Line), p.prog.Int()),
)
}

func (p *context) setCallerLocation(b llssa.Builder, pos token.Pos) {
if !p.shouldTrackCallerFrames() {
return
}
position := p.fset.Position(pos)
line := position.Line
if line <= 0 {
return
}
b.Call(
p.pkg.RuntimeFunc("SetCallerLocation"),
b.Str(p.callerFrameFile(pos, position.Filename)),
p.prog.IntVal(uint64(line), p.prog.Int()),
)
}

func (p *context) popCallerFrame(b llssa.Builder) {
b.Call(p.pkg.RuntimeFunc("PopCallerFrame"))
}

func runtimeFrameName(name string) string {
const commandLineArguments = "command-line-arguments."
if strings.HasPrefix(name, commandLineArguments) {
return "main." + name[len(commandLineArguments):]
}
return name
}

func (p *context) callerFrameFile(pos token.Pos, filename string) string {
if filename == "" {
return "??"
}
original := p.fset.PositionFor(pos, false).Filename
if original == "" || original == filename {
return filename
}
if rel, ok := relativeLineDirectiveFile(original, filename); ok {
return rel
}
return filename
}

func relativeLineDirectiveFile(original, adjusted string) (string, bool) {
rel, err := filepath.Rel(filepath.Dir(original), adjusted)
if err != nil || rel == "." || rel == ".." {
return "", false
}
if strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", false
}
return filepath.ToSlash(rel), true
}

// -----------------------------------------------------------------------------

type explicitDeferStack struct {
Expand Down Expand Up @@ -830,7 +976,8 @@ func (p *context) deferStackOwner(fn *ssa.Function) llssa.Function {
return owner
}

func (p *context) emitDo(b llssa.Builder, act llssa.DoAction, ds *explicitDeferStack, fn llssa.Expr, buildCall func(llssa.Builder, llssa.Expr, ...llssa.Expr) llssa.Expr, args ...llssa.Expr) llssa.Expr {
func (p *context) emitDo(b llssa.Builder, act llssa.DoAction, pos token.Pos, ds *explicitDeferStack, fn llssa.Expr, buildCall func(llssa.Builder, llssa.Expr, ...llssa.Expr) llssa.Expr, args ...llssa.Expr) llssa.Expr {
p.setCallerLocation(b, pos)
if ds != nil {
b.DeferTo(ds.owner, ds.stack, fn, buildCall, args...)
return llssa.Nil
Expand All @@ -848,7 +995,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm
hasVArg = fnHasVArg
}
args := p.compileValues(b, call.Args, hasVArg)
ret = p.emitDo(b, act, ds, fn, llssa.Builder.Call, args...)
ret = p.emitDo(b, act, call.Pos(), ds, fn, llssa.Builder.Call, args...)
return
}
kind := p.funcKind(cv)
Expand All @@ -871,7 +1018,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm
}
}
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, llssa.Builtin(fn), llssa.Builder.Call, args...)
ret = p.emitDo(b, act, call.Pos(), ds, llssa.Builtin(fn), llssa.Builder.Call, args...)
case *ssa.Function:
aFn, pyFn, ftype := p.compileFunction(cv)
// TODO(xsw): check ca != llssa.Call
Expand All @@ -880,13 +1027,13 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm
p.inCFunc = true
args := p.compileValues(b, args, kind)
p.inCFunc = false
ret = p.emitDo(b, act, ds, aFn.Expr, llssa.Builder.Call, args...)
ret = p.emitDo(b, act, call.Pos(), ds, aFn.Expr, llssa.Builder.Call, args...)
case goFunc:
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, aFn.Expr, llssa.Builder.Call, args...)
ret = p.emitDo(b, act, call.Pos(), ds, aFn.Expr, llssa.Builder.Call, args...)
case pyFunc:
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, pyFn.Expr, llssa.Builder.Call, args...)
ret = p.emitDo(b, act, call.Pos(), ds, pyFn.Expr, llssa.Builder.Call, args...)
case llgoPyList:
args := p.compileValues(b, args, fnHasVArg)
ret = b.PyList(args...)
Expand Down Expand Up @@ -960,33 +1107,33 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm
b.Unreachable()
case llgoAtomicLoad:
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
ret = p.emitDo(b, act, call.Pos(), ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
return p.atomicLoad(b, args)
}, args...)
case llgoAtomicStore:
args := p.compileValues(b, args, kind)
p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
p.emitDo(b, act, call.Pos(), ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
return p.atomicStore(b, args)
}, args...)
case llgoAtomicCmpXchg:
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
ret = p.emitDo(b, act, call.Pos(), ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
return p.atomicCmpXchg(b, args)
}, args...)
case llgoAtomicCmpXchgOK:
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
ret = p.emitDo(b, act, call.Pos(), ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
return p.atomicCmpXchgOK(b, args)
}, args...)
case llgoAtomicAddReturnNew:
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
ret = p.emitDo(b, act, call.Pos(), ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
return b.BinOp(token.ADD, p.atomic(b, llssa.OpAdd, args), args[1])
}, args...)
default:
if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast {
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
ret = p.emitDo(b, act, call.Pos(), ds, llssa.Nil, func(b llssa.Builder, _ llssa.Expr, args ...llssa.Expr) llssa.Expr {
return p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args)
}, args...)
} else {
Expand All @@ -996,7 +1143,7 @@ func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallComm
default:
fn := p.compileValue(b, cv)
args := p.compileValues(b, args, kind)
ret = p.emitDo(b, act, ds, fn, llssa.Builder.Call, args...)
ret = p.emitDo(b, act, call.Pos(), ds, fn, llssa.Builder.Call, args...)
}
return
}
Expand Down
6 changes: 3 additions & 3 deletions cl/rewrite_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ func TestEmitDoWithExplicitDeferStack(t *testing.T) {
b.SetBlockEx(owner.Block(0), llssa.BeforeLast, true)

ctx := &context{}
ctx.emitDo(b, llssa.DeferInLoop, &explicitDeferStack{stack: stack, owner: owner}, callee.Expr, llssa.Builder.Call)
ctx.emitDo(b, llssa.DeferAlways, nil, callee.Expr, llssa.Builder.Call)
ctx.emitDo(b, llssa.DeferInLoop, token.NoPos, &explicitDeferStack{stack: stack, owner: owner}, callee.Expr, llssa.Builder.Call)
ctx.emitDo(b, llssa.DeferAlways, token.NoPos, nil, callee.Expr, llssa.Builder.Call)
b.DeferStackDrain()
b.RunDefers()
b.Return()
Expand Down Expand Up @@ -465,7 +465,7 @@ func TestEmitDoWithoutExplicitDeferStack(t *testing.T) {
b := fn.MakeBody(1)

ctx := &context{}
got := ctx.emitDo(b, llssa.Call, nil, callee.Expr, llssa.Builder.Call)
got := ctx.emitDo(b, llssa.Call, token.NoPos, nil, callee.Expr, llssa.Builder.Call)
if got.IsNil() {
t.Fatal("emitDo without explicit defer stack should return direct call result")
}
Expand Down
Loading
Loading