Empty interface; prefer specific types or generics for type safety
// FuncPCABIInternal takes an interface{}, emulate that. This is needed
1// Copyright 2023 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package devirtualize67import (8 "cmd/compile/internal/base"9 "cmd/compile/internal/inline"10 "cmd/compile/internal/ir"11 "cmd/compile/internal/logopt"12 "cmd/compile/internal/pgoir"13 "cmd/compile/internal/typecheck"14 "cmd/compile/internal/types"15 "cmd/internal/obj"16 "cmd/internal/src"17 "encoding/json"18 "fmt"19 "os"20 "strings"21)2223// CallStat summarizes a single call site.24//25// This is used only for debug logging.26type CallStat struct {27 Pkg string // base.Ctxt.Pkgpath28 Pos string // file:line:col of call.2930 Caller string // Linker symbol name of calling function.3132 // Direct or indirect call.33 Direct bool3435 // For indirect calls, interface call or other indirect function call.36 Interface bool3738 // Total edge weight from this call site.39 Weight int644041 // Hottest callee from this call site, regardless of type42 // compatibility.43 Hottest string44 HottestWeight int644546 // Devirtualized callee if != "".47 //48 // Note that this may be different than Hottest because we apply49 // type-check restrictions, which helps distinguish multiple calls on50 // the same line.51 Devirtualized string52 DevirtualizedWeight int6453}5455// ProfileGuided performs call devirtualization of indirect calls based on56// profile information.57//58// Specifically, it performs conditional devirtualization of interface calls or59// function value calls for the hottest callee.60//61// That is, for interface calls it performs a transformation like:62//63// type Iface interface {64// Foo()65// }66//67// type Concrete struct{}68//69// func (Concrete) Foo() {}70//71// func foo(i Iface) {72// i.Foo()73// }74//75// to:76//77// func foo(i Iface) {78// if c, ok := i.(Concrete); ok {79// c.Foo()80// } else {81// i.Foo()82// }83// }84//85// For function value calls it performs a transformation like:86//87// func Concrete() {}88//89// func foo(fn func()) {90// fn()91// }92//93// to:94//95// func foo(fn func()) {96// if internal/abi.FuncPCABIInternal(fn) == internal/abi.FuncPCABIInternal(Concrete) {97// Concrete()98// } else {99// fn()100// }101// }102//103// The primary benefit of this transformation is enabling inlining of the104// direct call.105func ProfileGuided(fn *ir.Func, p *pgoir.Profile) {106 ir.CurFunc = fn107108 name := ir.LinkFuncName(fn)109110 var jsonW *json.Encoder111 if base.Debug.PGODebug >= 3 {112 jsonW = json.NewEncoder(os.Stdout)113 }114115 var edit func(n ir.Node) ir.Node116 edit = func(n ir.Node) ir.Node {117 if n == nil {118 return n119 }120121 ir.EditChildren(n, edit)122123 call, ok := n.(*ir.CallExpr)124 if !ok {125 return n126 }127128 var stat *CallStat129 if base.Debug.PGODebug >= 3 {130 // Statistics about every single call. Handy for external data analysis.131 //132 // TODO(prattmic): Log via logopt?133 stat = constructCallStat(p, fn, name, call)134 if stat != nil {135 defer func() {136 jsonW.Encode(&stat)137 }()138 }139 }140141 op := call.Op()142 if op != ir.OCALLFUNC && op != ir.OCALLINTER {143 return n144 }145146 if base.Debug.PGODebug >= 2 {147 fmt.Printf("%v: PGO devirtualize considering call %v\n", ir.Line(call), call)148 }149150 if call.GoDefer {151 if base.Debug.PGODebug >= 2 {152 fmt.Printf("%v: can't PGO devirtualize go/defer call %v\n", ir.Line(call), call)153 }154 return n155 }156157 var newNode ir.Node158 var callee *ir.Func159 var weight int64160 switch op {161 case ir.OCALLFUNC:162 newNode, callee, weight = maybeDevirtualizeFunctionCall(p, fn, call)163 case ir.OCALLINTER:164 newNode, callee, weight = maybeDevirtualizeInterfaceCall(p, fn, call)165 default:166 panic("unreachable")167 }168169 if newNode == nil {170 return n171 }172173 if stat != nil {174 stat.Devirtualized = ir.LinkFuncName(callee)175 stat.DevirtualizedWeight = weight176 }177178 return newNode179 }180181 ir.EditChildren(fn, edit)182}183184// Devirtualize interface call if possible and eligible. Returns the new185// ir.Node if call was devirtualized, and if so also the callee and weight of186// the devirtualized edge.187func maybeDevirtualizeInterfaceCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) {188 if base.Debug.PGODevirtualize < 1 {189 return nil, nil, 0190 }191192 // Bail if we do not have a hot callee.193 callee, weight := findHotConcreteInterfaceCallee(p, fn, call)194 if callee == nil {195 return nil, nil, 0196 }197 // Bail if we do not have a Type node for the hot callee.198 ctyp := methodRecvType(callee)199 if ctyp == nil {200 return nil, nil, 0201 }202 // Bail if we know for sure it won't inline.203 if !shouldPGODevirt(callee) {204 return nil, nil, 0205 }206 // Bail if de-selected by PGO Hash.207 if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) {208 return nil, nil, 0209 }210211 return rewriteInterfaceCall(call, fn, callee, ctyp), callee, weight212}213214// Devirtualize an indirect function call if possible and eligible. Returns the new215// ir.Node if call was devirtualized, and if so also the callee and weight of216// the devirtualized edge.217func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) {218 if base.Debug.PGODevirtualize < 2 {219 return nil, nil, 0220 }221222 // Bail if this is a direct call; no devirtualization necessary.223 callee := pgoir.DirectCallee(call.Fun)224 if callee != nil {225 return nil, nil, 0226 }227228 // Bail if we do not have a hot callee.229 callee, weight := findHotConcreteFunctionCallee(p, fn, call)230 if callee == nil {231 return nil, nil, 0232 }233234 // TODO(go.dev/issue/61577): Closures need the closure context passed235 // via the context register. That requires extra plumbing that we236 // haven't done yet.237 if callee.OClosure != nil {238 if base.Debug.PGODebug >= 3 {239 fmt.Printf("callee %s is a closure, skipping\n", ir.FuncName(callee))240 }241 return nil, nil, 0242 }243 // runtime.memhash_varlen does not look like a closure, but it uses244 // internal/runtime/sys.GetClosurePtr to access data encoded by245 // callers, which are generated by246 // cmd/compile/internal/reflectdata.genhash.247 if callee.Sym().Pkg.Path == "runtime" && callee.Sym().Name == "memhash_varlen" {248 if base.Debug.PGODebug >= 3 {249 fmt.Printf("callee %s is a closure (runtime.memhash_varlen), skipping\n", ir.FuncName(callee))250 }251 return nil, nil, 0252 }253 // TODO(prattmic): We don't properly handle methods as callees in two254 // different dimensions:255 //256 // 1. Method expressions. e.g.,257 //258 // var fn func(*os.File, []byte) (int, error) = (*os.File).Read259 //260 // In this case, typ will report *os.File as the receiver while261 // ctyp reports it as the first argument. types.Identical ignores262 // receiver parameters, so it treats these as different, even though263 // they are still call compatible.264 //265 // 2. Method values. e.g.,266 //267 // var f *os.File268 // var fn func([]byte) (int, error) = f.Read269 //270 // types.Identical will treat these as compatible (since receiver271 // parameters are ignored). However, in this case, we do not call272 // (*os.File).Read directly. Instead, f is stored in closure context273 // and we call the wrapper (*os.File).Read-fm. However, runtime/pprof274 // hides wrappers from profiles, making it appear that there is a call275 // directly to the method. We could recognize this pattern return the276 // wrapper rather than the method.277 //278 // N.B. perf profiles will report wrapper symbols directly, so279 // ideally we should support direct wrapper references as well.280 if callee.Type().Recv() != nil {281 if base.Debug.PGODebug >= 3 {282 fmt.Printf("callee %s is a method, skipping\n", ir.FuncName(callee))283 }284 return nil, nil, 0285 }286287 // Bail if we know for sure it won't inline.288 if !shouldPGODevirt(callee) {289 return nil, nil, 0290 }291 // Bail if de-selected by PGO Hash.292 if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) {293 return nil, nil, 0294 }295296 return rewriteFunctionCall(call, fn, callee), callee, weight297}298299// shouldPGODevirt checks if we should perform PGO devirtualization to the300// target function.301//302// PGO devirtualization is most valuable when the callee is inlined, so if it303// won't inline we can skip devirtualizing.304func shouldPGODevirt(fn *ir.Func) bool {305 var reason string306 if base.Flag.LowerM > 1 || logopt.Enabled() {307 defer func() {308 if reason != "" {309 if base.Flag.LowerM > 1 {310 fmt.Printf("%v: should not PGO devirtualize %v: %s\n", ir.Line(fn), ir.FuncName(fn), reason)311 }312 if logopt.Enabled() {313 logopt.LogOpt(fn.Pos(), ": should not PGO devirtualize function", "pgoir-devirtualize", ir.FuncName(fn), reason)314 }315 }316 }()317 }318319 reason = inline.InlineImpossible(fn)320 if reason != "" {321 return false322 }323324 // TODO(prattmic): checking only InlineImpossible is very conservative,325 // primarily excluding only functions with pragmas. We probably want to326 // move in either direction. Either:327 //328 // 1. Don't even bother to check InlineImpossible, as it affects so few329 // functions.330 //331 // 2. Or consider the function body (notably cost) to better determine332 // if the function will actually inline.333334 return true335}336337// constructCallStat builds an initial CallStat describing this call, for338// logging. If the call is devirtualized, the devirtualization fields should be339// updated.340func constructCallStat(p *pgoir.Profile, fn *ir.Func, name string, call *ir.CallExpr) *CallStat {341 switch call.Op() {342 case ir.OCALLFUNC, ir.OCALLINTER, ir.OCALLMETH:343 default:344 // We don't care about logging builtin functions.345 return nil346 }347348 stat := CallStat{349 Pkg: base.Ctxt.Pkgpath,350 Pos: ir.Line(call),351 Caller: name,352 }353354 offset := pgoir.NodeLineOffset(call, fn)355356 hotter := func(e *pgoir.IREdge) bool {357 if stat.Hottest == "" {358 return true359 }360 if e.Weight != stat.HottestWeight {361 return e.Weight > stat.HottestWeight362 }363 // If weight is the same, arbitrarily sort lexicographally, as364 // findHotConcreteCallee does.365 return e.Dst.Name() < stat.Hottest366 }367368 callerNode := p.WeightedCG.IRNodes[name]369 if callerNode == nil {370 return nil371 }372373 // Sum of all edges from this callsite, regardless of callee.374 // For direct calls, this should be the same as the single edge375 // weight (except for multiple calls on one line, which we376 // can't distinguish).377 for _, edge := range callerNode.OutEdges {378 if edge.CallSiteOffset != offset {379 continue380 }381 stat.Weight += edge.Weight382 if hotter(edge) {383 stat.HottestWeight = edge.Weight384 stat.Hottest = edge.Dst.Name()385 }386 }387388 switch call.Op() {389 case ir.OCALLFUNC:390 stat.Interface = false391392 callee := pgoir.DirectCallee(call.Fun)393 if callee != nil {394 stat.Direct = true395 if stat.Hottest == "" {396 stat.Hottest = ir.LinkFuncName(callee)397 }398 } else {399 stat.Direct = false400 }401 case ir.OCALLINTER:402 stat.Direct = false403 stat.Interface = true404 case ir.OCALLMETH:405 base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")406 }407408 return &stat409}410411// copyInputs copies the inputs to a call: the receiver (for interface calls)412// or function value (for function value calls) and the arguments. These413// expressions are evaluated once and assigned to temporaries.414//415// The assignment statement is added to init and the copied receiver/fn416// expression and copied arguments expressions are returned.417func copyInputs(curfn *ir.Func, pos src.XPos, recvOrFn ir.Node, args []ir.Node, init *ir.Nodes) (ir.Node, []ir.Node) {418 // Evaluate receiver/fn and argument expressions. The receiver/fn is419 // used twice but we don't want to cause side effects twice. The420 // arguments are used in two different calls and we can't trivially421 // copy them.422 //423 // recvOrFn must be first in the assignment list as its side effects424 // must be ordered before argument side effects.425 var lhs, rhs []ir.Node426 newRecvOrFn := typecheck.TempAt(pos, curfn, recvOrFn.Type())427 lhs = append(lhs, newRecvOrFn)428 rhs = append(rhs, recvOrFn)429430 for _, arg := range args {431 argvar := typecheck.TempAt(pos, curfn, arg.Type())432433 lhs = append(lhs, argvar)434 rhs = append(rhs, arg)435 }436437 asList := ir.NewAssignListStmt(pos, ir.OAS2, lhs, rhs)438 init.Append(typecheck.Stmt(asList))439440 return newRecvOrFn, lhs[1:]441}442443// retTemps returns a slice of temporaries to be used for storing result values from call.444func retTemps(curfn *ir.Func, pos src.XPos, call *ir.CallExpr) []ir.Node {445 sig := call.Fun.Type()446 var retvars []ir.Node447 for _, ret := range sig.Results() {448 retvars = append(retvars, typecheck.TempAt(pos, curfn, ret.Type))449 }450 return retvars451}452453// condCall returns an ir.InlinedCallExpr that performs a call to thenCall if454// cond is true and elseCall if cond is false. The return variables of the455// InlinedCallExpr evaluate to the return values from the call.456func condCall(curfn *ir.Func, pos src.XPos, cond ir.Node, thenCall, elseCall *ir.CallExpr, init ir.Nodes) *ir.InlinedCallExpr {457 // Doesn't matter whether we use thenCall or elseCall, they must have458 // the same return types.459 retvars := retTemps(curfn, pos, thenCall)460461 var thenBlock, elseBlock ir.Nodes462 if len(retvars) == 0 {463 thenBlock.Append(thenCall)464 elseBlock.Append(elseCall)465 } else {466 // Copy slice so edits in one location don't affect another.467 thenRet := append([]ir.Node(nil), retvars...)468 thenAsList := ir.NewAssignListStmt(pos, ir.OAS2, thenRet, []ir.Node{thenCall})469 thenBlock.Append(typecheck.Stmt(thenAsList))470471 elseRet := append([]ir.Node(nil), retvars...)472 elseAsList := ir.NewAssignListStmt(pos, ir.OAS2, elseRet, []ir.Node{elseCall})473 elseBlock.Append(typecheck.Stmt(elseAsList))474 }475476 nif := ir.NewIfStmt(pos, cond, thenBlock, elseBlock)477 nif.SetInit(init)478 nif.Likely = true479480 body := []ir.Node{typecheck.Stmt(nif)}481482 // This isn't really an inlined call of course, but InlinedCallExpr483 // makes handling reassignment of return values easier.484 res := ir.NewInlinedCallExpr(pos, body, retvars)485 res.SetType(thenCall.Type())486 res.SetTypecheck(1)487 res.Reshape = thenCall.Reshape488 return res489}490491// rewriteInterfaceCall devirtualizes the given interface call using a direct492// method call to concretetyp.493func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *types.Type) ir.Node {494 if base.Flag.LowerM != 0 {495 fmt.Printf("%v: PGO devirtualizing interface call %v to %v\n", ir.Line(call), call.Fun, callee)496 }497498 // We generate an OINCALL of:499 //500 // var recv Iface501 //502 // var arg1 A1503 // var argN AN504 //505 // var ret1 R1506 // var retN RN507 //508 // recv, arg1, argN = recv expr, arg1 expr, argN expr509 //510 // t, ok := recv.(Concrete)511 // if ok {512 // ret1, retN = t.Method(arg1, ... argN)513 // } else {514 // ret1, retN = recv.Method(arg1, ... argN)515 // }516 //517 // OINCALL retvars: ret1, ... retN518 //519 // This isn't really an inlined call of course, but InlinedCallExpr520 // makes handling reassignment of return values easier.521 //522 // TODO(prattmic): This increases the size of the AST in the caller,523 // making it less like to inline. We may want to compensate for this524 // somehow.525526 sel := call.Fun.(*ir.SelectorExpr)527 method := sel.Sel528 pos := call.Pos()529 init := ir.TakeInit(call)530531 recv, args := copyInputs(curfn, pos, sel.X, call.Args.Take(), &init)532533 // Copy slice so edits in one location don't affect another.534 argvars := append([]ir.Node(nil), args...)535 call.Args = argvars536537 tmpnode := typecheck.TempAt(base.Pos, curfn, concretetyp)538 tmpok := typecheck.TempAt(base.Pos, curfn, types.Types[types.TBOOL])539540 assert := ir.NewTypeAssertExpr(pos, recv, concretetyp)541542 assertAsList := ir.NewAssignListStmt(pos, ir.OAS2, []ir.Node{tmpnode, tmpok}, []ir.Node{typecheck.Expr(assert)})543 init.Append(typecheck.Stmt(assertAsList))544545 concreteCallee := typecheck.XDotMethod(pos, tmpnode, method, true)546 // Copy slice so edits in one location don't affect another.547 argvars = append([]ir.Node(nil), argvars...)548 concreteCall := typecheck.Call(pos, concreteCallee, argvars, call.IsDDD).(*ir.CallExpr)549550 res := condCall(curfn, pos, tmpok, concreteCall, call, init)551552 if base.Debug.PGODebug >= 3 {553 fmt.Printf("PGO devirtualizing interface call to %+v. After: %+v\n", concretetyp, res)554 }555556 return res557}558559// rewriteFunctionCall devirtualizes the given OCALLFUNC using a direct560// function call to callee.561func rewriteFunctionCall(call *ir.CallExpr, curfn, callee *ir.Func) ir.Node {562 if base.Flag.LowerM != 0 {563 fmt.Printf("%v: PGO devirtualizing function call %v to %v\n", ir.Line(call), call.Fun, callee)564 }565566 // We generate an OINCALL of:567 //568 // var fn FuncType569 //570 // var arg1 A1571 // var argN AN572 //573 // var ret1 R1574 // var retN RN575 //576 // fn, arg1, argN = fn expr, arg1 expr, argN expr577 //578 // fnPC := internal/abi.FuncPCABIInternal(fn)579 // concretePC := internal/abi.FuncPCABIInternal(concrete)580 //581 // if fnPC == concretePC {582 // ret1, retN = concrete(arg1, ... argN) // Same closure context passed (TODO)583 // } else {584 // ret1, retN = fn(arg1, ... argN)585 // }586 //587 // OINCALL retvars: ret1, ... retN588 //589 // This isn't really an inlined call of course, but InlinedCallExpr590 // makes handling reassignment of return values easier.591592 pos := call.Pos()593 init := ir.TakeInit(call)594595 fn, args := copyInputs(curfn, pos, call.Fun, call.Args.Take(), &init)596597 // Copy slice so edits in one location don't affect another.598 argvars := append([]ir.Node(nil), args...)599 call.Args = argvars600601 // FuncPCABIInternal takes an interface{}, emulate that. This is needed602 // for to ensure we get the MAKEFACE we need for SSA.603 fnIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], fn))604 calleeIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], callee.Nname))605606 fnPC := ir.FuncPC(pos, fnIface, obj.ABIInternal)607 concretePC := ir.FuncPC(pos, calleeIface, obj.ABIInternal)608609 pcEq := typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.OEQ, fnPC, concretePC))610611 // TODO(go.dev/issue/61577): Handle callees that a closures and need a612 // copy of the closure context from call. For now, we skip callees that613 // are closures in maybeDevirtualizeFunctionCall.614 if callee.OClosure != nil {615 base.Fatalf("Callee is a closure: %+v", callee)616 }617618 // Copy slice so edits in one location don't affect another.619 argvars = append([]ir.Node(nil), argvars...)620 concreteCall := typecheck.Call(pos, callee.Nname, argvars, call.IsDDD).(*ir.CallExpr)621622 res := condCall(curfn, pos, pcEq, concreteCall, call, init)623624 if base.Debug.PGODebug >= 3 {625 fmt.Printf("PGO devirtualizing function call to %+v. After: %+v\n", ir.FuncName(callee), res)626 }627628 return res629}630631// methodRecvType returns the type containing method fn. Returns nil if fn632// is not a method.633func methodRecvType(fn *ir.Func) *types.Type {634 recv := fn.Nname.Type().Recv()635 if recv == nil {636 return nil637 }638 return recv.Type639}640641// interfaceCallRecvTypeAndMethod returns the type and the method of the interface642// used in an interface call.643func interfaceCallRecvTypeAndMethod(call *ir.CallExpr) (*types.Type, *types.Sym) {644 if call.Op() != ir.OCALLINTER {645 base.Fatalf("Call isn't OCALLINTER: %+v", call)646 }647648 sel, ok := call.Fun.(*ir.SelectorExpr)649 if !ok {650 base.Fatalf("OCALLINTER doesn't contain SelectorExpr: %+v", call)651 }652653 return sel.X.Type(), sel.Sel654}655656// findHotConcreteCallee returns the *ir.Func of the hottest callee of a call,657// if available, and its edge weight. extraFn can perform additional658// applicability checks on each candidate edge. If extraFn returns false,659// candidate will not be considered a valid callee candidate.660func findHotConcreteCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr, extraFn func(callerName string, callOffset int, candidate *pgoir.IREdge) bool) (*ir.Func, int64) {661 callerName := ir.LinkFuncName(caller)662 callerNode := p.WeightedCG.IRNodes[callerName]663 callOffset := pgoir.NodeLineOffset(call, caller)664665 if callerNode == nil {666 return nil, 0667 }668669 var hottest *pgoir.IREdge670671 // Returns true if e is hotter than hottest.672 //673 // Naively this is just e.Weight > hottest.Weight, but because OutEdges674 // has arbitrary iteration order, we need to apply additional sort675 // criteria when e.Weight == hottest.Weight to ensure we have stable676 // selection.677 hotter := func(e *pgoir.IREdge) bool {678 if hottest == nil {679 return true680 }681 if e.Weight != hottest.Weight {682 return e.Weight > hottest.Weight683 }684685 // Now e.Weight == hottest.Weight, we must select on other686 // criteria.687688 // If only one edge has IR, prefer that one.689 if (hottest.Dst.AST == nil) != (e.Dst.AST == nil) {690 if e.Dst.AST != nil {691 return true692 }693 return false694 }695696 // Arbitrary, but the callee names will always differ. Select697 // the lexicographically first callee.698 return e.Dst.Name() < hottest.Dst.Name()699 }700701 for _, e := range callerNode.OutEdges {702 if e.CallSiteOffset != callOffset {703 continue704 }705706 if !hotter(e) {707 // TODO(prattmic): consider total caller weight? i.e.,708 // if the hottest callee is only 10% of the weight,709 // maybe don't devirtualize? Similarly, if this is call710 // is globally very cold, there is not much value in711 // devirtualizing.712 if base.Debug.PGODebug >= 2 {713 fmt.Printf("%v: edge %s:%d -> %s (weight %d): too cold (hottest %d)\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight, hottest.Weight)714 }715 continue716 }717718 if e.Dst.AST == nil {719 // Destination isn't visible from this package720 // compilation.721 //722 // We must assume it implements the interface.723 //724 // We still record this as the hottest callee so far725 // because we only want to return the #1 hottest726 // callee. If we skip this then we'd return the #2727 // hottest callee.728 if base.Debug.PGODebug >= 2 {729 fmt.Printf("%v: edge %s:%d -> %s (weight %d) (missing IR): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)730 }731 hottest = e732 continue733 }734735 if extraFn != nil && !extraFn(callerName, callOffset, e) {736 continue737 }738739 if base.Debug.PGODebug >= 2 {740 fmt.Printf("%v: edge %s:%d -> %s (weight %d): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)741 }742 hottest = e743 }744745 if hottest == nil || hottest.Weight == 0 {746 if base.Debug.PGODebug >= 2 {747 fmt.Printf("%v: call %s:%d: no hot callee\n", ir.Line(call), callerName, callOffset)748 }749 return nil, 0750 }751752 if base.Debug.PGODebug >= 2 {753 fmt.Printf("%v: call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight)754 }755 return hottest.Dst.AST, hottest.Weight756}757758// findHotConcreteInterfaceCallee returns the *ir.Func of the hottest callee of an759// interface call, if available, and its edge weight.760func findHotConcreteInterfaceCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) {761 inter, method := interfaceCallRecvTypeAndMethod(call)762763 return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgoir.IREdge) bool {764 ctyp := methodRecvType(e.Dst.AST)765 if ctyp == nil {766 // Not a method.767 // TODO(prattmic): Support non-interface indirect calls.768 if base.Debug.PGODebug >= 2 {769 fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee not a method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)770 }771 return false772 }773774 // If ctyp doesn't implement inter it is most likely from a775 // different call on the same line776 if !typecheck.Implements(ctyp, inter) {777 // TODO(prattmic): this is overly strict. Consider if778 // ctyp is a partial implementation of an interface779 // that gets embedded in types that complete the780 // interface. It would still be OK to devirtualize a781 // call to this method.782 //783 // What we'd need to do is check that the function784 // pointer in the itab matches the method we want,785 // rather than doing a full type assertion.786 if base.Debug.PGODebug >= 2 {787 why := typecheck.ImplementsExplain(ctyp, inter)788 fmt.Printf("%v: edge %s:%d -> %s (weight %d): %v doesn't implement %v (%s)\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight, ctyp, inter, why)789 }790 return false791 }792793 // If the method name is different it is most likely from a794 // different call on the same line795 if !strings.HasSuffix(e.Dst.Name(), "."+method.Name) {796 if base.Debug.PGODebug >= 2 {797 fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee is a different method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)798 }799 return false800 }801802 return true803 })804}805806// findHotConcreteFunctionCallee returns the *ir.Func of the hottest callee of an807// indirect function call, if available, and its edge weight.808func findHotConcreteFunctionCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) {809 typ := call.Fun.Type().Underlying()810811 return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgoir.IREdge) bool {812 ctyp := e.Dst.AST.Type().Underlying()813814 // If ctyp doesn't match typ it is most likely from a different815 // call on the same line.816 //817 // Note that we are comparing underlying types, as different818 // defined types are OK. e.g., a call to a value of type819 // net/http.HandlerFunc can be devirtualized to a function with820 // the same underlying type.821 if !types.Identical(typ, ctyp) {822 if base.Debug.PGODebug >= 2 {823 fmt.Printf("%v: edge %s:%d -> %s (weight %d): %v doesn't match %v\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight, ctyp, typ)824 }825 return false826 }827828 return true829 })830}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.