Source file src/cmd/compile/internal/devirtualize/pgo.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package devirtualize
     6  
     7  import (
     8  	"cmd/compile/internal/base"
     9  	"cmd/compile/internal/inline"
    10  	"cmd/compile/internal/ir"
    11  	"cmd/compile/internal/logopt"
    12  	"cmd/compile/internal/pgo"
    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  )
    22  
    23  // CallStat summarizes a single call site.
    24  //
    25  // This is used only for debug logging.
    26  type CallStat struct {
    27  	Pkg string // base.Ctxt.Pkgpath
    28  	Pos string // file:line:col of call.
    29  
    30  	Caller string // Linker symbol name of calling function.
    31  
    32  	// Direct or indirect call.
    33  	Direct bool
    34  
    35  	// For indirect calls, interface call or other indirect function call.
    36  	Interface bool
    37  
    38  	// Total edge weight from this call site.
    39  	Weight int64
    40  
    41  	// Hottest callee from this call site, regardless of type
    42  	// compatibility.
    43  	Hottest       string
    44  	HottestWeight int64
    45  
    46  	// Devirtualized callee if != "".
    47  	//
    48  	// Note that this may be different than Hottest because we apply
    49  	// type-check restrictions, which helps distinguish multiple calls on
    50  	// the same line.
    51  	Devirtualized       string
    52  	DevirtualizedWeight int64
    53  }
    54  
    55  // ProfileGuided performs call devirtualization of indirect calls based on
    56  // profile information.
    57  //
    58  // Specifically, it performs conditional devirtualization of interface calls or
    59  // 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 the
   104  // direct call.
   105  func ProfileGuided(fn *ir.Func, p *pgo.Profile) {
   106  	ir.CurFunc = fn
   107  
   108  	name := ir.LinkFuncName(fn)
   109  
   110  	var jsonW *json.Encoder
   111  	if base.Debug.PGODebug >= 3 {
   112  		jsonW = json.NewEncoder(os.Stdout)
   113  	}
   114  
   115  	var edit func(n ir.Node) ir.Node
   116  	edit = func(n ir.Node) ir.Node {
   117  		if n == nil {
   118  			return n
   119  		}
   120  
   121  		ir.EditChildren(n, edit)
   122  
   123  		call, ok := n.(*ir.CallExpr)
   124  		if !ok {
   125  			return n
   126  		}
   127  
   128  		var stat *CallStat
   129  		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  		}
   140  
   141  		op := call.Op()
   142  		if op != ir.OCALLFUNC && op != ir.OCALLINTER {
   143  			return n
   144  		}
   145  
   146  		if base.Debug.PGODebug >= 2 {
   147  			fmt.Printf("%v: PGO devirtualize considering call %v\n", ir.Line(call), call)
   148  		}
   149  
   150  		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 n
   155  		}
   156  
   157  		var newNode ir.Node
   158  		var callee *ir.Func
   159  		var weight int64
   160  		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  		}
   168  
   169  		if newNode == nil {
   170  			return n
   171  		}
   172  
   173  		if stat != nil {
   174  			stat.Devirtualized = ir.LinkFuncName(callee)
   175  			stat.DevirtualizedWeight = weight
   176  		}
   177  
   178  		return newNode
   179  	}
   180  
   181  	ir.EditChildren(fn, edit)
   182  }
   183  
   184  // Devirtualize interface call if possible and eligible. Returns the new
   185  // ir.Node if call was devirtualized, and if so also the callee and weight of
   186  // the devirtualized edge.
   187  func maybeDevirtualizeInterfaceCall(p *pgo.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) {
   188  	if base.Debug.PGODevirtualize < 1 {
   189  		return nil, nil, 0
   190  	}
   191  
   192  	// Bail if we do not have a hot callee.
   193  	callee, weight := findHotConcreteInterfaceCallee(p, fn, call)
   194  	if callee == nil {
   195  		return nil, nil, 0
   196  	}
   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, 0
   201  	}
   202  	// Bail if we know for sure it won't inline.
   203  	if !shouldPGODevirt(callee) {
   204  		return nil, nil, 0
   205  	}
   206  	// Bail if de-selected by PGO Hash.
   207  	if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) {
   208  		return nil, nil, 0
   209  	}
   210  
   211  	return rewriteInterfaceCall(call, fn, callee, ctyp), callee, weight
   212  }
   213  
   214  // Devirtualize an indirect function call if possible and eligible. Returns the new
   215  // ir.Node if call was devirtualized, and if so also the callee and weight of
   216  // the devirtualized edge.
   217  func maybeDevirtualizeFunctionCall(p *pgo.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) {
   218  	if base.Debug.PGODevirtualize < 2 {
   219  		return nil, nil, 0
   220  	}
   221  
   222  	// Bail if this is a direct call; no devirtualization necessary.
   223  	callee := pgo.DirectCallee(call.Fun)
   224  	if callee != nil {
   225  		return nil, nil, 0
   226  	}
   227  
   228  	// Bail if we do not have a hot callee.
   229  	callee, weight := findHotConcreteFunctionCallee(p, fn, call)
   230  	if callee == nil {
   231  		return nil, nil, 0
   232  	}
   233  
   234  	// TODO(go.dev/issue/61577): Closures need the closure context passed
   235  	// via the context register. That requires extra plumbing that we
   236  	// 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, 0
   242  	}
   243  	// runtime.memhash_varlen does not look like a closure, but it uses
   244  	// runtime.getclosureptr to access data encoded by callers, which are
   245  	// are generated by cmd/compile/internal/reflectdata.genhash.
   246  	if callee.Sym().Pkg.Path == "runtime" && callee.Sym().Name == "memhash_varlen" {
   247  		if base.Debug.PGODebug >= 3 {
   248  			fmt.Printf("callee %s is a closure (runtime.memhash_varlen), skipping\n", ir.FuncName(callee))
   249  		}
   250  		return nil, nil, 0
   251  	}
   252  	// TODO(prattmic): We don't properly handle methods as callees in two
   253  	// different dimensions:
   254  	//
   255  	// 1. Method expressions. e.g.,
   256  	//
   257  	//      var fn func(*os.File, []byte) (int, error) = (*os.File).Read
   258  	//
   259  	// In this case, typ will report *os.File as the receiver while
   260  	// ctyp reports it as the first argument. types.Identical ignores
   261  	// receiver parameters, so it treats these as different, even though
   262  	// they are still call compatible.
   263  	//
   264  	// 2. Method values. e.g.,
   265  	//
   266  	//      var f *os.File
   267  	//      var fn func([]byte) (int, error) = f.Read
   268  	//
   269  	// types.Identical will treat these as compatible (since receiver
   270  	// parameters are ignored). However, in this case, we do not call
   271  	// (*os.File).Read directly. Instead, f is stored in closure context
   272  	// and we call the wrapper (*os.File).Read-fm. However, runtime/pprof
   273  	// hides wrappers from profiles, making it appear that there is a call
   274  	// directly to the method. We could recognize this pattern return the
   275  	// wrapper rather than the method.
   276  	//
   277  	// N.B. perf profiles will report wrapper symbols directly, so
   278  	// ideally we should support direct wrapper references as well.
   279  	if callee.Type().Recv() != nil {
   280  		if base.Debug.PGODebug >= 3 {
   281  			fmt.Printf("callee %s is a method, skipping\n", ir.FuncName(callee))
   282  		}
   283  		return nil, nil, 0
   284  	}
   285  
   286  	// Bail if we know for sure it won't inline.
   287  	if !shouldPGODevirt(callee) {
   288  		return nil, nil, 0
   289  	}
   290  	// Bail if de-selected by PGO Hash.
   291  	if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) {
   292  		return nil, nil, 0
   293  	}
   294  
   295  	return rewriteFunctionCall(call, fn, callee), callee, weight
   296  }
   297  
   298  // shouldPGODevirt checks if we should perform PGO devirtualization to the
   299  // target function.
   300  //
   301  // PGO devirtualization is most valuable when the callee is inlined, so if it
   302  // won't inline we can skip devirtualizing.
   303  func shouldPGODevirt(fn *ir.Func) bool {
   304  	var reason string
   305  	if base.Flag.LowerM > 1 || logopt.Enabled() {
   306  		defer func() {
   307  			if reason != "" {
   308  				if base.Flag.LowerM > 1 {
   309  					fmt.Printf("%v: should not PGO devirtualize %v: %s\n", ir.Line(fn), ir.FuncName(fn), reason)
   310  				}
   311  				if logopt.Enabled() {
   312  					logopt.LogOpt(fn.Pos(), ": should not PGO devirtualize function", "pgo-devirtualize", ir.FuncName(fn), reason)
   313  				}
   314  			}
   315  		}()
   316  	}
   317  
   318  	reason = inline.InlineImpossible(fn)
   319  	if reason != "" {
   320  		return false
   321  	}
   322  
   323  	// TODO(prattmic): checking only InlineImpossible is very conservative,
   324  	// primarily excluding only functions with pragmas. We probably want to
   325  	// move in either direction. Either:
   326  	//
   327  	// 1. Don't even bother to check InlineImpossible, as it affects so few
   328  	// functions.
   329  	//
   330  	// 2. Or consider the function body (notably cost) to better determine
   331  	// if the function will actually inline.
   332  
   333  	return true
   334  }
   335  
   336  // constructCallStat builds an initial CallStat describing this call, for
   337  // logging. If the call is devirtualized, the devirtualization fields should be
   338  // updated.
   339  func constructCallStat(p *pgo.Profile, fn *ir.Func, name string, call *ir.CallExpr) *CallStat {
   340  	switch call.Op() {
   341  	case ir.OCALLFUNC, ir.OCALLINTER, ir.OCALLMETH:
   342  	default:
   343  		// We don't care about logging builtin functions.
   344  		return nil
   345  	}
   346  
   347  	stat := CallStat{
   348  		Pkg:    base.Ctxt.Pkgpath,
   349  		Pos:    ir.Line(call),
   350  		Caller: name,
   351  	}
   352  
   353  	offset := pgo.NodeLineOffset(call, fn)
   354  
   355  	hotter := func(e *pgo.IREdge) bool {
   356  		if stat.Hottest == "" {
   357  			return true
   358  		}
   359  		if e.Weight != stat.HottestWeight {
   360  			return e.Weight > stat.HottestWeight
   361  		}
   362  		// If weight is the same, arbitrarily sort lexicographally, as
   363  		// findHotConcreteCallee does.
   364  		return e.Dst.Name() < stat.Hottest
   365  	}
   366  
   367  	// Sum of all edges from this callsite, regardless of callee.
   368  	// For direct calls, this should be the same as the single edge
   369  	// weight (except for multiple calls on one line, which we
   370  	// can't distinguish).
   371  	callerNode := p.WeightedCG.IRNodes[name]
   372  	for _, edge := range callerNode.OutEdges {
   373  		if edge.CallSiteOffset != offset {
   374  			continue
   375  		}
   376  		stat.Weight += edge.Weight
   377  		if hotter(edge) {
   378  			stat.HottestWeight = edge.Weight
   379  			stat.Hottest = edge.Dst.Name()
   380  		}
   381  	}
   382  
   383  	switch call.Op() {
   384  	case ir.OCALLFUNC:
   385  		stat.Interface = false
   386  
   387  		callee := pgo.DirectCallee(call.Fun)
   388  		if callee != nil {
   389  			stat.Direct = true
   390  			if stat.Hottest == "" {
   391  				stat.Hottest = ir.LinkFuncName(callee)
   392  			}
   393  		} else {
   394  			stat.Direct = false
   395  		}
   396  	case ir.OCALLINTER:
   397  		stat.Direct = false
   398  		stat.Interface = true
   399  	case ir.OCALLMETH:
   400  		base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
   401  	}
   402  
   403  	return &stat
   404  }
   405  
   406  // copyInputs copies the inputs to a call: the receiver (for interface calls)
   407  // or function value (for function value calls) and the arguments. These
   408  // expressions are evaluated once and assigned to temporaries.
   409  //
   410  // The assignment statement is added to init and the copied receiver/fn
   411  // expression and copied arguments expressions are returned.
   412  func copyInputs(curfn *ir.Func, pos src.XPos, recvOrFn ir.Node, args []ir.Node, init *ir.Nodes) (ir.Node, []ir.Node) {
   413  	// Evaluate receiver/fn and argument expressions. The receiver/fn is
   414  	// used twice but we don't want to cause side effects twice. The
   415  	// arguments are used in two different calls and we can't trivially
   416  	// copy them.
   417  	//
   418  	// recvOrFn must be first in the assignment list as its side effects
   419  	// must be ordered before argument side effects.
   420  	var lhs, rhs []ir.Node
   421  	newRecvOrFn := typecheck.TempAt(pos, curfn, recvOrFn.Type())
   422  	lhs = append(lhs, newRecvOrFn)
   423  	rhs = append(rhs, recvOrFn)
   424  
   425  	for _, arg := range args {
   426  		argvar := typecheck.TempAt(pos, curfn, arg.Type())
   427  
   428  		lhs = append(lhs, argvar)
   429  		rhs = append(rhs, arg)
   430  	}
   431  
   432  	asList := ir.NewAssignListStmt(pos, ir.OAS2, lhs, rhs)
   433  	init.Append(typecheck.Stmt(asList))
   434  
   435  	return newRecvOrFn, lhs[1:]
   436  }
   437  
   438  // retTemps returns a slice of temporaries to be used for storing result values from call.
   439  func retTemps(curfn *ir.Func, pos src.XPos, call *ir.CallExpr) []ir.Node {
   440  	sig := call.Fun.Type()
   441  	var retvars []ir.Node
   442  	for _, ret := range sig.Results() {
   443  		retvars = append(retvars, typecheck.TempAt(pos, curfn, ret.Type))
   444  	}
   445  	return retvars
   446  }
   447  
   448  // condCall returns an ir.InlinedCallExpr that performs a call to thenCall if
   449  // cond is true and elseCall if cond is false. The return variables of the
   450  // InlinedCallExpr evaluate to the return values from the call.
   451  func condCall(curfn *ir.Func, pos src.XPos, cond ir.Node, thenCall, elseCall *ir.CallExpr, init ir.Nodes) *ir.InlinedCallExpr {
   452  	// Doesn't matter whether we use thenCall or elseCall, they must have
   453  	// the same return types.
   454  	retvars := retTemps(curfn, pos, thenCall)
   455  
   456  	var thenBlock, elseBlock ir.Nodes
   457  	if len(retvars) == 0 {
   458  		thenBlock.Append(thenCall)
   459  		elseBlock.Append(elseCall)
   460  	} else {
   461  		// Copy slice so edits in one location don't affect another.
   462  		thenRet := append([]ir.Node(nil), retvars...)
   463  		thenAsList := ir.NewAssignListStmt(pos, ir.OAS2, thenRet, []ir.Node{thenCall})
   464  		thenBlock.Append(typecheck.Stmt(thenAsList))
   465  
   466  		elseRet := append([]ir.Node(nil), retvars...)
   467  		elseAsList := ir.NewAssignListStmt(pos, ir.OAS2, elseRet, []ir.Node{elseCall})
   468  		elseBlock.Append(typecheck.Stmt(elseAsList))
   469  	}
   470  
   471  	nif := ir.NewIfStmt(pos, cond, thenBlock, elseBlock)
   472  	nif.SetInit(init)
   473  	nif.Likely = true
   474  
   475  	body := []ir.Node{typecheck.Stmt(nif)}
   476  
   477  	// This isn't really an inlined call of course, but InlinedCallExpr
   478  	// makes handling reassignment of return values easier.
   479  	res := ir.NewInlinedCallExpr(pos, body, retvars)
   480  	res.SetType(thenCall.Type())
   481  	res.SetTypecheck(1)
   482  	return res
   483  }
   484  
   485  // rewriteInterfaceCall devirtualizes the given interface call using a direct
   486  // method call to concretetyp.
   487  func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *types.Type) ir.Node {
   488  	if base.Flag.LowerM != 0 {
   489  		fmt.Printf("%v: PGO devirtualizing interface call %v to %v\n", ir.Line(call), call.Fun, callee)
   490  	}
   491  
   492  	// We generate an OINCALL of:
   493  	//
   494  	// var recv Iface
   495  	//
   496  	// var arg1 A1
   497  	// var argN AN
   498  	//
   499  	// var ret1 R1
   500  	// var retN RN
   501  	//
   502  	// recv, arg1, argN = recv expr, arg1 expr, argN expr
   503  	//
   504  	// t, ok := recv.(Concrete)
   505  	// if ok {
   506  	//   ret1, retN = t.Method(arg1, ... argN)
   507  	// } else {
   508  	//   ret1, retN = recv.Method(arg1, ... argN)
   509  	// }
   510  	//
   511  	// OINCALL retvars: ret1, ... retN
   512  	//
   513  	// This isn't really an inlined call of course, but InlinedCallExpr
   514  	// makes handling reassignment of return values easier.
   515  	//
   516  	// TODO(prattmic): This increases the size of the AST in the caller,
   517  	// making it less like to inline. We may want to compensate for this
   518  	// somehow.
   519  
   520  	sel := call.Fun.(*ir.SelectorExpr)
   521  	method := sel.Sel
   522  	pos := call.Pos()
   523  	init := ir.TakeInit(call)
   524  
   525  	recv, args := copyInputs(curfn, pos, sel.X, call.Args.Take(), &init)
   526  
   527  	// Copy slice so edits in one location don't affect another.
   528  	argvars := append([]ir.Node(nil), args...)
   529  	call.Args = argvars
   530  
   531  	tmpnode := typecheck.TempAt(base.Pos, curfn, concretetyp)
   532  	tmpok := typecheck.TempAt(base.Pos, curfn, types.Types[types.TBOOL])
   533  
   534  	assert := ir.NewTypeAssertExpr(pos, recv, concretetyp)
   535  
   536  	assertAsList := ir.NewAssignListStmt(pos, ir.OAS2, []ir.Node{tmpnode, tmpok}, []ir.Node{typecheck.Expr(assert)})
   537  	init.Append(typecheck.Stmt(assertAsList))
   538  
   539  	concreteCallee := typecheck.XDotMethod(pos, tmpnode, method, true)
   540  	// Copy slice so edits in one location don't affect another.
   541  	argvars = append([]ir.Node(nil), argvars...)
   542  	concreteCall := typecheck.Call(pos, concreteCallee, argvars, call.IsDDD).(*ir.CallExpr)
   543  
   544  	res := condCall(curfn, pos, tmpok, concreteCall, call, init)
   545  
   546  	if base.Debug.PGODebug >= 3 {
   547  		fmt.Printf("PGO devirtualizing interface call to %+v. After: %+v\n", concretetyp, res)
   548  	}
   549  
   550  	return res
   551  }
   552  
   553  // rewriteFunctionCall devirtualizes the given OCALLFUNC using a direct
   554  // function call to callee.
   555  func rewriteFunctionCall(call *ir.CallExpr, curfn, callee *ir.Func) ir.Node {
   556  	if base.Flag.LowerM != 0 {
   557  		fmt.Printf("%v: PGO devirtualizing function call %v to %v\n", ir.Line(call), call.Fun, callee)
   558  	}
   559  
   560  	// We generate an OINCALL of:
   561  	//
   562  	// var fn FuncType
   563  	//
   564  	// var arg1 A1
   565  	// var argN AN
   566  	//
   567  	// var ret1 R1
   568  	// var retN RN
   569  	//
   570  	// fn, arg1, argN = fn expr, arg1 expr, argN expr
   571  	//
   572  	// fnPC := internal/abi.FuncPCABIInternal(fn)
   573  	// concretePC := internal/abi.FuncPCABIInternal(concrete)
   574  	//
   575  	// if fnPC == concretePC {
   576  	//   ret1, retN = concrete(arg1, ... argN) // Same closure context passed (TODO)
   577  	// } else {
   578  	//   ret1, retN = fn(arg1, ... argN)
   579  	// }
   580  	//
   581  	// OINCALL retvars: ret1, ... retN
   582  	//
   583  	// This isn't really an inlined call of course, but InlinedCallExpr
   584  	// makes handling reassignment of return values easier.
   585  
   586  	pos := call.Pos()
   587  	init := ir.TakeInit(call)
   588  
   589  	fn, args := copyInputs(curfn, pos, call.Fun, call.Args.Take(), &init)
   590  
   591  	// Copy slice so edits in one location don't affect another.
   592  	argvars := append([]ir.Node(nil), args...)
   593  	call.Args = argvars
   594  
   595  	// FuncPCABIInternal takes an interface{}, emulate that. This is needed
   596  	// for to ensure we get the MAKEFACE we need for SSA.
   597  	fnIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], fn))
   598  	calleeIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], callee.Nname))
   599  
   600  	fnPC := ir.FuncPC(pos, fnIface, obj.ABIInternal)
   601  	concretePC := ir.FuncPC(pos, calleeIface, obj.ABIInternal)
   602  
   603  	pcEq := typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.OEQ, fnPC, concretePC))
   604  
   605  	// TODO(go.dev/issue/61577): Handle callees that a closures and need a
   606  	// copy of the closure context from call. For now, we skip callees that
   607  	// are closures in maybeDevirtualizeFunctionCall.
   608  	if callee.OClosure != nil {
   609  		base.Fatalf("Callee is a closure: %+v", callee)
   610  	}
   611  
   612  	// Copy slice so edits in one location don't affect another.
   613  	argvars = append([]ir.Node(nil), argvars...)
   614  	concreteCall := typecheck.Call(pos, callee.Nname, argvars, call.IsDDD).(*ir.CallExpr)
   615  
   616  	res := condCall(curfn, pos, pcEq, concreteCall, call, init)
   617  
   618  	if base.Debug.PGODebug >= 3 {
   619  		fmt.Printf("PGO devirtualizing function call to %+v. After: %+v\n", ir.FuncName(callee), res)
   620  	}
   621  
   622  	return res
   623  }
   624  
   625  // methodRecvType returns the type containing method fn. Returns nil if fn
   626  // is not a method.
   627  func methodRecvType(fn *ir.Func) *types.Type {
   628  	recv := fn.Nname.Type().Recv()
   629  	if recv == nil {
   630  		return nil
   631  	}
   632  	return recv.Type
   633  }
   634  
   635  // interfaceCallRecvTypeAndMethod returns the type and the method of the interface
   636  // used in an interface call.
   637  func interfaceCallRecvTypeAndMethod(call *ir.CallExpr) (*types.Type, *types.Sym) {
   638  	if call.Op() != ir.OCALLINTER {
   639  		base.Fatalf("Call isn't OCALLINTER: %+v", call)
   640  	}
   641  
   642  	sel, ok := call.Fun.(*ir.SelectorExpr)
   643  	if !ok {
   644  		base.Fatalf("OCALLINTER doesn't contain SelectorExpr: %+v", call)
   645  	}
   646  
   647  	return sel.X.Type(), sel.Sel
   648  }
   649  
   650  // findHotConcreteCallee returns the *ir.Func of the hottest callee of a call,
   651  // if available, and its edge weight. extraFn can perform additional
   652  // applicability checks on each candidate edge. If extraFn returns false,
   653  // candidate will not be considered a valid callee candidate.
   654  func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr, extraFn func(callerName string, callOffset int, candidate *pgo.IREdge) bool) (*ir.Func, int64) {
   655  	callerName := ir.LinkFuncName(caller)
   656  	callerNode := p.WeightedCG.IRNodes[callerName]
   657  	callOffset := pgo.NodeLineOffset(call, caller)
   658  
   659  	var hottest *pgo.IREdge
   660  
   661  	// Returns true if e is hotter than hottest.
   662  	//
   663  	// Naively this is just e.Weight > hottest.Weight, but because OutEdges
   664  	// has arbitrary iteration order, we need to apply additional sort
   665  	// criteria when e.Weight == hottest.Weight to ensure we have stable
   666  	// selection.
   667  	hotter := func(e *pgo.IREdge) bool {
   668  		if hottest == nil {
   669  			return true
   670  		}
   671  		if e.Weight != hottest.Weight {
   672  			return e.Weight > hottest.Weight
   673  		}
   674  
   675  		// Now e.Weight == hottest.Weight, we must select on other
   676  		// criteria.
   677  
   678  		// If only one edge has IR, prefer that one.
   679  		if (hottest.Dst.AST == nil) != (e.Dst.AST == nil) {
   680  			if e.Dst.AST != nil {
   681  				return true
   682  			}
   683  			return false
   684  		}
   685  
   686  		// Arbitrary, but the callee names will always differ. Select
   687  		// the lexicographically first callee.
   688  		return e.Dst.Name() < hottest.Dst.Name()
   689  	}
   690  
   691  	for _, e := range callerNode.OutEdges {
   692  		if e.CallSiteOffset != callOffset {
   693  			continue
   694  		}
   695  
   696  		if !hotter(e) {
   697  			// TODO(prattmic): consider total caller weight? i.e.,
   698  			// if the hottest callee is only 10% of the weight,
   699  			// maybe don't devirtualize? Similarly, if this is call
   700  			// is globally very cold, there is not much value in
   701  			// devirtualizing.
   702  			if base.Debug.PGODebug >= 2 {
   703  				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)
   704  			}
   705  			continue
   706  		}
   707  
   708  		if e.Dst.AST == nil {
   709  			// Destination isn't visible from this package
   710  			// compilation.
   711  			//
   712  			// We must assume it implements the interface.
   713  			//
   714  			// We still record this as the hottest callee so far
   715  			// because we only want to return the #1 hottest
   716  			// callee. If we skip this then we'd return the #2
   717  			// hottest callee.
   718  			if base.Debug.PGODebug >= 2 {
   719  				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)
   720  			}
   721  			hottest = e
   722  			continue
   723  		}
   724  
   725  		if extraFn != nil && !extraFn(callerName, callOffset, e) {
   726  			continue
   727  		}
   728  
   729  		if base.Debug.PGODebug >= 2 {
   730  			fmt.Printf("%v: edge %s:%d -> %s (weight %d): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)
   731  		}
   732  		hottest = e
   733  	}
   734  
   735  	if hottest == nil {
   736  		if base.Debug.PGODebug >= 2 {
   737  			fmt.Printf("%v: call %s:%d: no hot callee\n", ir.Line(call), callerName, callOffset)
   738  		}
   739  		return nil, 0
   740  	}
   741  
   742  	if base.Debug.PGODebug >= 2 {
   743  		fmt.Printf("%v call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight)
   744  	}
   745  	return hottest.Dst.AST, hottest.Weight
   746  }
   747  
   748  // findHotConcreteInterfaceCallee returns the *ir.Func of the hottest callee of an
   749  // interface call, if available, and its edge weight.
   750  func findHotConcreteInterfaceCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) {
   751  	inter, method := interfaceCallRecvTypeAndMethod(call)
   752  
   753  	return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgo.IREdge) bool {
   754  		ctyp := methodRecvType(e.Dst.AST)
   755  		if ctyp == nil {
   756  			// Not a method.
   757  			// TODO(prattmic): Support non-interface indirect calls.
   758  			if base.Debug.PGODebug >= 2 {
   759  				fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee not a method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)
   760  			}
   761  			return false
   762  		}
   763  
   764  		// If ctyp doesn't implement inter it is most likely from a
   765  		// different call on the same line
   766  		if !typecheck.Implements(ctyp, inter) {
   767  			// TODO(prattmic): this is overly strict. Consider if
   768  			// ctyp is a partial implementation of an interface
   769  			// that gets embedded in types that complete the
   770  			// interface. It would still be OK to devirtualize a
   771  			// call to this method.
   772  			//
   773  			// What we'd need to do is check that the function
   774  			// pointer in the itab matches the method we want,
   775  			// rather than doing a full type assertion.
   776  			if base.Debug.PGODebug >= 2 {
   777  				why := typecheck.ImplementsExplain(ctyp, inter)
   778  				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)
   779  			}
   780  			return false
   781  		}
   782  
   783  		// If the method name is different it is most likely from a
   784  		// different call on the same line
   785  		if !strings.HasSuffix(e.Dst.Name(), "."+method.Name) {
   786  			if base.Debug.PGODebug >= 2 {
   787  				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)
   788  			}
   789  			return false
   790  		}
   791  
   792  		return true
   793  	})
   794  }
   795  
   796  // findHotConcreteFunctionCallee returns the *ir.Func of the hottest callee of an
   797  // indirect function call, if available, and its edge weight.
   798  func findHotConcreteFunctionCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) {
   799  	typ := call.Fun.Type().Underlying()
   800  
   801  	return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgo.IREdge) bool {
   802  		ctyp := e.Dst.AST.Type().Underlying()
   803  
   804  		// If ctyp doesn't match typ it is most likely from a different
   805  		// call on the same line.
   806  		//
   807  		// Note that we are comparing underlying types, as different
   808  		// defined types are OK. e.g., a call to a value of type
   809  		// net/http.HandlerFunc can be devirtualized to a function with
   810  		// the same underlying type.
   811  		if !types.Identical(typ, ctyp) {
   812  			if base.Debug.PGODebug >= 2 {
   813  				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)
   814  			}
   815  			return false
   816  		}
   817  
   818  		return true
   819  	})
   820  }
   821  

View as plain text