@@ -51,8 +51,102 @@ 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
+ inlineBigForNodes = 50 // FORs with this many nodes are considered "big" and functions are not forced to be inlined.
56
+ inlineBigForCallNodes = 5 // FORs with this many call nodes are considered "big" and functions are not forced to be inlined.
57
+ inlineExtraForBudget = 20 // Extra budget to inline into not a "big" FOR.
58
+
59
+ // The upper budget for a visitor. It accounts the maximum cost with which a function could be inlined.
60
+ inlineVisitorBudget = inlineMaxBudget + inlineExtraForBudget
54
61
)
55
62
63
+ type forContext struct {
64
+ liveCounter int
65
+ totalNodes int
66
+ callNodes int
67
+ }
68
+
69
+ type inlContext struct {
70
+ inlMap map [* ir.Func ]bool
71
+ forsStack []forContext
72
+ }
73
+
74
+ // isinlinable checks if the function can be inlined in a 'typical' scenario
75
+ // when no boosts are applied.
76
+ func isinlinable (fn * ir.Func ) bool {
77
+ return fn != nil && fn .Inl != nil && fn .Inl .Cost <= inlineMaxBudget
78
+ }
79
+
80
+ // countNodes returns count of child nodes and inlinable child call nodes.
81
+ func countInlinableCallNodes (n ir.Node ) (int , int ) {
82
+ child_nodes := 0
83
+ child_inlinable_call_nodes := 0
84
+ ir .Any (n , func (n ir.Node ) bool {
85
+ child_nodes ++
86
+ switch n .Op () {
87
+ case ir .OCALLFUNC :
88
+ call := n .(* ir.CallExpr )
89
+ if call .NoInline {
90
+ break
91
+ }
92
+ if ir .IsIntrinsicCall (call ) {
93
+ break
94
+ }
95
+ if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
96
+ child_inlinable_call_nodes ++
97
+ }
98
+ }
99
+ return false
100
+ })
101
+ return child_nodes , child_inlinable_call_nodes
102
+ }
103
+
104
+ // updateForsStack maintains forsStack, which is used to recognise
105
+ // which call nodes are located inside fors, while doing inlnode.
106
+ func updateForsStack (inlCtx * inlContext , n ir.Node ) {
107
+ outdated := 0
108
+ for i := len (inlCtx .forsStack ) - 1 ; i >= 0 ; i -- {
109
+ inlCtx .forsStack [i ].liveCounter --
110
+ if inlCtx .forsStack [i ].liveCounter < 0 {
111
+ outdated ++
112
+ }
113
+ }
114
+ inlCtx .forsStack = inlCtx .forsStack [:len (inlCtx .forsStack )- outdated ]
115
+
116
+ // If we are in a "big" FOR, it's useless to calculate node count
117
+ // for this FOR, since no function will be inlined.
118
+ if n .Op () == ir .OFOR && (len (inlCtx .forsStack ) == 0 || ancestorForsAreSmall (inlCtx )) {
119
+ child_nodes , child_inlinable_call_nodes := countInlinableCallNodes (n )
120
+ inlCtx .forsStack = append (inlCtx .forsStack , forContext {child_nodes - 1 , child_nodes - 1 , child_inlinable_call_nodes })
121
+
122
+ if base .Flag .LowerM > 1 {
123
+ fmt .Printf ("%v: add for to stack %v\n " , ir .Line (n ), inlCtx .forsStack )
124
+ }
125
+ }
126
+ }
127
+
128
+ // fixupForStackAfterInline fixes forsStack after a call node was replaced with inlined node.
129
+ func fixupForStackAfterInline (inlCtx * inlContext , n ir.Node , call * ir.InlinedCallExpr ) {
130
+ if len (inlCtx .forsStack ) == 0 {
131
+ return
132
+ }
133
+
134
+ child_nodes , child_inlinable_call_nodes := countInlinableCallNodes (call )
135
+
136
+ for i := 0 ; i < len (inlCtx .forsStack ); i ++ {
137
+ inlCtx .forsStack [i ].liveCounter += child_nodes - 1
138
+ inlCtx .forsStack [i ].callNodes += child_inlinable_call_nodes
139
+ }
140
+
141
+ if base .Flag .LowerM > 1 {
142
+ fmt .Printf ("%v: fixup inline %v\n " , ir .Line (n ), inlCtx .forsStack )
143
+ }
144
+ }
145
+
146
+ func ancestorForsAreSmall (inlCtx * inlContext ) bool {
147
+ return len (inlCtx .forsStack ) > 0 && inlCtx .forsStack [0 ].totalNodes < inlineBigForNodes && inlCtx .forsStack [0 ].callNodes < inlineBigForCallNodes
148
+ }
149
+
56
150
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
57
151
func InlinePackage () {
58
152
ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
@@ -167,7 +261,7 @@ func CanInline(fn *ir.Func) {
167
261
// list. See issue 25249 for more context.
168
262
169
263
visitor := hairyVisitor {
170
- budget : inlineMaxBudget ,
264
+ budget : inlineVisitorBudget ,
171
265
extraCallCost : cc ,
172
266
}
173
267
if visitor .tooHairy (fn ) {
@@ -176,20 +270,24 @@ func CanInline(fn *ir.Func) {
176
270
}
177
271
178
272
n .Func .Inl = & ir.Inline {
179
- Cost : inlineMaxBudget - visitor .budget ,
273
+ Cost : inlineVisitorBudget - visitor .budget ,
180
274
Dcl : pruneUnusedAutos (n .Defn .(* ir.Func ).Dcl , & visitor ),
181
275
Body : inlcopylist (fn .Body ),
182
276
183
277
CanDelayResults : canDelayResults (fn ),
184
278
}
185
279
186
280
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 {
281
+ if isinlinable (n .Func ) {
282
+ 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 ))
283
+ } else {
284
+ 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 ))
285
+ }
286
+ } else if base .Flag .LowerM != 0 && isinlinable (n .Func ) {
189
287
fmt .Printf ("%v: can inline %v\n " , ir .Line (fn ), n )
190
288
}
191
289
if logopt .Enabled () {
192
- logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , inlineMaxBudget - visitor . budget ))
290
+ logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , n . Func . Inl . Cost ))
193
291
}
194
292
}
195
293
@@ -241,7 +339,7 @@ func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
241
339
return true
242
340
}
243
341
if v .budget < 0 {
244
- v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineMaxBudget - v .budget , inlineMaxBudget )
342
+ v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineVisitorBudget - v .budget , inlineVisitorBudget )
245
343
return true
246
344
}
247
345
return false
@@ -503,10 +601,11 @@ func InlineCalls(fn *ir.Func) {
503
601
// but allow inlining if there is a recursion cycle of many functions.
504
602
// Most likely, the inlining will stop before we even hit the beginning of
505
603
// the cycle again, but the map catches the unusual case.
506
- inlMap := make (map [* ir.Func ]bool )
604
+ inlCtx := inlContext {make (map [* ir.Func ]bool ), make ([]forContext , 0 )}
605
+
507
606
var edit func (ir.Node ) ir.Node
508
607
edit = func (n ir.Node ) ir.Node {
509
- return inlnode (n , maxCost , inlMap , edit )
608
+ return inlnode (n , maxCost , & inlCtx , edit )
510
609
}
511
610
ir .EditChildren (fn , edit )
512
611
ir .CurFunc = savefn
@@ -525,11 +624,16 @@ func InlineCalls(fn *ir.Func) {
525
624
// shorter and less complicated.
526
625
// The result of inlnode MUST be assigned back to n, e.g.
527
626
// 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 {
627
+ func inlnode (n ir.Node , maxCost int32 , inlCtx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
529
628
if n == nil {
530
629
return n
531
630
}
532
631
632
+ if updateForsStack (inlCtx , n ); ancestorForsAreSmall (inlCtx ) && maxCost == inlineMaxBudget {
633
+ // Boosts only regular functions
634
+ maxCost += inlineExtraForBudget
635
+ }
636
+
533
637
switch n .Op () {
534
638
case ir .ODEFER , ir .OGO :
535
639
n := n .(* ir.GoDeferStmt )
@@ -584,7 +688,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
584
688
break
585
689
}
586
690
if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
587
- n = mkinlcall (call , fn , maxCost , inlMap , edit )
691
+ n = mkinlcall (call , fn , maxCost , inlCtx , edit )
588
692
}
589
693
}
590
694
@@ -657,7 +761,7 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa
657
761
// parameters.
658
762
// The result of mkinlcall MUST be assigned back to n, e.g.
659
763
// 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 {
764
+ func mkinlcall (n * ir.CallExpr , fn * ir.Func , maxCost int32 , inlCtx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
661
765
if fn .Inl == nil {
662
766
if logopt .Enabled () {
663
767
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
@@ -693,15 +797,15 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
693
797
return n
694
798
}
695
799
696
- if inlMap [fn ] {
800
+ if inlCtx . inlMap [fn ] {
697
801
if base .Flag .LowerM > 1 {
698
802
fmt .Printf ("%v: cannot inline %v into %v: repeated recursive cycle\n " , ir .Line (n ), fn , ir .FuncName (ir .CurFunc ))
699
803
}
700
804
return n
701
805
}
702
- inlMap [fn ] = true
806
+ inlCtx . inlMap [fn ] = true
703
807
defer func () {
704
- inlMap [fn ] = false
808
+ inlCtx . inlMap [fn ] = false
705
809
}()
706
810
707
811
typecheck .FixVariadicCall (n )
@@ -730,6 +834,8 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
730
834
res = oldInline (n , fn , inlIndex )
731
835
}
732
836
837
+ fixupForStackAfterInline (inlCtx , n , res )
838
+
733
839
// transitive inlining
734
840
// might be nice to do this before exporting the body,
735
841
// but can't emit the body with inlining expanded.
0 commit comments