@@ -45,14 +45,107 @@ 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 // 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
+ // forCost calculates the cost of FORs. It is used to determine if functions
131
+ // will be boosted to inline into the FOR.
132
+ // We don't want to boost inlining into "big" FORs to keep their body
133
+ // in the instruction cache.
134
+ func forCost (n ir.Node ) int32 {
135
+ exceededCostReason := func (remainingBudget int32 ) string {
136
+ return fmt .Sprintf ("FOR is big: cost %d exceeds maximum cost %d" , inlineBigForCost - remainingBudget , inlineBigForCost )
137
+ }
138
+
139
+ visitor := hairyVisitor {
140
+ budget : inlineBigForCost ,
141
+ extraCallCost : inlineIntoForExtraCallCost ,
142
+ onlyCost : true ,
143
+ exceededCostReasonCallback : exceededCostReason ,
144
+ }
145
+ visitor .tooHairy (n )
146
+ return inlineBigForCost - visitor .budget
147
+ }
148
+
56
149
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
57
150
func InlinePackage () {
58
151
ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
@@ -166,30 +259,40 @@ func CanInline(fn *ir.Func) {
166
259
// locals, and we use this map to produce a pruned Inline.Dcl
167
260
// list. See issue 25249 for more context.
168
261
262
+ exceededCostReason := func (remainingBudget int32 ) string {
263
+ return fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineVisitorBudget - remainingBudget , inlineVisitorBudget )
264
+ }
265
+
169
266
visitor := hairyVisitor {
170
- budget : inlineMaxBudget ,
171
- extraCallCost : cc ,
267
+ budget : inlineVisitorBudget ,
268
+ extraCallCost : cc ,
269
+ onlyCost : false ,
270
+ exceededCostReasonCallback : exceededCostReason ,
172
271
}
173
272
if visitor .tooHairy (fn ) {
174
273
reason = visitor .reason
175
274
return
176
275
}
177
276
178
277
n .Func .Inl = & ir.Inline {
179
- Cost : inlineMaxBudget - visitor .budget ,
278
+ Cost : inlineVisitorBudget - visitor .budget ,
180
279
Dcl : pruneUnusedAutos (n .Defn .(* ir.Func ).Dcl , & visitor ),
181
280
Body : inlcopylist (fn .Body ),
182
281
183
282
CanDelayResults : canDelayResults (fn ),
184
283
}
185
284
186
285
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 {
286
+ if isInlinable (n .Func ) {
287
+ 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 ))
288
+ } else {
289
+ 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 ))
290
+ }
291
+ } else if base .Flag .LowerM != 0 && isInlinable (n .Func ) {
189
292
fmt .Printf ("%v: can inline %v\n " , ir .Line (fn ), n )
190
293
}
191
294
if logopt .Enabled () {
192
- logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , inlineMaxBudget - visitor . budget ))
295
+ logopt .LogOpt (fn .Pos (), "canInlineFunction" , "inline" , ir .FuncName (fn ), fmt .Sprintf ("cost: %d" , n . Func . Inl . Cost ))
193
296
}
194
297
}
195
298
@@ -228,20 +331,22 @@ func canDelayResults(fn *ir.Func) bool {
228
331
// hairyVisitor visits a function body to determine its inlining
229
332
// hairiness and whether or not it can be inlined.
230
333
type hairyVisitor struct {
231
- budget int32
232
- reason string
233
- extraCallCost int32
234
- usedLocals ir.NameSet
235
- do func (ir.Node ) bool
334
+ budget int32
335
+ extraCallCost int32
336
+ onlyCost bool // If set, tooHairy does NOT check inlinible nodes, only cost.
337
+ reason string
338
+ usedLocals ir.NameSet
339
+ do func (ir.Node ) bool
340
+ exceededCostReasonCallback func (remainingBudget int32 ) string
236
341
}
237
342
238
- func (v * hairyVisitor ) tooHairy (fn * ir.Func ) bool {
343
+ func (v * hairyVisitor ) tooHairy (n ir.Node ) bool {
239
344
v .do = v .doNode // cache closure
240
- if ir .DoChildren (fn , v .do ) {
345
+ if ir .DoChildren (n , v .do ) {
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 = v . exceededCostReasonCallback ( v .budget )
245
350
return true
246
351
}
247
352
return false
@@ -264,8 +369,12 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
264
369
if name .Class == ir .PFUNC && types .IsRuntimePkg (name .Sym ().Pkg ) {
265
370
fn := name .Sym ().Name
266
371
if fn == "getcallerpc" || fn == "getcallersp" {
267
- v .reason = "call to " + fn
268
- return true
372
+ if ! v .onlyCost {
373
+ v .reason = "call to " + fn
374
+ return true
375
+ } else {
376
+ break
377
+ }
269
378
}
270
379
if fn == "throw" {
271
380
v .budget -= inlineExtraThrowCost
@@ -292,7 +401,7 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
292
401
break
293
402
}
294
403
295
- if fn := inlCallee (n .X ); fn != nil && fn . Inl != nil {
404
+ if fn := inlCallee (n .X ); isInlinable ( fn ) {
296
405
v .budget -= fn .Inl .Cost
297
406
break
298
407
}
@@ -321,13 +430,19 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
321
430
case ir .ORECOVER :
322
431
// recover matches the argument frame pointer to find
323
432
// the right panic value, so it needs an argument frame.
324
- v .reason = "call to recover"
325
- return true
433
+ if ! v .onlyCost {
434
+ v .reason = "call to recover"
435
+ return true
436
+ }
326
437
327
438
case ir .OCLOSURE :
328
439
if base .Debug .InlFuncsWithClosures == 0 {
329
- v .reason = "not inlining functions with closures"
330
- return true
440
+ if ! v .onlyCost {
441
+ v .reason = "not inlining functions with closures"
442
+ return true
443
+ } else {
444
+ break
445
+ }
331
446
}
332
447
333
448
// TODO(danscales): Maybe make budget proportional to number of closure
@@ -338,7 +453,9 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
338
453
// do) to check for disallowed ops in the body and include the
339
454
// body in the budget.
340
455
if doList (n .(* ir.ClosureExpr ).Func .Body , v .do ) {
341
- return true
456
+ if ! v .onlyCost {
457
+ return true
458
+ }
342
459
}
343
460
344
461
case ir .ORANGE ,
@@ -347,8 +464,10 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
347
464
ir .ODEFER ,
348
465
ir .ODCLTYPE , // can't print yet
349
466
ir .OTAILCALL :
350
- v .reason = "unhandled op " + n .Op ().String ()
351
- return true
467
+ if ! v .onlyCost {
468
+ v .reason = "unhandled op " + n .Op ().String ()
469
+ return true
470
+ }
352
471
353
472
case ir .OAPPEND :
354
473
v .budget -= inlineExtraAppendCost
@@ -376,22 +495,28 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
376
495
case ir .OFOR , ir .OFORUNTIL :
377
496
n := n .(* ir.ForStmt )
378
497
if n .Label != nil {
379
- v .reason = "labeled control"
380
- return true
498
+ if ! v .onlyCost {
499
+ v .reason = "labeled control"
500
+ return true
501
+ }
381
502
}
382
503
case ir .OSWITCH :
383
504
n := n .(* ir.SwitchStmt )
384
505
if n .Label != nil {
385
- v .reason = "labeled control"
386
- return true
506
+ if ! v .onlyCost {
507
+ v .reason = "labeled control"
508
+ return true
509
+ }
387
510
}
388
511
// case ir.ORANGE, ir.OSELECT in "unhandled" above
389
512
390
513
case ir .OBREAK , ir .OCONTINUE :
391
514
n := n .(* ir.BranchStmt )
392
515
if n .Label != nil {
393
- // Should have short-circuited due to labeled control error above.
394
- base .Fatalf ("unexpected labeled break/continue: %v" , n )
516
+ if ! v .onlyCost {
517
+ // Should have short-circuited due to labeled control error above.
518
+ base .Fatalf ("unexpected labeled break/continue: %v" , n )
519
+ }
395
520
}
396
521
397
522
case ir .OIF :
@@ -497,20 +622,13 @@ func inlcopy(n ir.Node) ir.Node {
497
622
func InlineCalls (fn * ir.Func ) {
498
623
savefn := ir .CurFunc
499
624
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 )
625
+
626
+ var inlCtx inlContext
627
+ inlCtx .Init (fn )
628
+
511
629
var edit func (ir.Node ) ir.Node
512
630
edit = func (n ir.Node ) ir.Node {
513
- return inlnode (n , maxCost , inlMap , edit )
631
+ return inlnode (n , & inlCtx , edit )
514
632
}
515
633
ir .EditChildren (fn , edit )
516
634
ir .CurFunc = savefn
@@ -529,11 +647,16 @@ func InlineCalls(fn *ir.Func) {
529
647
// shorter and less complicated.
530
648
// The result of inlnode MUST be assigned back to n, e.g.
531
649
// 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 {
650
+ func inlnode (n ir.Node , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
533
651
if n == nil {
534
652
return n
535
653
}
536
654
655
+ if n .Op () == ir .OFOR {
656
+ ctx .PushFor (n )
657
+ defer ctx .PopFor ()
658
+ }
659
+
537
660
switch n .Op () {
538
661
case ir .ODEFER , ir .OGO :
539
662
n := n .(* ir.GoDeferStmt )
@@ -591,7 +714,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
591
714
break
592
715
}
593
716
if fn := inlCallee (call .X ); fn != nil && fn .Inl != nil {
594
- n = mkinlcall (call , fn , maxCost , inlMap , edit )
717
+ n = mkinlcall (call , fn , ctx , edit )
595
718
}
596
719
}
597
720
@@ -664,20 +787,20 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa
664
787
// parameters.
665
788
// The result of mkinlcall MUST be assigned back to n, e.g.
666
789
// 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 {
790
+ func mkinlcall (n * ir.CallExpr , fn * ir.Func , ctx * inlContext , edit func (ir.Node ) ir.Node ) ir.Node {
668
791
if fn .Inl == nil {
669
792
if logopt .Enabled () {
670
793
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
671
794
fmt .Sprintf ("%s cannot be inlined" , ir .PkgFuncName (fn )))
672
795
}
673
796
return n
674
797
}
675
- if fn .Inl .Cost > maxCost {
798
+ if fn .Inl .Cost > ctx . InlineBudget () {
676
799
// The inlined function body is too big. Typically we use this check to restrict
677
800
// inlining into very big functions. See issue 26546 and 17566.
678
801
if logopt .Enabled () {
679
802
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 ))
803
+ fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), ctx . InlineBudget () ))
681
804
}
682
805
return n
683
806
}
@@ -700,15 +823,15 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
700
823
return n
701
824
}
702
825
703
- if inlMap [fn ] {
826
+ if ctx . inlinedCallees [fn ] {
704
827
if base .Flag .LowerM > 1 {
705
828
fmt .Printf ("%v: cannot inline %v into %v: repeated recursive cycle\n " , ir .Line (n ), fn , ir .FuncName (ir .CurFunc ))
706
829
}
707
830
return n
708
831
}
709
- inlMap [fn ] = true
832
+ ctx . inlinedCallees [fn ] = true
710
833
defer func () {
711
- inlMap [fn ] = false
834
+ ctx . inlinedCallees [fn ] = false
712
835
}()
713
836
714
837
typecheck .FixVariadicCall (n )
0 commit comments