1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package inline
28
29 import (
30 "fmt"
31 "go/constant"
32 "internal/buildcfg"
33 "strconv"
34 "strings"
35
36 "cmd/compile/internal/base"
37 "cmd/compile/internal/inline/inlheur"
38 "cmd/compile/internal/ir"
39 "cmd/compile/internal/logopt"
40 "cmd/compile/internal/pgoir"
41 "cmd/compile/internal/typecheck"
42 "cmd/compile/internal/types"
43 "cmd/internal/obj"
44 "cmd/internal/pgo"
45 "cmd/internal/src"
46 )
47
48
49 const (
50 inlineMaxBudget = 80
51 inlineExtraAppendCost = 0
52
53 inlineExtraCallCost = 57
54 inlineParamCallCost = 17
55 inlineExtraPanicCost = 1
56 inlineExtraThrowCost = inlineMaxBudget
57
58 inlineBigFunctionNodes = 5000
59 inlineBigFunctionMaxCost = 20
60 inlineClosureCalledOnceCost = 10 * inlineMaxBudget
61 )
62
63 var (
64
65
66 candHotCalleeMap = make(map[*pgoir.IRNode]struct{})
67
68
69 hasHotCall = make(map[*ir.Func]struct{})
70
71
72
73 candHotEdgeMap = make(map[pgoir.CallSiteInfo]struct{})
74
75
76 inlineHotCallSiteThresholdPercent float64
77
78
79
80
81
82 inlineCDFHotCallSiteThresholdPercent = float64(99)
83
84
85 inlineHotMaxBudget int32 = 2000
86 )
87
88 func IsPgoHotFunc(fn *ir.Func, profile *pgoir.Profile) bool {
89 if profile == nil {
90 return false
91 }
92 if n, ok := profile.WeightedCG.IRNodes[ir.LinkFuncName(fn)]; ok {
93 _, ok := candHotCalleeMap[n]
94 return ok
95 }
96 return false
97 }
98
99 func HasPgoHotInline(fn *ir.Func) bool {
100 _, has := hasHotCall[fn]
101 return has
102 }
103
104
105 func PGOInlinePrologue(p *pgoir.Profile) {
106 if base.Debug.PGOInlineCDFThreshold != "" {
107 if s, err := strconv.ParseFloat(base.Debug.PGOInlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 {
108 inlineCDFHotCallSiteThresholdPercent = s
109 } else {
110 base.Fatalf("invalid PGOInlineCDFThreshold, must be between 0 and 100")
111 }
112 }
113 var hotCallsites []pgo.NamedCallEdge
114 inlineHotCallSiteThresholdPercent, hotCallsites = hotNodesFromCDF(p)
115 if base.Debug.PGODebug > 0 {
116 fmt.Printf("hot-callsite-thres-from-CDF=%v\n", inlineHotCallSiteThresholdPercent)
117 }
118
119 if x := base.Debug.PGOInlineBudget; x != 0 {
120 inlineHotMaxBudget = int32(x)
121 }
122
123 for _, n := range hotCallsites {
124
125 if callee := p.WeightedCG.IRNodes[n.CalleeName]; callee != nil {
126 candHotCalleeMap[callee] = struct{}{}
127 }
128
129 if caller := p.WeightedCG.IRNodes[n.CallerName]; caller != nil && caller.AST != nil {
130 csi := pgoir.CallSiteInfo{LineOffset: n.CallSiteOffset, Caller: caller.AST}
131 candHotEdgeMap[csi] = struct{}{}
132 }
133 }
134
135 if base.Debug.PGODebug >= 3 {
136 fmt.Printf("hot-cg before inline in dot format:")
137 p.PrintWeightedCallGraphDOT(inlineHotCallSiteThresholdPercent)
138 }
139 }
140
141
142
143
144
145
146
147 func hotNodesFromCDF(p *pgoir.Profile) (float64, []pgo.NamedCallEdge) {
148 cum := int64(0)
149 for i, n := range p.NamedEdgeMap.ByWeight {
150 w := p.NamedEdgeMap.Weight[n]
151 cum += w
152 if pgo.WeightInPercentage(cum, p.TotalWeight) > inlineCDFHotCallSiteThresholdPercent {
153
154
155
156 return pgo.WeightInPercentage(w, p.TotalWeight), p.NamedEdgeMap.ByWeight[:i+1]
157 }
158 }
159 return 0, p.NamedEdgeMap.ByWeight
160 }
161
162
163 func CanInlineFuncs(funcs []*ir.Func, profile *pgoir.Profile) {
164 if profile != nil {
165 PGOInlinePrologue(profile)
166 }
167
168 if base.Flag.LowerL == 0 {
169 return
170 }
171
172 ir.VisitFuncsBottomUp(funcs, func(funcs []*ir.Func, recursive bool) {
173 numfns := numNonClosures(funcs)
174
175 for _, fn := range funcs {
176 if !recursive || numfns > 1 {
177
178
179
180 CanInline(fn, profile)
181 } else {
182 if base.Flag.LowerM > 1 && fn.OClosure == nil {
183 fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(fn), fn.Nname)
184 }
185 }
186 if inlheur.Enabled() {
187 analyzeFuncProps(fn, profile)
188 }
189 }
190 })
191 }
192
193
194
195
196
197
198
199
200 func inlineBudget(fn *ir.Func, profile *pgoir.Profile, relaxed bool, verbose bool) int32 {
201
202 budget := int32(inlineMaxBudget)
203 if IsPgoHotFunc(fn, profile) {
204 budget = inlineHotMaxBudget
205 if verbose {
206 fmt.Printf("hot-node enabled increased budget=%v for func=%v\n", budget, ir.PkgFuncName(fn))
207 }
208 }
209 if relaxed {
210 budget += inlheur.BudgetExpansion(inlineMaxBudget)
211 }
212 if fn.ClosureParent != nil {
213
214 budget = max(budget, inlineClosureCalledOnceCost)
215 }
216 return budget
217 }
218
219
220
221
222 func CanInline(fn *ir.Func, profile *pgoir.Profile) {
223 if fn.Nname == nil {
224 base.Fatalf("CanInline no nname %+v", fn)
225 }
226
227 var reason string
228 if base.Flag.LowerM > 1 || logopt.Enabled() {
229 defer func() {
230 if reason != "" {
231 if base.Flag.LowerM > 1 {
232 fmt.Printf("%v: cannot inline %v: %s\n", ir.Line(fn), fn.Nname, reason)
233 }
234 if logopt.Enabled() {
235 logopt.LogOpt(fn.Pos(), "cannotInlineFunction", "inline", ir.FuncName(fn), reason)
236 }
237 }
238 }()
239 }
240
241 reason = InlineImpossible(fn)
242 if reason != "" {
243 return
244 }
245 if fn.Typecheck() == 0 {
246 base.Fatalf("CanInline on non-typechecked function %v", fn)
247 }
248
249 n := fn.Nname
250 if n.Func.InlinabilityChecked() {
251 return
252 }
253 defer n.Func.SetInlinabilityChecked(true)
254
255 cc := int32(inlineExtraCallCost)
256 if base.Flag.LowerL == 4 {
257 cc = 1
258 }
259
260
261 relaxed := inlheur.Enabled()
262
263
264 budget := inlineBudget(fn, profile, relaxed, base.Debug.PGODebug > 0)
265
266
267
268
269
270
271
272
273
274
275 visitor := hairyVisitor{
276 curFunc: fn,
277 isBigFunc: IsBigFunc(fn),
278 budget: budget,
279 maxBudget: budget,
280 extraCallCost: cc,
281 profile: profile,
282 }
283 if visitor.tooHairy(fn) {
284 reason = visitor.reason
285 return
286 }
287
288 n.Func.Inl = &ir.Inline{
289 Cost: budget - visitor.budget,
290 Dcl: pruneUnusedAutos(n.Func.Dcl, &visitor),
291 HaveDcl: true,
292 CanDelayResults: canDelayResults(fn),
293 }
294 if base.Flag.LowerM != 0 || logopt.Enabled() {
295 noteInlinableFunc(n, fn, budget-visitor.budget)
296 }
297 }
298
299
300
301 func noteInlinableFunc(n *ir.Name, fn *ir.Func, cost int32) {
302 if base.Flag.LowerM > 1 {
303 fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, cost, fn.Type(), ir.Nodes(fn.Body))
304 } else if base.Flag.LowerM != 0 {
305 fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
306 }
307
308 if logopt.Enabled() {
309 logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", cost))
310 }
311 }
312
313
314
315 func InlineImpossible(fn *ir.Func) string {
316 var reason string
317 if fn.Nname == nil {
318 reason = "no name"
319 return reason
320 }
321
322
323 if fn.Pragma&ir.Noinline != 0 {
324 reason = "marked go:noinline"
325 return reason
326 }
327
328
329 if base.Flag.Race && fn.Pragma&ir.Norace != 0 {
330 reason = "marked go:norace with -race compilation"
331 return reason
332 }
333
334
335 if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 {
336 reason = "marked go:nocheckptr"
337 return reason
338 }
339
340
341
342 if fn.Pragma&ir.CgoUnsafeArgs != 0 {
343 reason = "marked go:cgo_unsafe_args"
344 return reason
345 }
346
347
348
349
350
351
352
353 if fn.Pragma&ir.UintptrKeepAlive != 0 {
354 reason = "marked as having a keep-alive uintptr argument"
355 return reason
356 }
357
358
359
360 if fn.Pragma&ir.UintptrEscapes != 0 {
361 reason = "marked as having an escaping uintptr argument"
362 return reason
363 }
364
365
366
367
368 if fn.Pragma&ir.Yeswritebarrierrec != 0 {
369 reason = "marked go:yeswritebarrierrec"
370 return reason
371 }
372
373
374
375 if len(fn.Body) == 0 && !typecheck.HaveInlineBody(fn) {
376 reason = "no function body"
377 return reason
378 }
379
380 return ""
381 }
382
383
384
385 func canDelayResults(fn *ir.Func) bool {
386
387
388
389
390
391 nreturns := 0
392 ir.VisitList(fn.Body, func(n ir.Node) {
393 if n, ok := n.(*ir.ReturnStmt); ok {
394 nreturns++
395 if len(n.Results) == 0 {
396 nreturns++
397 }
398 }
399 })
400
401 if nreturns != 1 {
402 return false
403 }
404
405
406 for _, param := range fn.Type().Results() {
407 if sym := param.Sym; sym != nil && !sym.IsBlank() {
408 return false
409 }
410 }
411
412 return true
413 }
414
415
416
417 type hairyVisitor struct {
418
419 curFunc *ir.Func
420 isBigFunc bool
421 budget int32
422 maxBudget int32
423 reason string
424 extraCallCost int32
425 usedLocals ir.NameSet
426 do func(ir.Node) bool
427 profile *pgoir.Profile
428 }
429
430 func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
431 v.do = v.doNode
432 if ir.DoChildren(fn, v.do) {
433 return true
434 }
435 if v.budget < 0 {
436 v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", v.maxBudget-v.budget, v.maxBudget)
437 return true
438 }
439 return false
440 }
441
442
443
444 func (v *hairyVisitor) doNode(n ir.Node) bool {
445 if n == nil {
446 return false
447 }
448 opSwitch:
449 switch n.Op() {
450
451 case ir.OCALLFUNC:
452 n := n.(*ir.CallExpr)
453 var cheap bool
454 if n.Fun.Op() == ir.ONAME {
455 name := n.Fun.(*ir.Name)
456 if name.Class == ir.PFUNC {
457 s := name.Sym()
458 fn := s.Name
459 switch s.Pkg.Path {
460 case "internal/abi":
461 switch fn {
462 case "NoEscape":
463
464
465
466 cheap = true
467 }
468 case "internal/runtime/sys":
469 switch fn {
470 case "GetCallerPC", "GetCallerSP":
471
472
473
474 v.reason = "call to " + fn
475 return true
476 }
477 case "go.runtime":
478 switch fn {
479 case "throw":
480
481 v.budget -= inlineExtraThrowCost
482 break opSwitch
483 case "panicrangestate":
484 cheap = true
485 }
486 case "hash/maphash":
487 if strings.HasPrefix(fn, "escapeForHash[") {
488
489
490 cheap = true
491 }
492 }
493 }
494
495
496
497
498
499
500
501
502
503
504 if isAtomicCoverageCounterUpdate(n) {
505 return false
506 }
507 }
508 if n.Fun.Op() == ir.OMETHEXPR {
509 if meth := ir.MethodExprName(n.Fun); meth != nil {
510 if fn := meth.Func; fn != nil {
511 s := fn.Sym()
512 if types.RuntimeSymName(s) == "heapBits.nextArena" {
513
514
515
516 cheap = true
517 }
518
519
520
521
522 if base.Ctxt.Arch.CanMergeLoads && s.Pkg.Path == "encoding/binary" {
523 switch s.Name {
524 case "littleEndian.Uint64", "littleEndian.Uint32", "littleEndian.Uint16",
525 "bigEndian.Uint64", "bigEndian.Uint32", "bigEndian.Uint16",
526 "littleEndian.PutUint64", "littleEndian.PutUint32", "littleEndian.PutUint16",
527 "bigEndian.PutUint64", "bigEndian.PutUint32", "bigEndian.PutUint16",
528 "littleEndian.AppendUint64", "littleEndian.AppendUint32", "littleEndian.AppendUint16",
529 "bigEndian.AppendUint64", "bigEndian.AppendUint32", "bigEndian.AppendUint16":
530 cheap = true
531 }
532 }
533 }
534 }
535 }
536
537
538
539 extraCost := v.extraCallCost
540
541 if n.Fun.Op() == ir.ONAME {
542 name := n.Fun.(*ir.Name)
543 if name.Class == ir.PFUNC {
544
545
546
547
548 if base.Ctxt.Arch.CanMergeLoads && name.Sym().Pkg.Path == "internal/byteorder" {
549 switch name.Sym().Name {
550 case "LEUint64", "LEUint32", "LEUint16",
551 "BEUint64", "BEUint32", "BEUint16",
552 "LEPutUint64", "LEPutUint32", "LEPutUint16",
553 "BEPutUint64", "BEPutUint32", "BEPutUint16",
554 "LEAppendUint64", "LEAppendUint32", "LEAppendUint16",
555 "BEAppendUint64", "BEAppendUint32", "BEAppendUint16":
556 cheap = true
557 }
558 }
559 }
560 if name.Class == ir.PPARAM || name.Class == ir.PAUTOHEAP && name.IsClosureVar() {
561 extraCost = min(extraCost, inlineParamCallCost)
562 }
563 }
564
565 if cheap {
566 break
567 }
568
569 if ir.IsIntrinsicCall(n) {
570
571 break
572 }
573
574 if callee := inlCallee(v.curFunc, n.Fun, v.profile, false); callee != nil && typecheck.HaveInlineBody(callee) {
575
576
577
578 if ok, _, _ := canInlineCallExpr(v.curFunc, n, callee, v.isBigFunc, false, false); ok {
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593 v.budget -= callee.Inl.Cost
594 break
595 }
596 }
597
598
599 v.budget -= extraCost
600
601 case ir.OCALLMETH:
602 base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
603
604
605 case ir.OCALL, ir.OCALLINTER:
606
607 v.budget -= v.extraCallCost
608
609 case ir.OPANIC:
610 n := n.(*ir.UnaryExpr)
611 if n.X.Op() == ir.OCONVIFACE && n.X.(*ir.ConvExpr).Implicit() {
612
613
614
615 v.budget++
616 }
617 v.budget -= inlineExtraPanicCost
618
619 case ir.ORECOVER:
620 base.FatalfAt(n.Pos(), "ORECOVER missed typecheck")
621 case ir.ORECOVERFP:
622
623
624 v.reason = "call to recover"
625 return true
626
627 case ir.OCLOSURE:
628 if base.Debug.InlFuncsWithClosures == 0 {
629 v.reason = "not inlining functions with closures"
630 return true
631 }
632
633
634
635
636
637
638
639 v.budget -= 15
640
641 case ir.OGO, ir.ODEFER, ir.OTAILCALL:
642 v.reason = "unhandled op " + n.Op().String()
643 return true
644
645 case ir.OAPPEND:
646 v.budget -= inlineExtraAppendCost
647
648 case ir.OADDR:
649 n := n.(*ir.AddrExpr)
650
651 if dot, ok := n.X.(*ir.SelectorExpr); ok && (dot.Op() == ir.ODOT || dot.Op() == ir.ODOTPTR) {
652 if _, ok := dot.X.(*ir.Name); ok && dot.Selection.Offset == 0 {
653 v.budget += 2
654 }
655 }
656
657 case ir.ODEREF:
658
659 n := n.(*ir.StarExpr)
660
661 ptr := n.X
662 for ptr.Op() == ir.OCONVNOP {
663 ptr = ptr.(*ir.ConvExpr).X
664 }
665 if ptr.Op() == ir.OADDR {
666 v.budget += 1
667 }
668
669 case ir.OCONVNOP:
670
671 v.budget++
672
673 case ir.OFALL, ir.OTYPE:
674
675 return false
676
677 case ir.OIF:
678 n := n.(*ir.IfStmt)
679 if ir.IsConst(n.Cond, constant.Bool) {
680
681 if doList(n.Init(), v.do) {
682 return true
683 }
684 if ir.BoolVal(n.Cond) {
685 return doList(n.Body, v.do)
686 } else {
687 return doList(n.Else, v.do)
688 }
689 }
690
691 case ir.ONAME:
692 n := n.(*ir.Name)
693 if n.Class == ir.PAUTO {
694 v.usedLocals.Add(n)
695 }
696
697 case ir.OBLOCK:
698
699
700
701 v.budget++
702
703 case ir.OMETHVALUE, ir.OSLICELIT:
704 v.budget--
705
706 case ir.OMETHEXPR:
707 v.budget++
708
709 case ir.OAS2:
710 n := n.(*ir.AssignListStmt)
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729 if len(n.Rhs) > 0 {
730 if init := n.Rhs[0].Init(); len(init) == 1 {
731 if _, ok := init[0].(*ir.AssignListStmt); ok {
732
733
734
735
736 v.budget += 4*int32(len(n.Lhs)) + 1
737 }
738 }
739 }
740
741 case ir.OAS:
742
743
744
745
746
747
748
749
750
751
752 n := n.(*ir.AssignStmt)
753 if n.X.Op() == ir.OINDEX && isIndexingCoverageCounter(n.X) {
754 return false
755 }
756 }
757
758 v.budget--
759
760
761 if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() {
762 v.reason = "too expensive"
763 return true
764 }
765
766 return ir.DoChildren(n, v.do)
767 }
768
769
770
771
772 func IsBigFunc(fn *ir.Func) bool {
773 budget := inlineBigFunctionNodes
774 return ir.Any(fn, func(n ir.Node) bool {
775
776
777 if n, ok := n.(*ir.AssignListStmt); ok && n.Op() == ir.OAS2 && len(n.Rhs) > 0 {
778 if init := n.Rhs[0].Init(); len(init) == 1 {
779 if _, ok := init[0].(*ir.AssignListStmt); ok {
780 budget += 4*len(n.Lhs) + 1
781 }
782 }
783 }
784
785 budget--
786 return budget <= 0
787 })
788 }
789
790
791
792
793 func inlineCallCheck(callerfn *ir.Func, call *ir.CallExpr) (bool, bool) {
794 if base.Flag.LowerL == 0 {
795 return false, false
796 }
797 if call.Op() != ir.OCALLFUNC {
798 return false, false
799 }
800 if call.GoDefer || call.NoInline {
801 return false, false
802 }
803
804
805
806 if base.Debug.Checkptr != 0 && call.Fun.Op() == ir.OMETHEXPR {
807 if method := ir.MethodExprName(call.Fun); method != nil {
808 switch types.ReflectSymName(method.Sym()) {
809 case "Value.UnsafeAddr", "Value.Pointer":
810 return false, false
811 }
812 }
813 }
814
815
816
817 if fn := ir.StaticCalleeName(call.Fun); fn != nil && fn.Sym().Pkg.Path == "hash/maphash" &&
818 strings.HasPrefix(fn.Sym().Name, "escapeForHash[") {
819 return false, true
820 }
821
822 if ir.IsIntrinsicCall(call) {
823 return false, true
824 }
825 return true, false
826 }
827
828
829
830
831 func InlineCallTarget(callerfn *ir.Func, call *ir.CallExpr, profile *pgoir.Profile) *ir.Func {
832 if mightInline, _ := inlineCallCheck(callerfn, call); !mightInline {
833 return nil
834 }
835 return inlCallee(callerfn, call.Fun, profile, true)
836 }
837
838
839
840 func TryInlineCall(callerfn *ir.Func, call *ir.CallExpr, bigCaller bool, profile *pgoir.Profile, closureCalledOnce bool) *ir.InlinedCallExpr {
841 mightInline, isIntrinsic := inlineCallCheck(callerfn, call)
842
843
844 if (mightInline || isIntrinsic) && base.Flag.LowerM > 3 {
845 fmt.Printf("%v:call to func %+v\n", ir.Line(call), call.Fun)
846 }
847 if !mightInline {
848 return nil
849 }
850
851 if fn := inlCallee(callerfn, call.Fun, profile, false); fn != nil && typecheck.HaveInlineBody(fn) {
852 return mkinlcall(callerfn, call, fn, bigCaller, closureCalledOnce)
853 }
854 return nil
855 }
856
857
858
859
860 func inlCallee(caller *ir.Func, fn ir.Node, profile *pgoir.Profile, resolveOnly bool) (res *ir.Func) {
861 fn = ir.StaticValue(fn)
862 switch fn.Op() {
863 case ir.OMETHEXPR:
864 fn := fn.(*ir.SelectorExpr)
865 n := ir.MethodExprName(fn)
866
867
868
869 if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) {
870 return nil
871 }
872 return n.Func
873 case ir.ONAME:
874 fn := fn.(*ir.Name)
875 if fn.Class == ir.PFUNC {
876 return fn.Func
877 }
878 case ir.OCLOSURE:
879 fn := fn.(*ir.ClosureExpr)
880 c := fn.Func
881 if len(c.ClosureVars) != 0 && c.ClosureVars[0].Outer.Curfn != caller {
882 return nil
883 }
884 if !resolveOnly {
885 CanInline(c, profile)
886 }
887 return c
888 }
889 return nil
890 }
891
892 var inlgen int
893
894
895
896 var SSADumpInline = func(*ir.Func) {}
897
898
899
900 var InlineCall = func(callerfn *ir.Func, call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExpr {
901 base.Fatalf("inline.InlineCall not overridden")
902 panic("unreachable")
903 }
904
905
906
907
908
909
910
911
912 func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCalledOnce bool) (bool, int32, int32, bool) {
913 maxCost := int32(inlineMaxBudget)
914
915 if bigCaller {
916
917
918 maxCost = inlineBigFunctionMaxCost
919 }
920
921 if callee.ClosureParent != nil {
922 maxCost *= 2
923 if closureCalledOnce {
924 maxCost = max(maxCost, inlineClosureCalledOnceCost)
925 }
926 }
927
928 metric := callee.Inl.Cost
929 if inlheur.Enabled() {
930 score, ok := inlheur.GetCallSiteScore(caller, n)
931 if ok {
932 metric = int32(score)
933 }
934 }
935
936 lineOffset := pgoir.NodeLineOffset(n, caller)
937 csi := pgoir.CallSiteInfo{LineOffset: lineOffset, Caller: caller}
938 _, hot := candHotEdgeMap[csi]
939
940 if metric <= maxCost {
941
942 return true, 0, metric, hot
943 }
944
945
946
947
948 if !hot {
949
950 return false, maxCost, metric, false
951 }
952
953
954
955 if bigCaller {
956 if base.Debug.PGODebug > 0 {
957 fmt.Printf("hot-big check disallows inlining for call %s (cost %d) at %v in big function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller))
958 }
959 return false, maxCost, metric, false
960 }
961
962 if metric > inlineHotMaxBudget {
963 return false, inlineHotMaxBudget, metric, false
964 }
965
966 if !base.PGOHash.MatchPosWithInfo(n.Pos(), "inline", nil) {
967
968 return false, maxCost, metric, false
969 }
970
971 if base.Debug.PGODebug > 0 {
972 fmt.Printf("hot-budget check allows inlining for call %s (cost %d) at %v in function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller))
973 }
974
975 return true, 0, metric, hot
976 }
977
978
979 func parsePos(pos src.XPos, posTmp []src.Pos) ([]src.Pos, src.Pos) {
980 ctxt := base.Ctxt
981 ctxt.AllPos(pos, func(p src.Pos) {
982 posTmp = append(posTmp, p)
983 })
984 l := len(posTmp) - 1
985 return posTmp[:l], posTmp[l]
986 }
987
988
989
990
991
992
993
994
995 func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCaller, closureCalledOnce bool, log bool) (bool, int32, bool) {
996 if callee.Inl == nil {
997
998 if log && logopt.Enabled() {
999 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
1000 fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(callee)))
1001 }
1002 return false, 0, false
1003 }
1004
1005 ok, maxCost, callSiteScore, hot := inlineCostOK(n, callerfn, callee, bigCaller, closureCalledOnce)
1006 if !ok {
1007
1008 if log && logopt.Enabled() {
1009 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
1010 fmt.Sprintf("cost %d of %s exceeds max caller cost %d", callee.Inl.Cost, ir.PkgFuncName(callee), maxCost))
1011 }
1012 return false, 0, false
1013 }
1014
1015 callees, calleeInner := parsePos(n.Pos(), make([]src.Pos, 0, 10))
1016
1017 for _, p := range callees {
1018 if p.Line() == calleeInner.Line() && p.Col() == calleeInner.Col() && p.AbsFilename() == calleeInner.AbsFilename() {
1019 if log && logopt.Enabled() {
1020 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
1021 }
1022 return false, 0, false
1023 }
1024 }
1025
1026 if callee == callerfn {
1027
1028 if log && logopt.Enabled() {
1029 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
1030 }
1031 return false, 0, false
1032 }
1033
1034 isClosureParent := func(closure, parent *ir.Func) bool {
1035 for p := closure.ClosureParent; p != nil; p = p.ClosureParent {
1036 if p == parent {
1037 return true
1038 }
1039 }
1040 return false
1041 }
1042 if isClosureParent(callerfn, callee) {
1043
1044 if log && logopt.Enabled() {
1045 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to closure parent: %s, %s", ir.FuncName(callerfn), ir.FuncName(callee)))
1046 }
1047 return false, 0, false
1048 }
1049 if isClosureParent(callee, callerfn) {
1050
1051 if ir.Any(callee, func(node ir.Node) bool {
1052 if call, ok := node.(*ir.CallExpr); ok {
1053 if name, ok := call.Fun.(*ir.Name); ok && isClosureParent(callerfn, name.Func) {
1054 return true
1055 }
1056 }
1057 return false
1058 }) {
1059 if log && logopt.Enabled() {
1060 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to closure parent: %s, %s", ir.FuncName(callerfn), ir.FuncName(callee)))
1061 }
1062 return false, 0, false
1063 }
1064 }
1065 do := func(fn *ir.Func) bool {
1066
1067
1068 return ir.Any(fn, func(node ir.Node) bool {
1069 if call, ok := node.(*ir.CallExpr); ok {
1070 for _, arg := range call.Args {
1071 if call.Fun == arg {
1072 return true
1073 }
1074 }
1075 }
1076 return false
1077 })
1078 }
1079 for _, fn := range []*ir.Func{callerfn, callee} {
1080 if do(fn) {
1081 if log && logopt.Enabled() {
1082 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to function: %s", ir.FuncName(fn)))
1083 }
1084 return false, 0, false
1085 }
1086 }
1087
1088 if base.Flag.Cfg.Instrumenting && types.IsNoInstrumentPkg(callee.Sym().Pkg) {
1089
1090
1091
1092
1093
1094
1095 if log && logopt.Enabled() {
1096 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
1097 fmt.Sprintf("call to runtime function %s in instrumented build", ir.PkgFuncName(callee)))
1098 }
1099 return false, 0, false
1100 }
1101
1102 if base.Flag.Race && types.IsNoRacePkg(callee.Sym().Pkg) {
1103 if log && logopt.Enabled() {
1104 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
1105 fmt.Sprintf(`call to into "no-race" package function %s in race build`, ir.PkgFuncName(callee)))
1106 }
1107 return false, 0, false
1108 }
1109
1110 if base.Debug.Checkptr != 0 && types.IsRuntimePkg(callee.Sym().Pkg) {
1111
1112 if log && logopt.Enabled() {
1113 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
1114 fmt.Sprintf(`call to into runtime package function %s in -d=checkptr build`, ir.PkgFuncName(callee)))
1115 }
1116 return false, 0, false
1117 }
1118
1119
1120
1121
1122
1123
1124
1125
1126 parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()
1127 sym := callee.Linksym()
1128 for inlIndex := parent; inlIndex >= 0; inlIndex = base.Ctxt.InlTree.Parent(inlIndex) {
1129 if base.Ctxt.InlTree.InlinedFunction(inlIndex) == sym {
1130 if log {
1131 if base.Flag.LowerM > 1 {
1132 fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), callee, ir.FuncName(callerfn))
1133 }
1134 if logopt.Enabled() {
1135 logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
1136 fmt.Sprintf("repeated recursive cycle to %s", ir.PkgFuncName(callee)))
1137 }
1138 }
1139 return false, 0, false
1140 }
1141 }
1142
1143 return true, callSiteScore, hot
1144 }
1145
1146
1147
1148
1149
1150
1151
1152
1153 func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller, closureCalledOnce bool) *ir.InlinedCallExpr {
1154 ok, score, hot := canInlineCallExpr(callerfn, n, fn, bigCaller, closureCalledOnce, true)
1155 if !ok {
1156 return nil
1157 }
1158 if hot {
1159 hasHotCall[callerfn] = struct{}{}
1160 }
1161 typecheck.AssertFixedCall(n)
1162
1163 parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()
1164 sym := fn.Linksym()
1165 inlIndex := base.Ctxt.InlTree.Add(parent, n.Pos(), sym, ir.FuncName(fn))
1166
1167 closureInitLSym := func(n *ir.CallExpr, fn *ir.Func) {
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189 if n.Op() != ir.OCALLFUNC {
1190
1191 return
1192 }
1193 if n.Fun.Op() != ir.OCLOSURE {
1194
1195 return
1196 }
1197
1198 clo := n.Fun.(*ir.ClosureExpr)
1199 if !clo.Func.IsClosure() {
1200
1201 return
1202 }
1203
1204 ir.InitLSym(fn, true)
1205 }
1206
1207 closureInitLSym(n, fn)
1208
1209 if base.Flag.GenDwarfInl > 0 {
1210 if !sym.WasInlined() {
1211 base.Ctxt.DwFixups.SetPrecursorFunc(sym, fn)
1212 sym.Set(obj.AttrWasInlined, true)
1213 }
1214 }
1215
1216 if base.Flag.LowerM != 0 {
1217 if buildcfg.Experiment.NewInliner {
1218 fmt.Printf("%v: inlining call to %v with score %d\n",
1219 ir.Line(n), fn, score)
1220 } else {
1221 fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
1222 }
1223 }
1224 if base.Flag.LowerM > 2 {
1225 fmt.Printf("%v: Before inlining: %+v\n", ir.Line(n), n)
1226 }
1227
1228 res := InlineCall(callerfn, n, fn, inlIndex)
1229
1230 if res == nil {
1231 base.FatalfAt(n.Pos(), "inlining call to %v failed", fn)
1232 }
1233
1234 if base.Flag.LowerM > 2 {
1235 fmt.Printf("%v: After inlining %+v\n\n", ir.Line(res), res)
1236 }
1237
1238 if inlheur.Enabled() {
1239 inlheur.UpdateCallsiteTable(callerfn, n, res)
1240 }
1241
1242 return res
1243 }
1244
1245
1246 func CalleeEffects(init *ir.Nodes, callee ir.Node) {
1247 for {
1248 init.Append(ir.TakeInit(callee)...)
1249
1250 switch callee.Op() {
1251 case ir.ONAME, ir.OCLOSURE, ir.OMETHEXPR:
1252 return
1253
1254 case ir.OCONVNOP:
1255 conv := callee.(*ir.ConvExpr)
1256 callee = conv.X
1257
1258 case ir.OINLCALL:
1259 ic := callee.(*ir.InlinedCallExpr)
1260 init.Append(ic.Body.Take()...)
1261 callee = ic.SingleResult()
1262
1263 default:
1264 base.FatalfAt(callee.Pos(), "unexpected callee expression: %v", callee)
1265 }
1266 }
1267 }
1268
1269 func pruneUnusedAutos(ll []*ir.Name, vis *hairyVisitor) []*ir.Name {
1270 s := make([]*ir.Name, 0, len(ll))
1271 for _, n := range ll {
1272 if n.Class == ir.PAUTO {
1273 if !vis.usedLocals.Has(n) {
1274
1275
1276 base.FatalfAt(n.Pos(), "unused auto: %v", n)
1277 continue
1278 }
1279 }
1280 s = append(s, n)
1281 }
1282 return s
1283 }
1284
1285
1286 func numNonClosures(list []*ir.Func) int {
1287 count := 0
1288 for _, fn := range list {
1289 if fn.OClosure == nil {
1290 count++
1291 }
1292 }
1293 return count
1294 }
1295
1296 func doList(list []ir.Node, do func(ir.Node) bool) bool {
1297 for _, x := range list {
1298 if x != nil {
1299 if do(x) {
1300 return true
1301 }
1302 }
1303 }
1304 return false
1305 }
1306
1307
1308
1309 func isIndexingCoverageCounter(n ir.Node) bool {
1310 if n.Op() != ir.OINDEX {
1311 return false
1312 }
1313 ixn := n.(*ir.IndexExpr)
1314 if ixn.X.Op() != ir.ONAME || !ixn.X.Type().IsArray() {
1315 return false
1316 }
1317 nn := ixn.X.(*ir.Name)
1318
1319
1320
1321 return nn.CoverageAuxVar()
1322 }
1323
1324
1325
1326
1327 func isAtomicCoverageCounterUpdate(cn *ir.CallExpr) bool {
1328 if cn.Fun.Op() != ir.ONAME {
1329 return false
1330 }
1331 name := cn.Fun.(*ir.Name)
1332 if name.Class != ir.PFUNC {
1333 return false
1334 }
1335 fn := name.Sym().Name
1336 if name.Sym().Pkg.Path != "sync/atomic" ||
1337 (fn != "AddUint32" && fn != "StoreUint32") {
1338 return false
1339 }
1340 if len(cn.Args) != 2 || cn.Args[0].Op() != ir.OADDR {
1341 return false
1342 }
1343 adn := cn.Args[0].(*ir.AddrExpr)
1344 v := isIndexingCoverageCounter(adn.X)
1345 return v
1346 }
1347
1348 func PostProcessCallSites(profile *pgoir.Profile) {
1349 if base.Debug.DumpInlCallSiteScores != 0 {
1350 budgetCallback := func(fn *ir.Func, prof *pgoir.Profile) (int32, bool) {
1351 v := inlineBudget(fn, prof, false, false)
1352 return v, v == inlineHotMaxBudget
1353 }
1354 inlheur.DumpInlCallSiteScores(profile, budgetCallback)
1355 }
1356 }
1357
1358 func analyzeFuncProps(fn *ir.Func, p *pgoir.Profile) {
1359 canInline := func(fn *ir.Func) { CanInline(fn, p) }
1360 budgetForFunc := func(fn *ir.Func) int32 {
1361 return inlineBudget(fn, p, true, false)
1362 }
1363 inlheur.AnalyzeFunc(fn, canInline, budgetForFunc, inlineMaxBudget)
1364 }
1365
View as plain text