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