@@ -51,8 +51,103 @@ 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 ir .IsIntrinsicCall (call ) {
137
+ // Treat like any other node.
138
+ break
139
+ }
140
+
141
+ cost -= inlineIntoForExtraCallCost
142
+ if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
143
+ cost -= inlineIntoForExtraInlinableCallCost
144
+ }
145
+ }
146
+ return cost < 0
147
+ })
148
+ return inlineBigForCost - cost
149
+ }
150
+
56
151
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
57
152
func InlinePackage () {
58
153
ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
@@ -167,7 +262,7 @@ func CanInline(fn *ir.Func) {
167
262
// list. See issue 25249 for more context.
168
263
169
264
visitor := hairyVisitor {
170
- budget : inlineMaxBudget ,
265
+ budget : inlineVisitorBudget ,
171
266
extraCallCost : cc ,
172
267
}
173
268
if visitor .tooHairy (fn ) {
@@ -176,20 +271,24 @@ func CanInline(fn *ir.Func) {
176
271
}
177
272
178
273
n .Func .Inl = & ir.Inline {
179
- Cost : inlineMaxBudget - visitor .budget ,
274
+ Cost : inlineVisitorBudget - visitor .budget ,
180
275
Dcl : pruneUnusedAutos (n .Defn .(* ir.Func ).Dcl , & visitor ),
181
276
Body : inlcopylist (fn .Body ),
182
277
183
278
CanDelayResults : canDelayResults (fn ),
184
279
}
185
280
186
281
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 {
282
+ if isInlinable (n .Func ) {
283
+ 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 ))
284
+ } else {
285
+ 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 ))
286
+ }
287
+ } else if base .Flag .LowerM != 0 && isInlinable (n .Func ) {
189
288
fmt .Printf ("%v: can inline %v\n " , ir .Line (fn ), n )
190
289
}
191
290
if logopt .Enabled () {
192
- logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , inlineMaxBudget - visitor . budget ))
291
+ logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , n . Func . Inl . Cost ))
193
292
}
194
293
}
195
294
@@ -241,7 +340,7 @@ func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
241
340
return true
242
341
}
243
342
if v .budget < 0 {
244
- v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineMaxBudget - v .budget , inlineMaxBudget )
343
+ v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineVisitorBudget - v .budget , inlineVisitorBudget )
245
344
return true
246
345
}
247
346
return false
@@ -493,20 +592,13 @@ func inlcopy(n ir.Node) ir.Node {
493
592
func InlineCalls (fn * ir.Func ) {
494
593
savefn := ir .CurFunc
495
594
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 )
595
+
596
+ var inlCtx inlContext
597
+ inlCtx .Init (fn )
598
+
507
599
var edit func (ir.Node ) ir.Node
508
600
edit = func (n ir.Node ) ir.Node {
509
- return inlnode (n , maxCost , inlMap , edit )
601
+ return inlnode (n , & inlCtx , edit )
510
602
}
511
603
ir .EditChildren (fn , edit )
512
604
ir .CurFunc = savefn
@@ -525,11 +617,16 @@ func InlineCalls(fn *ir.Func) {
525
617
// shorter and less complicated.
526
618
// The result of inlnode MUST be assigned back to n, e.g.
527
619
// 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 {
620
+ func inlnode (n ir.Node , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
529
621
if n == nil {
530
622
return n
531
623
}
532
624
625
+ if n .Op () == ir .OFOR {
626
+ ctx .PushFor (n )
627
+ defer ctx .PopFor ()
628
+ }
629
+
533
630
switch n .Op () {
534
631
case ir .ODEFER , ir .OGO :
535
632
n := n .(* ir.GoDeferStmt )
@@ -584,7 +681,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
584
681
break
585
682
}
586
683
if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
587
- n = mkinlcall (call , fn , maxCost , inlMap , edit )
684
+ n = mkinlcall (call , fn , ctx , edit )
588
685
}
589
686
}
590
687
@@ -657,20 +754,20 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa
657
754
// parameters.
658
755
// The result of mkinlcall MUST be assigned back to n, e.g.
659
756
// 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 {
757
+ func mkinlcall (n * ir.CallExpr , fn * ir.Func , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
661
758
if fn .Inl == nil {
662
759
if logopt .Enabled () {
663
760
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
664
761
fmt .Sprintf ("%s cannot be inlined" , ir .PkgFuncName (fn )))
665
762
}
666
763
return n
667
764
}
668
- if fn .Inl .Cost > maxCost {
765
+ if fn .Inl .Cost > ctx . InlineBudget () {
669
766
// The inlined function body is too big. Typically we use this check to restrict
670
767
// inlining into very big functions. See issue 26546 and 17566.
671
768
if logopt .Enabled () {
672
769
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 ))
770
+ fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), ctx . InlineBudget () ))
674
771
}
675
772
return n
676
773
}
@@ -693,15 +790,15 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
693
790
return n
694
791
}
695
792
696
- if inlMap [fn ] {
793
+ if ctx . inlinedCallees [fn ] {
697
794
if base .Flag .LowerM > 1 {
698
795
fmt .Printf ("%v: cannot inline %v into %v: repeated recursive cycle\n " , ir .Line (n ), fn , ir .FuncName (ir .CurFunc ))
699
796
}
700
797
return n
701
798
}
702
- inlMap [fn ] = true
799
+ ctx . inlinedCallees [fn ] = true
703
800
defer func () {
704
- inlMap [fn ] = false
801
+ ctx . inlinedCallees [fn ] = false
705
802
}()
706
803
707
804
typecheck .FixVariadicCall (n )
0 commit comments