| | |
| | |
| | |
| |
|
| | package devirtualize |
| |
|
| | import ( |
| | "cmd/compile/internal/base" |
| | "cmd/compile/internal/inline" |
| | "cmd/compile/internal/ir" |
| | "cmd/compile/internal/logopt" |
| | "cmd/compile/internal/pgoir" |
| | "cmd/compile/internal/typecheck" |
| | "cmd/compile/internal/types" |
| | "cmd/internal/obj" |
| | "cmd/internal/src" |
| | "encoding/json" |
| | "fmt" |
| | "os" |
| | "strings" |
| | ) |
| |
|
| | |
| | |
| | |
| | type CallStat struct { |
| | Pkg string |
| | Pos string |
| |
|
| | Caller string |
| |
|
| | |
| | Direct bool |
| |
|
| | |
| | Interface bool |
| |
|
| | |
| | Weight int64 |
| |
|
| | |
| | |
| | Hottest string |
| | HottestWeight int64 |
| |
|
| | |
| | |
| | |
| | |
| | |
| | Devirtualized string |
| | DevirtualizedWeight int64 |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func ProfileGuided(fn *ir.Func, p *pgoir.Profile) { |
| | ir.CurFunc = fn |
| |
|
| | name := ir.LinkFuncName(fn) |
| |
|
| | var jsonW *json.Encoder |
| | if base.Debug.PGODebug >= 3 { |
| | jsonW = json.NewEncoder(os.Stdout) |
| | } |
| |
|
| | var edit func(n ir.Node) ir.Node |
| | edit = func(n ir.Node) ir.Node { |
| | if n == nil { |
| | return n |
| | } |
| |
|
| | ir.EditChildren(n, edit) |
| |
|
| | call, ok := n.(*ir.CallExpr) |
| | if !ok { |
| | return n |
| | } |
| |
|
| | var stat *CallStat |
| | if base.Debug.PGODebug >= 3 { |
| | |
| | |
| | |
| | stat = constructCallStat(p, fn, name, call) |
| | if stat != nil { |
| | defer func() { |
| | jsonW.Encode(&stat) |
| | }() |
| | } |
| | } |
| |
|
| | op := call.Op() |
| | if op != ir.OCALLFUNC && op != ir.OCALLINTER { |
| | return n |
| | } |
| |
|
| | if base.Debug.PGODebug >= 2 { |
| | fmt.Printf("%v: PGO devirtualize considering call %v\n", ir.Line(call), call) |
| | } |
| |
|
| | if call.GoDefer { |
| | if base.Debug.PGODebug >= 2 { |
| | fmt.Printf("%v: can't PGO devirtualize go/defer call %v\n", ir.Line(call), call) |
| | } |
| | return n |
| | } |
| |
|
| | var newNode ir.Node |
| | var callee *ir.Func |
| | var weight int64 |
| | switch op { |
| | case ir.OCALLFUNC: |
| | newNode, callee, weight = maybeDevirtualizeFunctionCall(p, fn, call) |
| | case ir.OCALLINTER: |
| | newNode, callee, weight = maybeDevirtualizeInterfaceCall(p, fn, call) |
| | default: |
| | panic("unreachable") |
| | } |
| |
|
| | if newNode == nil { |
| | return n |
| | } |
| |
|
| | if stat != nil { |
| | stat.Devirtualized = ir.LinkFuncName(callee) |
| | stat.DevirtualizedWeight = weight |
| | } |
| |
|
| | return newNode |
| | } |
| |
|
| | ir.EditChildren(fn, edit) |
| | } |
| |
|
| | |
| | |
| | |
| | func maybeDevirtualizeInterfaceCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) { |
| | if base.Debug.PGODevirtualize < 1 { |
| | return nil, nil, 0 |
| | } |
| |
|
| | |
| | callee, weight := findHotConcreteInterfaceCallee(p, fn, call) |
| | if callee == nil { |
| | return nil, nil, 0 |
| | } |
| | |
| | ctyp := methodRecvType(callee) |
| | if ctyp == nil { |
| | return nil, nil, 0 |
| | } |
| | |
| | if !shouldPGODevirt(callee) { |
| | return nil, nil, 0 |
| | } |
| | |
| | if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) { |
| | return nil, nil, 0 |
| | } |
| |
|
| | return rewriteInterfaceCall(call, fn, callee, ctyp), callee, weight |
| | } |
| |
|
| | |
| | |
| | |
| | func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) { |
| | if base.Debug.PGODevirtualize < 2 { |
| | return nil, nil, 0 |
| | } |
| |
|
| | |
| | callee := pgoir.DirectCallee(call.Fun) |
| | if callee != nil { |
| | return nil, nil, 0 |
| | } |
| |
|
| | |
| | callee, weight := findHotConcreteFunctionCallee(p, fn, call) |
| | if callee == nil { |
| | return nil, nil, 0 |
| | } |
| |
|
| | |
| | |
| | |
| | if callee.OClosure != nil { |
| | if base.Debug.PGODebug >= 3 { |
| | fmt.Printf("callee %s is a closure, skipping\n", ir.FuncName(callee)) |
| | } |
| | return nil, nil, 0 |
| | } |
| | |
| | |
| | |
| | |
| | if callee.Sym().Pkg.Path == "runtime" && callee.Sym().Name == "memhash_varlen" { |
| | if base.Debug.PGODebug >= 3 { |
| | fmt.Printf("callee %s is a closure (runtime.memhash_varlen), skipping\n", ir.FuncName(callee)) |
| | } |
| | return nil, nil, 0 |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if callee.Type().Recv() != nil { |
| | if base.Debug.PGODebug >= 3 { |
| | fmt.Printf("callee %s is a method, skipping\n", ir.FuncName(callee)) |
| | } |
| | return nil, nil, 0 |
| | } |
| |
|
| | |
| | if !shouldPGODevirt(callee) { |
| | return nil, nil, 0 |
| | } |
| | |
| | if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) { |
| | return nil, nil, 0 |
| | } |
| |
|
| | return rewriteFunctionCall(call, fn, callee), callee, weight |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func shouldPGODevirt(fn *ir.Func) bool { |
| | var reason string |
| | if base.Flag.LowerM > 1 || logopt.Enabled() { |
| | defer func() { |
| | if reason != "" { |
| | if base.Flag.LowerM > 1 { |
| | fmt.Printf("%v: should not PGO devirtualize %v: %s\n", ir.Line(fn), ir.FuncName(fn), reason) |
| | } |
| | if logopt.Enabled() { |
| | logopt.LogOpt(fn.Pos(), ": should not PGO devirtualize function", "pgoir-devirtualize", ir.FuncName(fn), reason) |
| | } |
| | } |
| | }() |
| | } |
| |
|
| | reason = inline.InlineImpossible(fn) |
| | if reason != "" { |
| | return false |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | return true |
| | } |
| |
|
| | |
| | |
| | |
| | func constructCallStat(p *pgoir.Profile, fn *ir.Func, name string, call *ir.CallExpr) *CallStat { |
| | switch call.Op() { |
| | case ir.OCALLFUNC, ir.OCALLINTER, ir.OCALLMETH: |
| | default: |
| | |
| | return nil |
| | } |
| |
|
| | stat := CallStat{ |
| | Pkg: base.Ctxt.Pkgpath, |
| | Pos: ir.Line(call), |
| | Caller: name, |
| | } |
| |
|
| | offset := pgoir.NodeLineOffset(call, fn) |
| |
|
| | hotter := func(e *pgoir.IREdge) bool { |
| | if stat.Hottest == "" { |
| | return true |
| | } |
| | if e.Weight != stat.HottestWeight { |
| | return e.Weight > stat.HottestWeight |
| | } |
| | |
| | |
| | return e.Dst.Name() < stat.Hottest |
| | } |
| |
|
| | callerNode := p.WeightedCG.IRNodes[name] |
| | if callerNode == nil { |
| | return nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | for _, edge := range callerNode.OutEdges { |
| | if edge.CallSiteOffset != offset { |
| | continue |
| | } |
| | stat.Weight += edge.Weight |
| | if hotter(edge) { |
| | stat.HottestWeight = edge.Weight |
| | stat.Hottest = edge.Dst.Name() |
| | } |
| | } |
| |
|
| | switch call.Op() { |
| | case ir.OCALLFUNC: |
| | stat.Interface = false |
| |
|
| | callee := pgoir.DirectCallee(call.Fun) |
| | if callee != nil { |
| | stat.Direct = true |
| | if stat.Hottest == "" { |
| | stat.Hottest = ir.LinkFuncName(callee) |
| | } |
| | } else { |
| | stat.Direct = false |
| | } |
| | case ir.OCALLINTER: |
| | stat.Direct = false |
| | stat.Interface = true |
| | case ir.OCALLMETH: |
| | base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck") |
| | } |
| |
|
| | return &stat |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | func copyInputs(curfn *ir.Func, pos src.XPos, recvOrFn ir.Node, args []ir.Node, init *ir.Nodes) (ir.Node, []ir.Node) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | var lhs, rhs []ir.Node |
| | newRecvOrFn := typecheck.TempAt(pos, curfn, recvOrFn.Type()) |
| | lhs = append(lhs, newRecvOrFn) |
| | rhs = append(rhs, recvOrFn) |
| |
|
| | for _, arg := range args { |
| | argvar := typecheck.TempAt(pos, curfn, arg.Type()) |
| |
|
| | lhs = append(lhs, argvar) |
| | rhs = append(rhs, arg) |
| | } |
| |
|
| | asList := ir.NewAssignListStmt(pos, ir.OAS2, lhs, rhs) |
| | init.Append(typecheck.Stmt(asList)) |
| |
|
| | return newRecvOrFn, lhs[1:] |
| | } |
| |
|
| | |
| | func retTemps(curfn *ir.Func, pos src.XPos, call *ir.CallExpr) []ir.Node { |
| | sig := call.Fun.Type() |
| | var retvars []ir.Node |
| | for _, ret := range sig.Results() { |
| | retvars = append(retvars, typecheck.TempAt(pos, curfn, ret.Type)) |
| | } |
| | return retvars |
| | } |
| |
|
| | |
| | |
| | |
| | func condCall(curfn *ir.Func, pos src.XPos, cond ir.Node, thenCall, elseCall *ir.CallExpr, init ir.Nodes) *ir.InlinedCallExpr { |
| | |
| | |
| | retvars := retTemps(curfn, pos, thenCall) |
| |
|
| | var thenBlock, elseBlock ir.Nodes |
| | if len(retvars) == 0 { |
| | thenBlock.Append(thenCall) |
| | elseBlock.Append(elseCall) |
| | } else { |
| | |
| | thenRet := append([]ir.Node(nil), retvars...) |
| | thenAsList := ir.NewAssignListStmt(pos, ir.OAS2, thenRet, []ir.Node{thenCall}) |
| | thenBlock.Append(typecheck.Stmt(thenAsList)) |
| |
|
| | elseRet := append([]ir.Node(nil), retvars...) |
| | elseAsList := ir.NewAssignListStmt(pos, ir.OAS2, elseRet, []ir.Node{elseCall}) |
| | elseBlock.Append(typecheck.Stmt(elseAsList)) |
| | } |
| |
|
| | nif := ir.NewIfStmt(pos, cond, thenBlock, elseBlock) |
| | nif.SetInit(init) |
| | nif.Likely = true |
| |
|
| | body := []ir.Node{typecheck.Stmt(nif)} |
| |
|
| | |
| | |
| | res := ir.NewInlinedCallExpr(pos, body, retvars) |
| | res.SetType(thenCall.Type()) |
| | res.SetTypecheck(1) |
| | return res |
| | } |
| |
|
| | |
| | |
| | func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *types.Type) ir.Node { |
| | if base.Flag.LowerM != 0 { |
| | fmt.Printf("%v: PGO devirtualizing interface call %v to %v\n", ir.Line(call), call.Fun, callee) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | sel := call.Fun.(*ir.SelectorExpr) |
| | method := sel.Sel |
| | pos := call.Pos() |
| | init := ir.TakeInit(call) |
| |
|
| | recv, args := copyInputs(curfn, pos, sel.X, call.Args.Take(), &init) |
| |
|
| | |
| | argvars := append([]ir.Node(nil), args...) |
| | call.Args = argvars |
| |
|
| | tmpnode := typecheck.TempAt(base.Pos, curfn, concretetyp) |
| | tmpok := typecheck.TempAt(base.Pos, curfn, types.Types[types.TBOOL]) |
| |
|
| | assert := ir.NewTypeAssertExpr(pos, recv, concretetyp) |
| |
|
| | assertAsList := ir.NewAssignListStmt(pos, ir.OAS2, []ir.Node{tmpnode, tmpok}, []ir.Node{typecheck.Expr(assert)}) |
| | init.Append(typecheck.Stmt(assertAsList)) |
| |
|
| | concreteCallee := typecheck.XDotMethod(pos, tmpnode, method, true) |
| | |
| | argvars = append([]ir.Node(nil), argvars...) |
| | concreteCall := typecheck.Call(pos, concreteCallee, argvars, call.IsDDD).(*ir.CallExpr) |
| |
|
| | res := condCall(curfn, pos, tmpok, concreteCall, call, init) |
| |
|
| | if base.Debug.PGODebug >= 3 { |
| | fmt.Printf("PGO devirtualizing interface call to %+v. After: %+v\n", concretetyp, res) |
| | } |
| |
|
| | return res |
| | } |
| |
|
| | |
| | |
| | func rewriteFunctionCall(call *ir.CallExpr, curfn, callee *ir.Func) ir.Node { |
| | if base.Flag.LowerM != 0 { |
| | fmt.Printf("%v: PGO devirtualizing function call %v to %v\n", ir.Line(call), call.Fun, callee) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | pos := call.Pos() |
| | init := ir.TakeInit(call) |
| |
|
| | fn, args := copyInputs(curfn, pos, call.Fun, call.Args.Take(), &init) |
| |
|
| | |
| | argvars := append([]ir.Node(nil), args...) |
| | call.Args = argvars |
| |
|
| | |
| | |
| | fnIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], fn)) |
| | calleeIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], callee.Nname)) |
| |
|
| | fnPC := ir.FuncPC(pos, fnIface, obj.ABIInternal) |
| | concretePC := ir.FuncPC(pos, calleeIface, obj.ABIInternal) |
| |
|
| | pcEq := typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.OEQ, fnPC, concretePC)) |
| |
|
| | |
| | |
| | |
| | if callee.OClosure != nil { |
| | base.Fatalf("Callee is a closure: %+v", callee) |
| | } |
| |
|
| | |
| | argvars = append([]ir.Node(nil), argvars...) |
| | concreteCall := typecheck.Call(pos, callee.Nname, argvars, call.IsDDD).(*ir.CallExpr) |
| |
|
| | res := condCall(curfn, pos, pcEq, concreteCall, call, init) |
| |
|
| | if base.Debug.PGODebug >= 3 { |
| | fmt.Printf("PGO devirtualizing function call to %+v. After: %+v\n", ir.FuncName(callee), res) |
| | } |
| |
|
| | return res |
| | } |
| |
|
| | |
| | |
| | func methodRecvType(fn *ir.Func) *types.Type { |
| | recv := fn.Nname.Type().Recv() |
| | if recv == nil { |
| | return nil |
| | } |
| | return recv.Type |
| | } |
| |
|
| | |
| | |
| | func interfaceCallRecvTypeAndMethod(call *ir.CallExpr) (*types.Type, *types.Sym) { |
| | if call.Op() != ir.OCALLINTER { |
| | base.Fatalf("Call isn't OCALLINTER: %+v", call) |
| | } |
| |
|
| | sel, ok := call.Fun.(*ir.SelectorExpr) |
| | if !ok { |
| | base.Fatalf("OCALLINTER doesn't contain SelectorExpr: %+v", call) |
| | } |
| |
|
| | return sel.X.Type(), sel.Sel |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func findHotConcreteCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr, extraFn func(callerName string, callOffset int, candidate *pgoir.IREdge) bool) (*ir.Func, int64) { |
| | callerName := ir.LinkFuncName(caller) |
| | callerNode := p.WeightedCG.IRNodes[callerName] |
| | callOffset := pgoir.NodeLineOffset(call, caller) |
| |
|
| | if callerNode == nil { |
| | return nil, 0 |
| | } |
| |
|
| | var hottest *pgoir.IREdge |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | hotter := func(e *pgoir.IREdge) bool { |
| | if hottest == nil { |
| | return true |
| | } |
| | if e.Weight != hottest.Weight { |
| | return e.Weight > hottest.Weight |
| | } |
| |
|
| | |
| | |
| |
|
| | |
| | if (hottest.Dst.AST == nil) != (e.Dst.AST == nil) { |
| | if e.Dst.AST != nil { |
| | return true |
| | } |
| | return false |
| | } |
| |
|
| | |
| | |
| | return e.Dst.Name() < hottest.Dst.Name() |
| | } |
| |
|
| | for _, e := range callerNode.OutEdges { |
| | if e.CallSiteOffset != callOffset { |
| | continue |
| | } |
| |
|
| | if !hotter(e) { |
| | |
| | |
| | |
| | |
| | |
| | if base.Debug.PGODebug >= 2 { |
| | 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) |
| | } |
| | continue |
| | } |
| |
|
| | if e.Dst.AST == nil { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if base.Debug.PGODebug >= 2 { |
| | 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) |
| | } |
| | hottest = e |
| | continue |
| | } |
| |
|
| | if extraFn != nil && !extraFn(callerName, callOffset, e) { |
| | continue |
| | } |
| |
|
| | if base.Debug.PGODebug >= 2 { |
| | fmt.Printf("%v: edge %s:%d -> %s (weight %d): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight) |
| | } |
| | hottest = e |
| | } |
| |
|
| | if hottest == nil || hottest.Weight == 0 { |
| | if base.Debug.PGODebug >= 2 { |
| | fmt.Printf("%v: call %s:%d: no hot callee\n", ir.Line(call), callerName, callOffset) |
| | } |
| | return nil, 0 |
| | } |
| |
|
| | if base.Debug.PGODebug >= 2 { |
| | fmt.Printf("%v: call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight) |
| | } |
| | return hottest.Dst.AST, hottest.Weight |
| | } |
| |
|
| | |
| | |
| | func findHotConcreteInterfaceCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) { |
| | inter, method := interfaceCallRecvTypeAndMethod(call) |
| |
|
| | return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgoir.IREdge) bool { |
| | ctyp := methodRecvType(e.Dst.AST) |
| | if ctyp == nil { |
| | |
| | |
| | if base.Debug.PGODebug >= 2 { |
| | fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee not a method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight) |
| | } |
| | return false |
| | } |
| |
|
| | |
| | |
| | if !typecheck.Implements(ctyp, inter) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if base.Debug.PGODebug >= 2 { |
| | why := typecheck.ImplementsExplain(ctyp, inter) |
| | 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) |
| | } |
| | return false |
| | } |
| |
|
| | |
| | |
| | if !strings.HasSuffix(e.Dst.Name(), "."+method.Name) { |
| | if base.Debug.PGODebug >= 2 { |
| | 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) |
| | } |
| | return false |
| | } |
| |
|
| | return true |
| | }) |
| | } |
| |
|
| | |
| | |
| | func findHotConcreteFunctionCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) { |
| | typ := call.Fun.Type().Underlying() |
| |
|
| | return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgoir.IREdge) bool { |
| | ctyp := e.Dst.AST.Type().Underlying() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if !types.Identical(typ, ctyp) { |
| | if base.Debug.PGODebug >= 2 { |
| | 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) |
| | } |
| | return false |
| | } |
| |
|
| | return true |
| | }) |
| | } |
| |
|