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