@@ -45,14 +45,103 @@ const (
45
45
inlineMaxBudget = 80
46
46
inlineExtraAppendCost = 0
47
47
// default is to inline if there's at most one call. -l=4 overrides this by using 1 instead.
48
- inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
48
+ inlineExtraCallCost = 57 // 57 was benchmarked to provide most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
49
49
inlineExtraPanicCost = 1 // do not penalize inlining panics.
50
50
inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help.
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
+ // These values were benchmarked to provide most benefit with no bad surprises.
56
+ inlineBigForCost = 105 // FORs with at least this cost are considered "big".
57
+ inlineIntoForExtraCallCost = 14
58
+ inlineIntoForExtraBudget = 18 // Extra budget when inlining into FORs which are not "big".
59
+
60
+ // The upper budget for a visitor. It accounts the maximum cost with which a function could be inlined.
61
+ inlineVisitorBudget = inlineMaxBudget + inlineIntoForExtraBudget
54
62
)
55
63
64
+ // isInlinable checks if the function can be inlined in a 'typical' scenario
65
+ // when no boosts are applied.
66
+ func isInlinable (fn * ir.Func ) bool {
67
+ return fn != nil && fn .Inl != nil && fn .Inl .Cost <= inlineMaxBudget
68
+ }
69
+
70
+ type forContext struct {
71
+ cost int32 // Cost helps to determine if FOR is a "big" one.
72
+ }
73
+
74
+ type inlContext struct {
75
+ // Map to keep track of functions that have been inlined at a particular
76
+ // call site, in order to stop inlining when we reach the beginning of a
77
+ // recursion cycle again. We don't inline immediately recursive functions,
78
+ // but allow inlining if there is a recursion cycle of many functions.
79
+ // Most likely, the inlining will stop before we even hit the beginning of
80
+ // the cycle again, but the map catches the unusual case.
81
+ inlinedCallees map [* ir.Func ]bool
82
+
83
+ // Stack to recognise which call nodes are located inside fors, while doing inlnode.
84
+ forsStack []forContext
85
+ initialInlineBudget int32 // Initial inline budget. Boosts are calculated related to this.
86
+ }
87
+
88
+ // Current decision is made on whether all FORs in current scope are not "big".
89
+ func (ctx inlContext ) canBoostInliningIntoFor () bool {
90
+ for i := 0 ; i < len (ctx .forsStack ); i ++ {
91
+ if ctx .forsStack [i ].cost >= inlineBigForCost {
92
+ return false
93
+ }
94
+ }
95
+ return len (ctx .forsStack ) > 0
96
+ }
97
+
98
+ func (ctx * inlContext ) Init (fn * ir.Func ) {
99
+ ctx .inlinedCallees = make (map [* ir.Func ]bool )
100
+
101
+ if isBigFunc (fn ) {
102
+ ctx .initialInlineBudget = inlineBigFunctionMaxCost
103
+ } else {
104
+ ctx .initialInlineBudget = inlineMaxBudget
105
+ }
106
+ }
107
+
108
+ func (ctx * inlContext ) PushFor (n ir.Node ) {
109
+ ctx .forsStack = append (ctx .forsStack , forContext {forCost (n )})
110
+
111
+ if base .Flag .LowerM > 1 {
112
+ fmt .Printf ("%v: add FOR to stack %v\n " , ir .Line (n ), ctx .forsStack )
113
+ }
114
+ }
115
+
116
+ func (ctx * inlContext ) PopFor () {
117
+ ctx .forsStack = ctx .forsStack [:len (ctx .forsStack )- 1 ]
118
+ }
119
+
120
+ func (ctx inlContext ) InlineBudget () int32 {
121
+ finalBudget := ctx .initialInlineBudget
122
+ if ctx .canBoostInliningIntoFor () && ctx .initialInlineBudget == inlineMaxBudget {
123
+ // Boosts only regular functions
124
+ finalBudget += inlineIntoForExtraBudget
125
+ }
126
+
127
+ return finalBudget
128
+ }
129
+
130
+ func forCost (n ir.Node ) int32 {
131
+ exceededCostReason := func (remainingBudget int32 ) string {
132
+ return fmt .Sprintf ("FOR is big: cost %d exceeds maximum cost %d" , inlineBigForCost - remainingBudget , inlineBigForCost )
133
+ }
134
+
135
+ visitor := hairyVisitor {
136
+ budget : inlineBigForCost ,
137
+ extraCallCost : inlineIntoForExtraCallCost ,
138
+ stopIfNodeNotInlinable : false , //Use doNode to calculate the final cost only.
139
+ exceededCostReasonCallback : exceededCostReason ,
140
+ }
141
+ visitor .tooHairy (n )
142
+ return inlineBigForCost - visitor .budget
143
+ }
144
+
56
145
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
57
146
func InlinePackage () {
58
147
ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
@@ -166,30 +255,40 @@ func CanInline(fn *ir.Func) {
166
255
// locals, and we use this map to produce a pruned Inline.Dcl
167
256
// list. See issue 25249 for more context.
168
257
258
+ exceededCostReason := func (remainingBudget int32 ) string {
259
+ return fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineVisitorBudget - remainingBudget , inlineVisitorBudget )
260
+ }
261
+
169
262
visitor := hairyVisitor {
170
- budget : inlineMaxBudget ,
171
- extraCallCost : cc ,
263
+ budget : inlineVisitorBudget ,
264
+ extraCallCost : cc ,
265
+ exceededCostReasonCallback : exceededCostReason ,
266
+ stopIfNodeNotInlinable : true , // No reason to continue, if node of the fn is not inlinable.
172
267
}
173
268
if visitor .tooHairy (fn ) {
174
269
reason = visitor .reason
175
270
return
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
@@ -228,20 +327,22 @@ func canDelayResults(fn *ir.Func) bool {
228
327
// hairyVisitor visits a function body to determine its inlining
229
328
// hairiness and whether or not it can be inlined.
230
329
type hairyVisitor struct {
231
- budget int32
232
- reason string
233
- extraCallCost int32
234
- usedLocals ir.NameSet
235
- do func (ir.Node ) bool
330
+ budget int32
331
+ extraCallCost int32
332
+ stopIfNodeNotInlinable bool
333
+ reason string
334
+ usedLocals ir.NameSet
335
+ do func (ir.Node ) bool
336
+ exceededCostReasonCallback func (remainingBudget int32 ) string
236
337
}
237
338
238
- func (v * hairyVisitor ) tooHairy (fn * ir.Func ) bool {
339
+ func (v * hairyVisitor ) tooHairy (n ir.Node ) bool {
239
340
v .do = v .doNode // cache closure
240
- if ir .DoChildren (fn , v .do ) {
341
+ if ir .DoChildren (n , v .do ) {
241
342
return true
242
343
}
243
344
if v .budget < 0 {
244
- v .reason = fmt . Sprintf ( "function too complex: cost %d exceeds budget %d" , inlineMaxBudget - v .budget , inlineMaxBudget )
345
+ v .reason = v . exceededCostReasonCallback ( v .budget )
245
346
return true
246
347
}
247
348
return false
@@ -265,7 +366,11 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
265
366
fn := name .Sym ().Name
266
367
if fn == "getcallerpc" || fn == "getcallersp" {
267
368
v .reason = "call to " + fn
268
- return true
369
+ if v .stopIfNodeNotInlinable {
370
+ return true
371
+ } else {
372
+ break
373
+ }
269
374
}
270
375
if fn == "throw" {
271
376
v .budget -= inlineExtraThrowCost
@@ -292,7 +397,7 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
292
397
break
293
398
}
294
399
295
- if fn := inlCallee (n .X ); fn != nil && fn . Inl != nil {
400
+ if fn := inlCallee (n .X ); isInlinable ( fn ) {
296
401
v .budget -= fn .Inl .Cost
297
402
break
298
403
}
@@ -322,12 +427,18 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
322
427
// recover matches the argument frame pointer to find
323
428
// the right panic value, so it needs an argument frame.
324
429
v .reason = "call to recover"
325
- return true
430
+ if v .stopIfNodeNotInlinable {
431
+ return true
432
+ }
326
433
327
434
case ir .OCLOSURE :
328
435
if base .Debug .InlFuncsWithClosures == 0 {
329
436
v .reason = "not inlining functions with closures"
330
- return true
437
+ if v .stopIfNodeNotInlinable {
438
+ return true
439
+ } else {
440
+ break
441
+ }
331
442
}
332
443
333
444
// TODO(danscales): Maybe make budget proportional to number of closure
@@ -338,7 +449,9 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
338
449
// do) to check for disallowed ops in the body and include the
339
450
// body in the budget.
340
451
if doList (n .(* ir.ClosureExpr ).Func .Body , v .do ) {
341
- return true
452
+ if v .stopIfNodeNotInlinable {
453
+ return true
454
+ }
342
455
}
343
456
344
457
case ir .ORANGE ,
@@ -348,7 +461,9 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
348
461
ir .ODCLTYPE , // can't print yet
349
462
ir .OTAILCALL :
350
463
v .reason = "unhandled op " + n .Op ().String ()
351
- return true
464
+ if v .stopIfNodeNotInlinable {
465
+ return true
466
+ }
352
467
353
468
case ir .OAPPEND :
354
469
v .budget -= inlineExtraAppendCost
@@ -377,21 +492,27 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
377
492
n := n .(* ir.ForStmt )
378
493
if n .Label != nil {
379
494
v .reason = "labeled control"
380
- return true
495
+ if v .stopIfNodeNotInlinable {
496
+ return true
497
+ }
381
498
}
382
499
case ir .OSWITCH :
383
500
n := n .(* ir.SwitchStmt )
384
501
if n .Label != nil {
385
502
v .reason = "labeled control"
386
- return true
503
+ if v .stopIfNodeNotInlinable {
504
+ return true
505
+ }
387
506
}
388
507
// case ir.ORANGE, ir.OSELECT in "unhandled" above
389
508
390
509
case ir .OBREAK , ir .OCONTINUE :
391
510
n := n .(* ir.BranchStmt )
392
511
if n .Label != nil {
393
- // Should have short-circuited due to labeled control error above.
394
- base .Fatalf ("unexpected labeled break/continue: %v" , n )
512
+ if v .stopIfNodeNotInlinable {
513
+ // Should have short-circuited due to labeled control error above.
514
+ base .Fatalf ("unexpected labeled break/continue: %v" , n )
515
+ }
395
516
}
396
517
397
518
case ir .OIF :
@@ -497,20 +618,13 @@ func inlcopy(n ir.Node) ir.Node {
497
618
func InlineCalls (fn * ir.Func ) {
498
619
savefn := ir .CurFunc
499
620
ir .CurFunc = fn
500
- maxCost := int32 (inlineMaxBudget )
501
- if isBigFunc (fn ) {
502
- maxCost = inlineBigFunctionMaxCost
503
- }
504
- // Map to keep track of functions that have been inlined at a particular
505
- // call site, in order to stop inlining when we reach the beginning of a
506
- // recursion cycle again. We don't inline immediately recursive functions,
507
- // but allow inlining if there is a recursion cycle of many functions.
508
- // Most likely, the inlining will stop before we even hit the beginning of
509
- // the cycle again, but the map catches the unusual case.
510
- inlMap := make (map [* ir.Func ]bool )
621
+
622
+ var inlCtx inlContext
623
+ inlCtx .Init (fn )
624
+
511
625
var edit func (ir.Node ) ir.Node
512
626
edit = func (n ir.Node ) ir.Node {
513
- return inlnode (n , maxCost , inlMap , edit )
627
+ return inlnode (n , & inlCtx , edit )
514
628
}
515
629
ir .EditChildren (fn , edit )
516
630
ir .CurFunc = savefn
@@ -529,11 +643,16 @@ func InlineCalls(fn *ir.Func) {
529
643
// shorter and less complicated.
530
644
// The result of inlnode MUST be assigned back to n, e.g.
531
645
// n.Left = inlnode(n.Left)
532
- func inlnode (n ir.Node , maxCost int32 , inlMap map [ * ir. Func ] bool , edit func (ir.Node ) ir.Node ) ir.Node {
646
+ func inlnode (n ir.Node , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
533
647
if n == nil {
534
648
return n
535
649
}
536
650
651
+ if n .Op () == ir .OFOR {
652
+ ctx .PushFor (n )
653
+ defer ctx .PopFor ()
654
+ }
655
+
537
656
switch n .Op () {
538
657
case ir .ODEFER , ir .OGO :
539
658
n := n .(* ir.GoDeferStmt )
@@ -591,7 +710,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
591
710
break
592
711
}
593
712
if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
594
- n = mkinlcall (call , fn , maxCost , inlMap , edit )
713
+ n = mkinlcall (call , fn , ctx , edit )
595
714
}
596
715
}
597
716
@@ -664,20 +783,20 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa
664
783
// parameters.
665
784
// The result of mkinlcall MUST be assigned back to n, e.g.
666
785
// n.Left = mkinlcall(n.Left, fn, isddd)
667
- func mkinlcall (n * ir.CallExpr , fn * ir.Func , maxCost int32 , inlMap map [ * ir. Func ] bool , edit func (ir.Node ) ir.Node ) ir.Node {
786
+ func mkinlcall (n * ir.CallExpr , fn * ir.Func , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
668
787
if fn .Inl == nil {
669
788
if logopt .Enabled () {
670
789
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
671
790
fmt .Sprintf ("%s cannot be inlined" , ir .PkgFuncName (fn )))
672
791
}
673
792
return n
674
793
}
675
- if fn .Inl .Cost > maxCost {
794
+ if fn .Inl .Cost > ctx . InlineBudget () {
676
795
// The inlined function body is too big. Typically we use this check to restrict
677
796
// inlining into very big functions. See issue 26546 and 17566.
678
797
if logopt .Enabled () {
679
798
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
680
- fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), maxCost ))
799
+ fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), ctx . InlineBudget () ))
681
800
}
682
801
return n
683
802
}
@@ -700,15 +819,15 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
700
819
return n
701
820
}
702
821
703
- if inlMap [fn ] {
822
+ if ctx . inlinedCallees [fn ] {
704
823
if base .Flag .LowerM > 1 {
705
824
fmt .Printf ("%v: cannot inline %v into %v: repeated recursive cycle\n " , ir .Line (n ), fn , ir .FuncName (ir .CurFunc ))
706
825
}
707
826
return n
708
827
}
709
- inlMap [fn ] = true
828
+ ctx . inlinedCallees [fn ] = true
710
829
defer func () {
711
- inlMap [fn ] = false
830
+ ctx . inlinedCallees [fn ] = false
712
831
}()
713
832
714
833
typecheck .FixVariadicCall (n )
0 commit comments