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
5package devirtualize
6
7import (
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)
22
23// CallStat summarizes a single call site.
24//
25// This is used only for debug logging.
26type 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.
105func ProfileGuided(fn *ir.Func, p *pgoir.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.
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, 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.
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, 0
220	}
221
222	// Bail if this is a direct call; no devirtualization necessary.
223	callee := pgoir.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.
303func 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", "pgoir-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.
339func constructCallStat(p *pgoir.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 := pgoir.NodeLineOffset(call, fn)
354
355	hotter := func(e *pgoir.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	callerNode := p.WeightedCG.IRNodes[name]
368	if callerNode == nil {
369		return nil
370	}
371
372	// Sum of all edges from this callsite, regardless of callee.
373	// For direct calls, this should be the same as the single edge
374	// weight (except for multiple calls on one line, which we
375	// can't distinguish).
376	for _, edge := range callerNode.OutEdges {
377		if edge.CallSiteOffset != offset {
378			continue
379		}
380		stat.Weight += edge.Weight
381		if hotter(edge) {
382			stat.HottestWeight = edge.Weight
383			stat.Hottest = edge.Dst.Name()
384		}
385	}
386
387	switch call.Op() {
388	case ir.OCALLFUNC:
389		stat.Interface = false
390
391		callee := pgoir.DirectCallee(call.Fun)
392		if callee != nil {
393			stat.Direct = true
394			if stat.Hottest == "" {
395				stat.Hottest = ir.LinkFuncName(callee)
396			}
397		} else {
398			stat.Direct = false
399		}
400	case ir.OCALLINTER:
401		stat.Direct = false
402		stat.Interface = true
403	case ir.OCALLMETH:
404		base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
405	}
406
407	return &stat
408}
409
410// copyInputs copies the inputs to a call: the receiver (for interface calls)
411// or function value (for function value calls) and the arguments. These
412// expressions are evaluated once and assigned to temporaries.
413//
414// The assignment statement is added to init and the copied receiver/fn
415// expression and copied arguments expressions are returned.
416func copyInputs(curfn *ir.Func, pos src.XPos, recvOrFn ir.Node, args []ir.Node, init *ir.Nodes) (ir.Node, []ir.Node) {
417	// Evaluate receiver/fn and argument expressions. The receiver/fn is
418	// used twice but we don't want to cause side effects twice. The
419	// arguments are used in two different calls and we can't trivially
420	// copy them.
421	//
422	// recvOrFn must be first in the assignment list as its side effects
423	// must be ordered before argument side effects.
424	var lhs, rhs []ir.Node
425	newRecvOrFn := typecheck.TempAt(pos, curfn, recvOrFn.Type())
426	lhs = append(lhs, newRecvOrFn)
427	rhs = append(rhs, recvOrFn)
428
429	for _, arg := range args {
430		argvar := typecheck.TempAt(pos, curfn, arg.Type())
431
432		lhs = append(lhs, argvar)
433		rhs = append(rhs, arg)
434	}
435
436	asList := ir.NewAssignListStmt(pos, ir.OAS2, lhs, rhs)
437	init.Append(typecheck.Stmt(asList))
438
439	return newRecvOrFn, lhs[1:]
440}
441
442// retTemps returns a slice of temporaries to be used for storing result values from call.
443func retTemps(curfn *ir.Func, pos src.XPos, call *ir.CallExpr) []ir.Node {
444	sig := call.Fun.Type()
445	var retvars []ir.Node
446	for _, ret := range sig.Results() {
447		retvars = append(retvars, typecheck.TempAt(pos, curfn, ret.Type))
448	}
449	return retvars
450}
451
452// condCall returns an ir.InlinedCallExpr that performs a call to thenCall if
453// cond is true and elseCall if cond is false. The return variables of the
454// InlinedCallExpr evaluate to the return values from the call.
455func condCall(curfn *ir.Func, pos src.XPos, cond ir.Node, thenCall, elseCall *ir.CallExpr, init ir.Nodes) *ir.InlinedCallExpr {
456	// Doesn't matter whether we use thenCall or elseCall, they must have
457	// the same return types.
458	retvars := retTemps(curfn, pos, thenCall)
459
460	var thenBlock, elseBlock ir.Nodes
461	if len(retvars) == 0 {
462		thenBlock.Append(thenCall)
463		elseBlock.Append(elseCall)
464	} else {
465		// Copy slice so edits in one location don't affect another.
466		thenRet := append([]ir.Node(nil), retvars...)
467		thenAsList := ir.NewAssignListStmt(pos, ir.OAS2, thenRet, []ir.Node{thenCall})
468		thenBlock.Append(typecheck.Stmt(thenAsList))
469
470		elseRet := append([]ir.Node(nil), retvars...)
471		elseAsList := ir.NewAssignListStmt(pos, ir.OAS2, elseRet, []ir.Node{elseCall})
472		elseBlock.Append(typecheck.Stmt(elseAsList))
473	}
474
475	nif := ir.NewIfStmt(pos, cond, thenBlock, elseBlock)
476	nif.SetInit(init)
477	nif.Likely = true
478
479	body := []ir.Node{typecheck.Stmt(nif)}
480
481	// This isn't really an inlined call of course, but InlinedCallExpr
482	// makes handling reassignment of return values easier.
483	res := ir.NewInlinedCallExpr(pos, body, retvars)
484	res.SetType(thenCall.Type())
485	res.SetTypecheck(1)
486	return res
487}
488
489// rewriteInterfaceCall devirtualizes the given interface call using a direct
490// method call to concretetyp.
491func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *types.Type) ir.Node {
492	if base.Flag.LowerM != 0 {
493		fmt.Printf("%v: PGO devirtualizing interface call %v to %v\n", ir.Line(call), call.Fun, callee)
494	}
495
496	// We generate an OINCALL of:
497	//
498	// var recv Iface
499	//
500	// var arg1 A1
501	// var argN AN
502	//
503	// var ret1 R1
504	// var retN RN
505	//
506	// recv, arg1, argN = recv expr, arg1 expr, argN expr
507	//
508	// t, ok := recv.(Concrete)
509	// if ok {
510	//   ret1, retN = t.Method(arg1, ... argN)
511	// } else {
512	//   ret1, retN = recv.Method(arg1, ... argN)
513	// }
514	//
515	// OINCALL retvars: ret1, ... retN
516	//
517	// This isn't really an inlined call of course, but InlinedCallExpr
518	// makes handling reassignment of return values easier.
519	//
520	// TODO(prattmic): This increases the size of the AST in the caller,
521	// making it less like to inline. We may want to compensate for this
522	// somehow.
523
524	sel := call.Fun.(*ir.SelectorExpr)
525	method := sel.Sel
526	pos := call.Pos()
527	init := ir.TakeInit(call)
528
529	recv, args := copyInputs(curfn, pos, sel.X, call.Args.Take(), &init)
530
531	// Copy slice so edits in one location don't affect another.
532	argvars := append([]ir.Node(nil), args...)
533	call.Args = argvars
534
535	tmpnode := typecheck.TempAt(base.Pos, curfn, concretetyp)
536	tmpok := typecheck.TempAt(base.Pos, curfn, types.Types[types.TBOOL])
537
538	assert := ir.NewTypeAssertExpr(pos, recv, concretetyp)
539
540	assertAsList := ir.NewAssignListStmt(pos, ir.OAS2, []ir.Node{tmpnode, tmpok}, []ir.Node{typecheck.Expr(assert)})
541	init.Append(typecheck.Stmt(assertAsList))
542
543	concreteCallee := typecheck.XDotMethod(pos, tmpnode, method, true)
544	// Copy slice so edits in one location don't affect another.
545	argvars = append([]ir.Node(nil), argvars...)
546	concreteCall := typecheck.Call(pos, concreteCallee, argvars, call.IsDDD).(*ir.CallExpr)
547
548	res := condCall(curfn, pos, tmpok, concreteCall, call, init)
549
550	if base.Debug.PGODebug >= 3 {
551		fmt.Printf("PGO devirtualizing interface call to %+v. After: %+v\n", concretetyp, res)
552	}
553
554	return res
555}
556
557// rewriteFunctionCall devirtualizes the given OCALLFUNC using a direct
558// function call to callee.
559func rewriteFunctionCall(call *ir.CallExpr, curfn, callee *ir.Func) ir.Node {
560	if base.Flag.LowerM != 0 {
561		fmt.Printf("%v: PGO devirtualizing function call %v to %v\n", ir.Line(call), call.Fun, callee)
562	}
563
564	// We generate an OINCALL of:
565	//
566	// var fn FuncType
567	//
568	// var arg1 A1
569	// var argN AN
570	//
571	// var ret1 R1
572	// var retN RN
573	//
574	// fn, arg1, argN = fn expr, arg1 expr, argN expr
575	//
576	// fnPC := internal/abi.FuncPCABIInternal(fn)
577	// concretePC := internal/abi.FuncPCABIInternal(concrete)
578	//
579	// if fnPC == concretePC {
580	//   ret1, retN = concrete(arg1, ... argN) // Same closure context passed (TODO)
581	// } else {
582	//   ret1, retN = fn(arg1, ... argN)
583	// }
584	//
585	// OINCALL retvars: ret1, ... retN
586	//
587	// This isn't really an inlined call of course, but InlinedCallExpr
588	// makes handling reassignment of return values easier.
589
590	pos := call.Pos()
591	init := ir.TakeInit(call)
592
593	fn, args := copyInputs(curfn, pos, call.Fun, call.Args.Take(), &init)
594
595	// Copy slice so edits in one location don't affect another.
596	argvars := append([]ir.Node(nil), args...)
597	call.Args = argvars
598
599	// FuncPCABIInternal takes an interface{}, emulate that. This is needed
600	// for to ensure we get the MAKEFACE we need for SSA.
601	fnIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], fn))
602	calleeIface := typecheck.Expr(ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], callee.Nname))
603
604	fnPC := ir.FuncPC(pos, fnIface, obj.ABIInternal)
605	concretePC := ir.FuncPC(pos, calleeIface, obj.ABIInternal)
606
607	pcEq := typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.OEQ, fnPC, concretePC))
608
609	// TODO(go.dev/issue/61577): Handle callees that a closures and need a
610	// copy of the closure context from call. For now, we skip callees that
611	// are closures in maybeDevirtualizeFunctionCall.
612	if callee.OClosure != nil {
613		base.Fatalf("Callee is a closure: %+v", callee)
614	}
615
616	// Copy slice so edits in one location don't affect another.
617	argvars = append([]ir.Node(nil), argvars...)
618	concreteCall := typecheck.Call(pos, callee.Nname, argvars, call.IsDDD).(*ir.CallExpr)
619
620	res := condCall(curfn, pos, pcEq, concreteCall, call, init)
621
622	if base.Debug.PGODebug >= 3 {
623		fmt.Printf("PGO devirtualizing function call to %+v. After: %+v\n", ir.FuncName(callee), res)
624	}
625
626	return res
627}
628
629// methodRecvType returns the type containing method fn. Returns nil if fn
630// is not a method.
631func methodRecvType(fn *ir.Func) *types.Type {
632	recv := fn.Nname.Type().Recv()
633	if recv == nil {
634		return nil
635	}
636	return recv.Type
637}
638
639// interfaceCallRecvTypeAndMethod returns the type and the method of the interface
640// used in an interface call.
641func interfaceCallRecvTypeAndMethod(call *ir.CallExpr) (*types.Type, *types.Sym) {
642	if call.Op() != ir.OCALLINTER {
643		base.Fatalf("Call isn't OCALLINTER: %+v", call)
644	}
645
646	sel, ok := call.Fun.(*ir.SelectorExpr)
647	if !ok {
648		base.Fatalf("OCALLINTER doesn't contain SelectorExpr: %+v", call)
649	}
650
651	return sel.X.Type(), sel.Sel
652}
653
654// findHotConcreteCallee returns the *ir.Func of the hottest callee of a call,
655// if available, and its edge weight. extraFn can perform additional
656// applicability checks on each candidate edge. If extraFn returns false,
657// candidate will not be considered a valid callee candidate.
658func findHotConcreteCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr, extraFn func(callerName string, callOffset int, candidate *pgoir.IREdge) bool) (*ir.Func, int64) {
659	callerName := ir.LinkFuncName(caller)
660	callerNode := p.WeightedCG.IRNodes[callerName]
661	callOffset := pgoir.NodeLineOffset(call, caller)
662
663	if callerNode == nil {
664		return nil, 0
665	}
666
667	var hottest *pgoir.IREdge
668
669	// Returns true if e is hotter than hottest.
670	//
671	// Naively this is just e.Weight > hottest.Weight, but because OutEdges
672	// has arbitrary iteration order, we need to apply additional sort
673	// criteria when e.Weight == hottest.Weight to ensure we have stable
674	// selection.
675	hotter := func(e *pgoir.IREdge) bool {
676		if hottest == nil {
677			return true
678		}
679		if e.Weight != hottest.Weight {
680			return e.Weight > hottest.Weight
681		}
682
683		// Now e.Weight == hottest.Weight, we must select on other
684		// criteria.
685
686		// If only one edge has IR, prefer that one.
687		if (hottest.Dst.AST == nil) != (e.Dst.AST == nil) {
688			if e.Dst.AST != nil {
689				return true
690			}
691			return false
692		}
693
694		// Arbitrary, but the callee names will always differ. Select
695		// the lexicographically first callee.
696		return e.Dst.Name() < hottest.Dst.Name()
697	}
698
699	for _, e := range callerNode.OutEdges {
700		if e.CallSiteOffset != callOffset {
701			continue
702		}
703
704		if !hotter(e) {
705			// TODO(prattmic): consider total caller weight? i.e.,
706			// if the hottest callee is only 10% of the weight,
707			// maybe don't devirtualize? Similarly, if this is call
708			// is globally very cold, there is not much value in
709			// devirtualizing.
710			if base.Debug.PGODebug >= 2 {
711				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)
712			}
713			continue
714		}
715
716		if e.Dst.AST == nil {
717			// Destination isn't visible from this package
718			// compilation.
719			//
720			// We must assume it implements the interface.
721			//
722			// We still record this as the hottest callee so far
723			// because we only want to return the #1 hottest
724			// callee. If we skip this then we'd return the #2
725			// hottest callee.
726			if base.Debug.PGODebug >= 2 {
727				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)
728			}
729			hottest = e
730			continue
731		}
732
733		if extraFn != nil && !extraFn(callerName, callOffset, e) {
734			continue
735		}
736
737		if base.Debug.PGODebug >= 2 {
738			fmt.Printf("%v: edge %s:%d -> %s (weight %d): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)
739		}
740		hottest = e
741	}
742
743	if hottest == nil {
744		if base.Debug.PGODebug >= 2 {
745			fmt.Printf("%v: call %s:%d: no hot callee\n", ir.Line(call), callerName, callOffset)
746		}
747		return nil, 0
748	}
749
750	if base.Debug.PGODebug >= 2 {
751		fmt.Printf("%v: call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight)
752	}
753	return hottest.Dst.AST, hottest.Weight
754}
755
756// findHotConcreteInterfaceCallee returns the *ir.Func of the hottest callee of an
757// interface call, if available, and its edge weight.
758func findHotConcreteInterfaceCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) {
759	inter, method := interfaceCallRecvTypeAndMethod(call)
760
761	return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgoir.IREdge) bool {
762		ctyp := methodRecvType(e.Dst.AST)
763		if ctyp == nil {
764			// Not a method.
765			// TODO(prattmic): Support non-interface indirect calls.
766			if base.Debug.PGODebug >= 2 {
767				fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee not a method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight)
768			}
769			return false
770		}
771
772		// If ctyp doesn't implement inter it is most likely from a
773		// different call on the same line
774		if !typecheck.Implements(ctyp, inter) {
775			// TODO(prattmic): this is overly strict. Consider if
776			// ctyp is a partial implementation of an interface
777			// that gets embedded in types that complete the
778			// interface. It would still be OK to devirtualize a
779			// call to this method.
780			//
781			// What we'd need to do is check that the function
782			// pointer in the itab matches the method we want,
783			// rather than doing a full type assertion.
784			if base.Debug.PGODebug >= 2 {
785				why := typecheck.ImplementsExplain(ctyp, inter)
786				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)
787			}
788			return false
789		}
790
791		// If the method name is different it is most likely from a
792		// different call on the same line
793		if !strings.HasSuffix(e.Dst.Name(), "."+method.Name) {
794			if base.Debug.PGODebug >= 2 {
795				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)
796			}
797			return false
798		}
799
800		return true
801	})
802}
803
804// findHotConcreteFunctionCallee returns the *ir.Func of the hottest callee of an
805// indirect function call, if available, and its edge weight.
806func findHotConcreteFunctionCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) {
807	typ := call.Fun.Type().Underlying()
808
809	return findHotConcreteCallee(p, caller, call, func(callerName string, callOffset int, e *pgoir.IREdge) bool {
810		ctyp := e.Dst.AST.Type().Underlying()
811
812		// If ctyp doesn't match typ it is most likely from a different
813		// call on the same line.
814		//
815		// Note that we are comparing underlying types, as different
816		// defined types are OK. e.g., a call to a value of type
817		// net/http.HandlerFunc can be devirtualized to a function with
818		// the same underlying type.
819		if !types.Identical(typ, ctyp) {
820			if base.Debug.PGODebug >= 2 {
821				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)
822			}
823			return false
824		}
825
826		return true
827	})
828}
829