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