@@ -51,8 +51,109 @@ const (
51
51
52
52
inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big".
53
53
inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
54
+
55
+ inlineBigForCost = 51 // FORs with at least this cost are considered "big".
56
+ inlineForMaxCost = 37 // FORs should be cheaper than this to boost inlining into themselves.
57
+ inlineIntoForExtraCallCost = 6 // These extra costs were benchmarked to provided most benefit with no bad surprises.
58
+ inlineIntoForExtraInlinableCallCost = 10
59
+ inlineIntoForExtraBudget = 16 // Extra budget when inlining into FORs which are not "big".
60
+
61
+ // The upper budget for a visitor. It accounts the maximum cost with which a function could be inlined.
62
+ inlineVisitorBudget = inlineMaxBudget + inlineIntoForExtraBudget
54
63
)
55
64
65
+ // isInlinable checks if the function can be inlined in a 'typical' scenario
66
+ // when no boosts are applied.
67
+ func isInlinable (fn * ir.Func ) bool {
68
+ return fn != nil && fn .Inl != nil && fn .Inl .Cost <= inlineMaxBudget
69
+ }
70
+
71
+ type forContext struct {
72
+ cost int32
73
+ }
74
+
75
+ type inlContext struct {
76
+ // Map to keep track of functions that have been inlined at a particular
77
+ // call site, in order to stop inlining when we reach the beginning of a
78
+ // recursion cycle again. We don't inline immediately recursive functions,
79
+ // but allow inlining if there is a recursion cycle of many functions.
80
+ // Most likely, the inlining will stop before we even hit the beginning of
81
+ // the cycle again, but the map catches the unusual case.
82
+ inlinedCallees map [* ir.Func ]bool
83
+
84
+ // Stack to recognise which call nodes are located inside fors, while doing inlnode.
85
+ forsStack []forContext
86
+ initialInlineBudget int32 // Initial inline budget, boosts are calculated related to this.
87
+ }
88
+
89
+ func (ctx inlContext ) canBoostInliningIntoFor () bool {
90
+ // The decision is based on:
91
+ // 1) The first FOR in the stack is not "big".
92
+ // 2) The last FOR cost should be less inlineForMaxCost.
93
+ return len (ctx .forsStack ) > 0 && ctx .forsStack [0 ].cost < inlineBigForCost && ctx .forsStack [len (ctx .forsStack )- 1 ].cost < inlineForMaxCost
94
+ }
95
+
96
+ func (ctx * inlContext ) Init (fn * ir.Func ) {
97
+ ctx .inlinedCallees = make (map [* ir.Func ]bool )
98
+
99
+ if isBigFunc (fn ) {
100
+ ctx .initialInlineBudget = inlineBigFunctionMaxCost
101
+ } else {
102
+ ctx .initialInlineBudget = inlineMaxBudget
103
+ }
104
+ }
105
+
106
+ func (ctx * inlContext ) PushFor (n ir.Node ) {
107
+ ctx .forsStack = append (ctx .forsStack , forContext {forCost (n )})
108
+
109
+ if base .Flag .LowerM > 1 {
110
+ fmt .Printf ("%v: add for to stack %v\n " , ir .Line (n ), ctx .forsStack )
111
+ }
112
+ }
113
+
114
+ func (ctx * inlContext ) PopFor () {
115
+ ctx .forsStack = ctx .forsStack [:len (ctx .forsStack )- 1 ]
116
+ }
117
+
118
+ func (ctx inlContext ) InlineBudget () int32 {
119
+ finalBudget := ctx .initialInlineBudget
120
+ if ctx .canBoostInliningIntoFor () && ctx .initialInlineBudget == inlineMaxBudget {
121
+ // Boosts only regular functions
122
+ finalBudget += inlineIntoForExtraBudget
123
+ }
124
+
125
+ return finalBudget
126
+ }
127
+
128
+ func forCost (n ir.Node ) int32 {
129
+ cost := int32 (inlineBigForCost )
130
+ ir .Any (n , func (n ir.Node ) bool {
131
+ cost --
132
+
133
+ switch n .Op () {
134
+ case ir .OCALLFUNC :
135
+ call := n .(* ir.CallExpr )
136
+ if call .NoInline {
137
+ // These are deferred or go-ed calls, treating a FOR as "big".
138
+ cost = - 1
139
+ break
140
+ }
141
+
142
+ if ir .IsIntrinsicCall (call ) {
143
+ // Treat like any other node.
144
+ break
145
+ }
146
+
147
+ cost -= inlineIntoForExtraCallCost
148
+ if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
149
+ cost -= inlineIntoForExtraInlinableCallCost
150
+ }
151
+ }
152
+ return cost < 0
153
+ })
154
+ return inlineBigForCost - cost
155
+ }
156
+
56
157
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
57
158
func InlinePackage () {
58
159
ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
@@ -167,7 +268,7 @@ func CanInline(fn *ir.Func) {
167
268
// list. See issue 25249 for more context.
168
269
169
270
visitor := hairyVisitor {
170
- budget : inlineMaxBudget ,
271
+ budget : inlineVisitorBudget ,
171
272
extraCallCost : cc ,
172
273
}
173
274
if visitor .tooHairy (fn ) {
@@ -176,20 +277,24 @@ func CanInline(fn *ir.Func) {
176
277
}
177
278
178
279
n .Func .Inl = & ir.Inline {
179
- Cost : inlineMaxBudget - visitor .budget ,
280
+ Cost : inlineVisitorBudget - visitor .budget ,
180
281
Dcl : pruneUnusedAutos (n .Defn .(* ir.Func ).Dcl , & visitor ),
181
282
Body : inlcopylist (fn .Body ),
182
283
183
284
CanDelayResults : canDelayResults (fn ),
184
285
}
185
286
186
287
if base .Flag .LowerM > 1 {
187
- fmt .Printf ("%v: can inline %v with cost %d as: %v { %v }\n " , ir .Line (fn ), n , inlineMaxBudget - visitor .budget , fn .Type (), ir .Nodes (n .Func .Inl .Body ))
188
- } else if base .Flag .LowerM != 0 {
288
+ if isInlinable (n .Func ) {
289
+ fmt .Printf ("%v: can inline %v with cost %d as: %v { %v }\n " , ir .Line (fn ), n , n .Func .Inl .Cost , fn .Type (), ir .Nodes (n .Func .Inl .Body ))
290
+ } else {
291
+ fmt .Printf ("%v: can inline only into small FORs %v with cost %d as: %v { %v }\n " , ir .Line (fn ), n , n .Func .Inl .Cost , fn .Type (), ir .Nodes (n .Func .Inl .Body ))
292
+ }
293
+ } else if base .Flag .LowerM != 0 && isInlinable (n .Func ) {
189
294
fmt .Printf ("%v: can inline %v\n " , ir .Line (fn ), n )
190
295
}
191
296
if logopt .Enabled () {
192
- logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , inlineMaxBudget - visitor . budget ))
297
+ logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , n . Func . Inl . Cost ))
193
298
}
194
299
}
195
300
@@ -241,7 +346,7 @@ func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
241
346
return true
242
347
}
243
348
if v .budget < 0 {
244
- v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineMaxBudget - v .budget , inlineMaxBudget )
349
+ v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineVisitorBudget - v .budget , inlineVisitorBudget )
245
350
return true
246
351
}
247
352
return false
@@ -493,20 +598,13 @@ func inlcopy(n ir.Node) ir.Node {
493
598
func InlineCalls (fn * ir.Func ) {
494
599
savefn := ir .CurFunc
495
600
ir .CurFunc = fn
496
- maxCost := int32 (inlineMaxBudget )
497
- if isBigFunc (fn ) {
498
- maxCost = inlineBigFunctionMaxCost
499
- }
500
- // Map to keep track of functions that have been inlined at a particular
501
- // call site, in order to stop inlining when we reach the beginning of a
502
- // recursion cycle again. We don't inline immediately recursive functions,
503
- // but allow inlining if there is a recursion cycle of many functions.
504
- // Most likely, the inlining will stop before we even hit the beginning of
505
- // the cycle again, but the map catches the unusual case.
506
- inlMap := make (map [* ir.Func ]bool )
601
+
602
+ var inlCtx inlContext
603
+ inlCtx .Init (fn )
604
+
507
605
var edit func (ir.Node ) ir.Node
508
606
edit = func (n ir.Node ) ir.Node {
509
- return inlnode (n , maxCost , inlMap , edit )
607
+ return inlnode (n , & inlCtx , edit )
510
608
}
511
609
ir .EditChildren (fn , edit )
512
610
ir .CurFunc = savefn
@@ -525,11 +623,16 @@ func InlineCalls(fn *ir.Func) {
525
623
// shorter and less complicated.
526
624
// The result of inlnode MUST be assigned back to n, e.g.
527
625
// n.Left = inlnode(n.Left)
528
- func inlnode (n ir.Node , maxCost int32 , inlMap map [ * ir. Func ] bool , edit func (ir.Node ) ir.Node ) ir.Node {
626
+ func inlnode (n ir.Node , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
529
627
if n == nil {
530
628
return n
531
629
}
532
630
631
+ if n .Op () == ir .OFOR {
632
+ ctx .PushFor (n )
633
+ defer ctx .PopFor ()
634
+ }
635
+
533
636
switch n .Op () {
534
637
case ir .ODEFER , ir .OGO :
535
638
n := n .(* ir.GoDeferStmt )
@@ -584,7 +687,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
584
687
break
585
688
}
586
689
if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
587
- n = mkinlcall (call , fn , maxCost , inlMap , edit )
690
+ n = mkinlcall (call , fn , ctx , edit )
588
691
}
589
692
}
590
693
@@ -657,20 +760,20 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa
657
760
// parameters.
658
761
// The result of mkinlcall MUST be assigned back to n, e.g.
659
762
// n.Left = mkinlcall(n.Left, fn, isddd)
660
- func mkinlcall (n * ir.CallExpr , fn * ir.Func , maxCost int32 , inlMap map [ * ir. Func ] bool , edit func (ir.Node ) ir.Node ) ir.Node {
763
+ func mkinlcall (n * ir.CallExpr , fn * ir.Func , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
661
764
if fn .Inl == nil {
662
765
if logopt .Enabled () {
663
766
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
664
767
fmt .Sprintf ("%s cannot be inlined" , ir .PkgFuncName (fn )))
665
768
}
666
769
return n
667
770
}
668
- if fn .Inl .Cost > maxCost {
771
+ if fn .Inl .Cost > ctx . InlineBudget () {
669
772
// The inlined function body is too big. Typically we use this check to restrict
670
773
// inlining into very big functions. See issue 26546 and 17566.
671
774
if logopt .Enabled () {
672
775
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
673
- fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), maxCost ))
776
+ fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), ctx . InlineBudget () ))
674
777
}
675
778
return n
676
779
}
@@ -693,15 +796,15 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
693
796
return n
694
797
}
695
798
696
- if inlMap [fn ] {
799
+ if ctx . inlinedCallees [fn ] {
697
800
if base .Flag .LowerM > 1 {
698
801
fmt .Printf ("%v: cannot inline %v into %v: repeated recursive cycle\n " , ir .Line (n ), fn , ir .FuncName (ir .CurFunc ))
699
802
}
700
803
return n
701
804
}
702
- inlMap [fn ] = true
805
+ ctx . inlinedCallees [fn ] = true
703
806
defer func () {
704
- inlMap [fn ] = false
807
+ ctx . inlinedCallees [fn ] = false
705
808
}()
706
809
707
810
typecheck .FixVariadicCall (n )
0 commit comments