@@ -53,8 +53,9 @@ const (
53
53
inlineExtraPanicCost = 1 // do not penalize inlining panics.
54
54
inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help.
55
55
56
- inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big".
57
- inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
56
+ inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big".
57
+ inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
58
+ inlineClosureCalledOnceCost = 10 * inlineMaxBudget // if a closure is just called once, inline it.
58
59
)
59
60
60
61
var (
@@ -207,7 +208,8 @@ func inlineBudget(fn *ir.Func, profile *pgoir.Profile, relaxed bool, verbose boo
207
208
budget += inlheur .BudgetExpansion (inlineMaxBudget )
208
209
}
209
210
if fn .ClosureParent != nil {
210
- budget *= 2
211
+ // be very liberal here, if the closure is only called once, the budget is large
212
+ budget = max (budget , inlineClosureCalledOnceCost )
211
213
}
212
214
return budget
213
215
}
@@ -561,11 +563,11 @@ opSwitch:
561
563
break
562
564
}
563
565
564
- if callee := inlCallee (v .curFunc , n .Fun , v .profile ); callee != nil && typecheck .HaveInlineBody (callee ) {
566
+ if callee := inlCallee (v .curFunc , n .Fun , v .profile , false ); callee != nil && typecheck .HaveInlineBody (callee ) {
565
567
// Check whether we'd actually inline this call. Set
566
568
// log == false since we aren't actually doing inlining
567
569
// yet.
568
- if ok , _ , _ := canInlineCallExpr (v .curFunc , n , callee , v .isBigFunc , false ); ok {
570
+ if ok , _ , _ := canInlineCallExpr (v .curFunc , n , callee , v .isBigFunc , false , false ); ok {
569
571
// mkinlcall would inline this call [1], so use
570
572
// the cost of the inline body as the cost of
571
573
// the call, as that is what will actually
@@ -577,6 +579,9 @@ opSwitch:
577
579
// by looking at what has already been inlined.
578
580
// Since we haven't done any inlining yet we
579
581
// will miss those.
582
+ //
583
+ // TODO: in the case of a single-call closure, the inlining budget here is potentially much, much larger.
584
+ //
580
585
v .budget -= callee .Inl .Cost
581
586
break
582
587
}
@@ -774,17 +779,18 @@ func IsBigFunc(fn *ir.Func) bool {
774
779
})
775
780
}
776
781
777
- // TryInlineCall returns an inlined call expression for call, or nil
778
- // if inlining is not possible.
779
- func TryInlineCall (callerfn * ir.Func , call * ir.CallExpr , bigCaller bool , profile * pgoir.Profile ) * ir.InlinedCallExpr {
782
+ // inlineCallCheck returns whether a call will never be inlineable
783
+ // for basic reasons, and whether the call is an intrinisic call.
784
+ // The intrinsic result singles out intrinsic calls for debug logging.
785
+ func inlineCallCheck (callerfn * ir.Func , call * ir.CallExpr ) (bool , bool ) {
780
786
if base .Flag .LowerL == 0 {
781
- return nil
787
+ return false , false
782
788
}
783
789
if call .Op () != ir .OCALLFUNC {
784
- return nil
790
+ return false , false
785
791
}
786
792
if call .GoDefer || call .NoInline {
787
- return nil
793
+ return false , false
788
794
}
789
795
790
796
// Prevent inlining some reflect.Value methods when using checkptr,
@@ -793,26 +799,49 @@ func TryInlineCall(callerfn *ir.Func, call *ir.CallExpr, bigCaller bool, profile
793
799
if method := ir .MethodExprName (call .Fun ); method != nil {
794
800
switch types .ReflectSymName (method .Sym ()) {
795
801
case "Value.UnsafeAddr" , "Value.Pointer" :
796
- return nil
802
+ return false , false
797
803
}
798
804
}
799
805
}
806
+ if ir .IsIntrinsicCall (call ) {
807
+ return false , true
808
+ }
809
+ return true , false
810
+ }
811
+
812
+ // InlineCallTarget returns the resolved-for-inlining target of a call.
813
+ // It does not necessarily guarantee that the target can be inlined, though
814
+ // obvious exclusions are applied.
815
+ func InlineCallTarget (callerfn * ir.Func , call * ir.CallExpr , profile * pgoir.Profile ) * ir.Func {
816
+ if mightInline , _ := inlineCallCheck (callerfn , call ); ! mightInline {
817
+ return nil
818
+ }
819
+ return inlCallee (callerfn , call .Fun , profile , true )
820
+ }
821
+
822
+ // TryInlineCall returns an inlined call expression for call, or nil
823
+ // if inlining is not possible.
824
+ func TryInlineCall (callerfn * ir.Func , call * ir.CallExpr , bigCaller bool , profile * pgoir.Profile , closureCalledOnce bool ) * ir.InlinedCallExpr {
825
+ mightInline , isIntrinsic := inlineCallCheck (callerfn , call )
800
826
801
- if base .Flag .LowerM > 3 {
827
+ // Preserve old logging behavior
828
+ if (mightInline || isIntrinsic ) && base .Flag .LowerM > 3 {
802
829
fmt .Printf ("%v:call to func %+v\n " , ir .Line (call ), call .Fun )
803
830
}
804
- if ir . IsIntrinsicCall ( call ) {
831
+ if ! mightInline {
805
832
return nil
806
833
}
807
- if fn := inlCallee (callerfn , call .Fun , profile ); fn != nil && typecheck .HaveInlineBody (fn ) {
808
- return mkinlcall (callerfn , call , fn , bigCaller )
834
+
835
+ if fn := inlCallee (callerfn , call .Fun , profile , false ); fn != nil && typecheck .HaveInlineBody (fn ) {
836
+ return mkinlcall (callerfn , call , fn , bigCaller , closureCalledOnce )
809
837
}
810
838
return nil
811
839
}
812
840
813
841
// inlCallee takes a function-typed expression and returns the underlying function ONAME
814
842
// that it refers to if statically known. Otherwise, it returns nil.
815
- func inlCallee (caller * ir.Func , fn ir.Node , profile * pgoir.Profile ) (res * ir.Func ) {
843
+ // resolveOnly skips cost-based inlineability checks for closures; the result may not actually be inlineable.
844
+ func inlCallee (caller * ir.Func , fn ir.Node , profile * pgoir.Profile , resolveOnly bool ) (res * ir.Func ) {
816
845
fn = ir .StaticValue (fn )
817
846
switch fn .Op () {
818
847
case ir .OMETHEXPR :
@@ -836,7 +865,9 @@ func inlCallee(caller *ir.Func, fn ir.Node, profile *pgoir.Profile) (res *ir.Fun
836
865
if len (c .ClosureVars ) != 0 && c .ClosureVars [0 ].Outer .Curfn != caller {
837
866
return nil // inliner doesn't support inlining across closure frames
838
867
}
839
- CanInline (c , profile )
868
+ if ! resolveOnly {
869
+ CanInline (c , profile )
870
+ }
840
871
return c
841
872
}
842
873
return nil
@@ -862,18 +893,22 @@ var InlineCall = func(callerfn *ir.Func, call *ir.CallExpr, fn *ir.Func, inlInde
862
893
// - the "max cost" limit used to make the decision (which may differ depending on func size)
863
894
// - the score assigned to this specific callsite
864
895
// - whether the inlined function is "hot" according to PGO.
865
- func inlineCostOK (n * ir.CallExpr , caller , callee * ir.Func , bigCaller bool ) (bool , int32 , int32 , bool ) {
896
+ func inlineCostOK (n * ir.CallExpr , caller , callee * ir.Func , bigCaller , closureCalledOnce bool ) (bool , int32 , int32 , bool ) {
866
897
maxCost := int32 (inlineMaxBudget )
867
- if callee .ClosureParent != nil {
868
- maxCost *= 2 // favor inlining closures
869
- }
870
898
871
899
if bigCaller {
872
900
// We use this to restrict inlining into very big functions.
873
901
// See issue 26546 and 17566.
874
902
maxCost = inlineBigFunctionMaxCost
875
903
}
876
904
905
+ if callee .ClosureParent != nil {
906
+ maxCost *= 2 // favor inlining closures
907
+ if closureCalledOnce { // really favor inlining the one call to this closure
908
+ maxCost = max (maxCost , inlineClosureCalledOnceCost )
909
+ }
910
+ }
911
+
877
912
metric := callee .Inl .Cost
878
913
if inlheur .Enabled () {
879
914
score , ok := inlheur .GetCallSiteScore (caller , n )
@@ -931,7 +966,7 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool
931
966
// indicates that the 'cannot inline' reason should be logged.
932
967
//
933
968
// Preconditions: CanInline(callee) has already been called.
934
- func canInlineCallExpr (callerfn * ir.Func , n * ir.CallExpr , callee * ir.Func , bigCaller bool , log bool ) (bool , int32 , bool ) {
969
+ func canInlineCallExpr (callerfn * ir.Func , n * ir.CallExpr , callee * ir.Func , bigCaller , closureCalledOnce bool , log bool ) (bool , int32 , bool ) {
935
970
if callee .Inl == nil {
936
971
// callee is never inlinable.
937
972
if log && logopt .Enabled () {
@@ -941,7 +976,7 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
941
976
return false , 0 , false
942
977
}
943
978
944
- ok , maxCost , callSiteScore , hot := inlineCostOK (n , callerfn , callee , bigCaller )
979
+ ok , maxCost , callSiteScore , hot := inlineCostOK (n , callerfn , callee , bigCaller , closureCalledOnce )
945
980
if ! ok {
946
981
// callee cost too high for this call site.
947
982
if log && logopt .Enabled () {
@@ -1024,8 +1059,8 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
1024
1059
// The result of mkinlcall MUST be assigned back to n, e.g.
1025
1060
//
1026
1061
// n.Left = mkinlcall(n.Left, fn, isddd)
1027
- func mkinlcall (callerfn * ir.Func , n * ir.CallExpr , fn * ir.Func , bigCaller bool ) * ir.InlinedCallExpr {
1028
- ok , score , hot := canInlineCallExpr (callerfn , n , fn , bigCaller , true )
1062
+ func mkinlcall (callerfn * ir.Func , n * ir.CallExpr , fn * ir.Func , bigCaller , closureCalledOnce bool ) * ir.InlinedCallExpr {
1063
+ ok , score , hot := canInlineCallExpr (callerfn , n , fn , bigCaller , closureCalledOnce , true )
1029
1064
if ! ok {
1030
1065
return nil
1031
1066
}
0 commit comments