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
32 changes: 31 additions & 1 deletion cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,11 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue
}
}
x := p.compileValue(b, v.X)
ret = b.MakeInterface(t, x)
if p.makeInterfaceNeedsFuncPC(v) {
ret = b.MakeInterfaceFuncPC(t, x)
} else {
ret = b.MakeInterface(t, x)
}
case *ssa.MakeSlice:
t := p.type_(v.Type(), llssa.InGo)
nLen := p.compileValue(b, v.Len)
Expand Down Expand Up @@ -1251,6 +1255,32 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr {
panic(fmt.Sprintf("compileValue: unknown value - %T\n", v))
}

func (p *context) makeInterfaceNeedsFuncPC(v *ssa.MakeInterface) bool {
srcFn, ok := v.X.(*ssa.Function)
if !ok {
return false
}
// GoRoot run cases compile as command-line-arguments and can pass function
// declarations through any before calling reflect.Value.Pointer.
if srcFn.Pkg != nil && srcFn.Pkg.Pkg != nil && srcFn.Pkg.Pkg.Path() == "command-line-arguments" {
return true
}
for _, ref := range *v.Referrers() {
call, ok := ref.(*ssa.Call)
if !ok {
continue
}
fn, ok := call.Call.Value.(*ssa.Function)
if !ok || fn.Name() != "ValueOf" || fn.Pkg == nil || fn.Pkg.Pkg == nil {
continue
}
if fn.Pkg.Pkg.Path() == "reflect" {
return true
}
}
return false
}

const rangeOverFuncYieldSynthetic = "range-over-func yield"

func (p *context) rangeFuncCallNeedsDeferDrain(call *ssa.CallCommon) bool {
Expand Down
18 changes: 18 additions & 0 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,9 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
if needsLinuxNoPIE(ctx, linkArgs) {
buildArgs = append(buildArgs, "-no-pie")
}
if needsLinuxFuncPCDynamicSymbolExport(ctx, linkArgs) {
buildArgs = append(buildArgs, "-Wl,--export-dynamic-symbol=__llgo_funcpc_stub.*")
}
}

// Add common linker arguments based on target OS and architecture
Expand Down Expand Up @@ -1151,6 +1154,21 @@ func needsLinuxNoPIE(ctx *context, linkArgs []string) bool {
return true
}

func needsLinuxFuncPCDynamicSymbolExport(ctx *context, linkArgs []string) bool {
if ctx.buildConf.Target != "" || ctx.buildConf.Goos != "linux" {
return false
}
for _, arg := range linkArgs {
if arg == "-rdynamic" || arg == "-Wl,-E" || arg == "-Wl,--export-dynamic" {
return false
}
if strings.HasPrefix(arg, "-Wl,--export-dynamic-symbol=__llgo_funcpc_stub.") {
return false
}
}
return true
}

// archiver returns the archiving tool to use for the current context.
// For wasm targets, it prefers llvm-ar because wasm-ld requires archives
// created with llvm-ar (system ar cannot create valid wasm archive indexes).
Expand Down
26 changes: 26 additions & 0 deletions internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ func TestNeedsLinuxNoPIE(t *testing.T) {
}
}

func TestNeedsLinuxFuncPCDynamicSymbolExport(t *testing.T) {
ctx := &context{buildConf: &Config{Goos: "linux"}}
if !needsLinuxFuncPCDynamicSymbolExport(ctx, nil) {
t.Fatal("linux executable link should export FuncForPC wrapper symbols")
}
for _, flag := range []string{
"-rdynamic",
"-Wl,-E",
"-Wl,--export-dynamic",
"-Wl,--export-dynamic-symbol=__llgo_funcpc_stub.*",
} {
if needsLinuxFuncPCDynamicSymbolExport(ctx, []string{flag}) {
t.Fatalf("explicit %s should not be duplicated", flag)
}
}
ctx.buildConf.Goos = "darwin"
if needsLinuxFuncPCDynamicSymbolExport(ctx, nil) {
t.Fatal("non-linux executable link should not export ELF symbols")
}
ctx.buildConf.Goos = "linux"
ctx.buildConf.Target = "wasi"
if needsLinuxFuncPCDynamicSymbolExport(ctx, nil) {
t.Fatal("named targets should not force host linux symbol export")
}
}

func mockRun(args []string, cfg *Config) {
defer mockable.DisableMock()
mockable.EnableMock()
Expand Down
4 changes: 0 additions & 4 deletions runtime/internal/lib/runtime/pprof_runtime_stub_llgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,3 @@ func NumGoroutine() int {
}

func SetCPUProfileRate(hz int) {}

func FuncForPC(pc uintptr) *Func {
return nil
}
67 changes: 65 additions & 2 deletions runtime/internal/lib/runtime/symtab.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,43 @@ func unknownFunctionName(pc uintptr) string {
return "pc=" + uintptrHex(pc)
}

func normalizeFunctionName(name string) string {
const stubPrefix = "__llgo_stub."
if len(name) > len(stubPrefix) && name[:len(stubPrefix)] == stubPrefix {
name = name[len(stubPrefix):]
}
const funcPCStubPrefix = "__llgo_funcpc_stub."
if len(name) > len(funcPCStubPrefix) && name[:len(funcPCStubPrefix)] == funcPCStubPrefix {
name = name[len(funcPCStubPrefix):]
}
switch name {
case "":
return ""
case "main":
return "main.main"
}
const mainPkg = "command-line-arguments."
if len(name) > len(mainPkg) && name[:len(mainPkg)] == mainPkg {
return "main." + name[len(mainPkg):]
}
return name
}

func pcInfo(pc uintptr) (name string, entry uintptr, ok bool) {
info := &clitedebug.Info{}
if clitedebug.Addrinfo(unsafe.Pointer(pc), info) == 0 && pc > 0 {
info = &clitedebug.Info{}
if clitedebug.Addrinfo(unsafe.Pointer(pc-1), info) == 0 {
return "", 0, false
}
}
name = normalizeFunctionName(safeGoString(info.Sname, ""))
if name == "" {
return "", 0, false
}
return name, uintptr(info.Saddr), true
}

func (ci *Frames) Next() (frame Frame, more bool) {
for len(ci.frames) < 2 {
// Find the next frame.
Expand Down Expand Up @@ -176,11 +213,37 @@ 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) {
return "", 0
}

func FuncForPC(pc uintptr) *Func {
if pc == 0 {
return nil
}
name, entry, ok := pcInfo(pc)
if !ok {
return nil
}
return &Func{entry: entry, name: name}
}

// moduledata records information about the layout of the executable
Expand Down
25 changes: 23 additions & 2 deletions ssa/closure_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ func closureWrapReturn(b Builder, sig *types.Signature, ret Expr) {

// closureWrapDecl wraps a function declaration that lacks __llgo_ctx.
// It directly calls the target symbol and ignores the ctx parameter.
func (p Package) closureWrapDecl(fn Expr, sig *types.Signature) Function {
name := closureStub + fn.impl.Name()
func (p Package) closureWrapDecl(fn Expr, sig *types.Signature, keepFuncPC bool) Function {
prefix := closureStub
if keepFuncPC {
prefix = closureFuncPCStub
}
name := prefix + fn.impl.Name()
if wrap := p.FuncOf(name); wrap != nil {
return wrap
}
Expand All @@ -79,12 +83,29 @@ func (p Package) closureWrapDecl(fn Expr, sig *types.Signature) Function {
wrap := p.NewFunc(name, sigCtx, InC)
wrap.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
b := wrap.MakeBody(1)
if keepFuncPC {
closureWrapKeepUnique(b, p.closureWrapIdentity(name))
}
args := closureWrapArgs(wrap)
ret := b.Call(fn, args...)
closureWrapReturn(b, sig, ret)
return wrap
}

func (p Package) closureWrapIdentity(name string) Expr {
g := p.NewVar(name+"$identity", types.NewPointer(types.Typ[types.Byte]), InGo)
g.Init(p.Prog.Zero(p.Prog.Byte()))
g.impl.SetLinkage(llvm.PrivateLinkage)
g.impl.SetGlobalConstant(true)
return g.Expr
}

func closureWrapKeepUnique(b Builder, marker Expr) {
elem := b.Prog.Elem(marker.Type)
load := llvm.CreateLoad(b.impl, elem.ll, marker.impl)
load.SetVolatile(true)
}

// closureWrapPtr wraps a raw function pointer by loading it from ctx.
// The ctx parameter is treated as a pointer to a stored function pointer cell.
func (p Package) closureWrapPtr(sig *types.Signature) Function {
Expand Down
6 changes: 5 additions & 1 deletion ssa/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,10 @@ func (b Builder) PrintEx(ln bool, args ...Expr) (ret Expr) {
// -----------------------------------------------------------------------------

func checkExpr(v Expr, t types.Type, b Builder) Expr {
return checkExprEx(v, t, b, false)
}

func checkExprEx(v Expr, t types.Type, b Builder, keepFuncPC bool) Expr {
if st, ok := t.Underlying().(*types.Struct); ok && IsClosure(st) {
if v.kind == vkClosure {
return v
Expand All @@ -1595,7 +1599,7 @@ func checkExpr(v Expr, t types.Type, b Builder) Expr {
data := prog.Nil(prog.VoidPtr())
if origKind == vkFuncDecl || origKind == vkFuncPtr {
if sig, ok := fnType.raw.Type.(*types.Signature); ok && closureCtxParam(sig) == nil {
v, data = b.Pkg.closureStub(b, v, sig, origKind)
v, data = b.Pkg.closureStub(b, v, sig, origKind, keepFuncPC)
}
}
return b.aggregateValue(tclosure, v.impl, data.impl)
Expand Down
12 changes: 10 additions & 2 deletions ssa/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,20 @@ func (b Builder) Imethod(intf Expr, method *types.Func) Expr {
//
// t1 = make interface{} <- int (42:int)
// t2 = make Stringer <- t0
func (b Builder) MakeInterface(tinter Type, x Expr) (ret Expr) {
func (b Builder) MakeInterface(tinter Type, x Expr) Expr {
return b.makeInterface(tinter, x, false)
}

func (b Builder) MakeInterfaceFuncPC(tinter Type, x Expr) Expr {
return b.makeInterface(tinter, x, true)
}

func (b Builder) makeInterface(tinter Type, x Expr, keepFuncPC bool) (ret Expr) {
rawIntf := tinter.raw.Type.Underlying().(*types.Interface)
dbgInstrf("MakeInterface %v, %v\n", rawIntf, x.impl)
if x.kind == vkFuncDecl {
typ := b.Prog.Type(x.raw.Type, InGo)
x = checkExpr(x, typ.raw.Type, b)
x = checkExprEx(x, typ.raw.Type, b, keepFuncPC)
}
prog := b.Prog
typ := x.Type
Expand Down
9 changes: 5 additions & 4 deletions ssa/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,17 +793,18 @@ func (p Package) cFunc(fullName string, sig *types.Signature) Expr {
}

const (
closureCtx = "__llgo_ctx"
closureStub = "__llgo_stub."
closureCtx = "__llgo_ctx"
closureStub = "__llgo_stub."
closureFuncPCStub = "__llgo_funcpc_stub."
)

// closureStub creates or reuses a wrapper for function values that lack closure ctx.
// It stays on Package to match the original placement of closure stubs.
func (p Package) closureStub(b Builder, fn Expr, sig *types.Signature, origKind valueKind) (Expr, Expr) {
func (p Package) closureStub(b Builder, fn Expr, sig *types.Signature, origKind valueKind, keepFuncPC bool) (Expr, Expr) {
prog := b.Prog
switch origKind {
case vkFuncDecl:
wrap := p.closureWrapDecl(fn, sig)
wrap := p.closureWrapDecl(fn, sig, keepFuncPC)
return wrap.Expr, prog.Nil(prog.VoidPtr())
case vkFuncPtr:
wrap := p.closureWrapPtr(sig)
Expand Down
14 changes: 11 additions & 3 deletions ssa/ssa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,11 +668,19 @@ func TestClosureWrapCache(t *testing.T) {
b := fn.MakeBody(1)
b.Return(fn.Param(0))

w1 := pkg.closureWrapDecl(fn.Expr, sig)
w2 := pkg.closureWrapDecl(fn.Expr, sig)
w1 := pkg.closureWrapDecl(fn.Expr, sig, false)
w2 := pkg.closureWrapDecl(fn.Expr, sig, false)
if w1 != w2 {
t.Fatal("closureWrapDecl should reuse existing wrapper")
}
wFuncPC1 := pkg.closureWrapDecl(fn.Expr, sig, true)
wFuncPC2 := pkg.closureWrapDecl(fn.Expr, sig, true)
if wFuncPC1 != wFuncPC2 {
t.Fatal("closureWrapDecl should reuse existing FuncForPC wrapper")
}
if w1 == wFuncPC1 {
t.Fatal("FuncForPC wrapper should be distinct from the normal closure wrapper")
}

p1 := pkg.closureWrapPtr(sig)
p2 := pkg.closureWrapPtr(sig)
Expand Down Expand Up @@ -930,7 +938,7 @@ func TestPackageCoverageHelpers(t *testing.T) {
fn := pkg.NewFunc("noop", NoArgsNoRet, InGo)
b := fn.MakeBody(1)
expr := prog.Val(1)
got, data := pkg.closureStub(b, expr, nil, vkString)
got, data := pkg.closureStub(b, expr, nil, vkString, false)
if got.impl.IsNil() || !data.impl.IsNull() {
t.Fatal("closureStub default branch should return expr and nil data")
}
Expand Down
Loading
Loading