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