]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/compile/inline/inleur: use "largest possible score" to revise inlinability
authorThan McIntosh <thanm@google.com>
Fri, 15 Sep 2023 19:48:49 +0000 (15:48 -0400)
committerThan McIntosh <thanm@google.com>
Wed, 8 Nov 2023 15:03:14 +0000 (15:03 +0000)
The current GOEXPERIMENT=newinliner strategy us to run "CanInline" for
a given function F with an expanded/relaxed budget of 160 (instead of
the default 80), and then only inline a call to F if the adjustments
we made to F's original score are below 80.

This way of doing things winds up writing out many more functions to
export data that have size between 80 and 160, on the theory that they
might be inlinable somewhere given the right context, which is
expensive from a compile time perspective.

This patch changes things to add a pass that revises the inlinability
of a function after its properties are computed by looking at its
properties and estimating the largest possible negative score
adjustment that could happen given the various return and param props.
If the computed score for the function minus the max adjust is not
less than 80, then we demote it from inlinable to non-inlinable to
save compile time.

Change-Id: Iedaac520d47f632be4fff3bd15d30112b46ec573
Reviewed-on: https://go-review.googlesource.com/c/go/+/529118
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/cmd/compile/internal/inline/inl.go
src/cmd/compile/internal/inline/inlheur/analyze.go
src/cmd/compile/internal/inline/inlheur/scoring.go

index f3ad19d241232ee7923918c4ccd6e724e80b39e2..6283c4c3a4de7d9b08f7c2fe4fbf688db8788d07 100644 (file)
@@ -285,6 +285,10 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
        if goexperiment.NewInliner || inlheur.UnitTesting() {
                callCanInline := func(fn *ir.Func) { CanInline(fn, profile) }
                funcProps = inlheur.AnalyzeFunc(fn, callCanInline, inlineMaxBudget)
+               budgetForFunc := func(fn *ir.Func) int32 {
+                       return inlineBudget(fn, profile, true, false)
+               }
+               defer func() { inlheur.RevisitInlinability(fn, budgetForFunc) }()
        }
 
        var reason string // reason, if any, that the function was not inlined
index 9af7e1207d9f1b8a3089529d61b194b869ad247b..3ef750bf615fd62b2bbc763485e9df2cef63dbdb 100644 (file)
@@ -83,6 +83,28 @@ func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func), inlineMaxBudget int32) *
        return funcProps
 }
 
+// RevisitInlinability revisits the question of whether to continue to
+// treat function 'fn' as an inline candidate based on the set of
+// properties we've computed for it. If (for example) it has an
+// initial size score of 150 and no interesting properties to speak
+// of, then there isn't really any point to moving ahead with it as an
+// inline candidate.
+func RevisitInlinability(fn *ir.Func, budgetForFunc func(*ir.Func) int32) {
+       if fn.Inl == nil {
+               return
+       }
+       entry, ok := fpmap[fn]
+       if !ok {
+               //FIXME: issue error?
+               return
+       }
+       mxAdjust := int32(largestScoreAdjustment(fn, entry.props))
+       budget := budgetForFunc(fn)
+       if fn.Inl.Cost+mxAdjust > budget {
+               fn.Inl = nil
+       }
+}
+
 // computeFuncProps examines the Go function 'fn' and computes for it
 // a function "properties" object, to be used to drive inlining
 // heuristics. See comments on the FuncProps type for more info.
index d45d5f005e1ad64e103710960bb166cf910c8070..b490d234bac2e3392af1e4ee540fdc77d9f47414 100644 (file)
@@ -42,7 +42,7 @@ type scoreAdjustTyp uint
 // there may be control flow that could cause the benefit to be
 // bypassed.
 const (
-       // Catgegory 1 adjustments (see above)
+       // Category 1 adjustments (see above)
        panicPathAdj scoreAdjustTyp = (1 << iota)
        initFuncAdj
        inLoopAdj
@@ -96,7 +96,7 @@ func adjValue(x scoreAdjustTyp) int {
        }
 }
 
-var mayMust = [...]struct{ may, must scoreAdjustTyp }{
+var mayMustAdj = [...]struct{ may, must scoreAdjustTyp }{
        {may: passConstToNestedIfAdj, must: passConstToIfAdj},
        {may: passConcreteToNestedItfCallAdj, must: passConcreteToItfCallAdj},
        {may: passFuncToNestedIndCallAdj, must: passFuncToNestedIndCallAdj},
@@ -112,7 +112,7 @@ func isMust(x scoreAdjustTyp) bool {
 }
 
 func mayToMust(x scoreAdjustTyp) scoreAdjustTyp {
-       for _, v := range mayMust {
+       for _, v := range mayMustAdj {
                if x == v.may {
                        return v.must
                }
@@ -121,7 +121,7 @@ func mayToMust(x scoreAdjustTyp) scoreAdjustTyp {
 }
 
 func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
-       for _, v := range mayMust {
+       for _, v := range mayMustAdj {
                if x == v.must {
                        return v.may
                }
@@ -269,6 +269,67 @@ func adjustScore(typ scoreAdjustTyp, score int, mask scoreAdjustTyp) (int, score
        return score, mask
 }
 
+var resultFlagToPositiveAdj map[ResultPropBits]scoreAdjustTyp
+var paramFlagToPositiveAdj map[ParamPropBits]scoreAdjustTyp
+
+func setupFlagToAdjMaps() {
+       resultFlagToPositiveAdj = map[ResultPropBits]scoreAdjustTyp{
+               ResultIsAllocatedMem:     returnFeedsConcreteToInterfaceCallAdj,
+               ResultAlwaysSameFunc:     returnFeedsFuncToIndCallAdj,
+               ResultAlwaysSameConstant: returnFeedsConstToIfAdj,
+       }
+       paramFlagToPositiveAdj = map[ParamPropBits]scoreAdjustTyp{
+               ParamMayFeedInterfaceMethodCall: passConcreteToNestedItfCallAdj,
+               ParamFeedsInterfaceMethodCall:   passConcreteToItfCallAdj,
+               ParamMayFeedIndirectCall:        passInlinableFuncToNestedIndCallAdj,
+               ParamFeedsIndirectCall:          passInlinableFuncToIndCallAdj,
+       }
+}
+
+// largestScoreAdjustment tries to estimate the largest possible
+// negative score adjustment that could be applied to a call of the
+// function with the specified props. Example:
+//
+//     func foo() {                  func bar(x int, p *int) int {
+//        ...                          if x < 0 { *p = x }
+//     }                               return 99
+//                                   }
+//
+// Function 'foo' above on the left has no interesting properties,
+// thus as a result the most we'll adjust any call to is the value for
+// "call in loop". If the calculated cost of the function is 150, and
+// the in-loop adjustment is 5 (for example), then there is not much
+// point treating it as inlinable. On the other hand "bar" has a param
+// property (parameter "x" feeds unmodified to an "if" statement") and
+// a return property (always returns same constant) meaning that a
+// given call _could_ be rescored down as much as -35 points-- thus if
+// the size of "bar" is 100 (for example) then there is at least a
+// chance that scoring will enable inlining.
+func largestScoreAdjustment(fn *ir.Func, props *FuncProps) int {
+       if resultFlagToPositiveAdj == nil {
+               setupFlagToAdjMaps()
+       }
+       var tmask scoreAdjustTyp
+       score := adjValues[inLoopAdj] // any call can be in a loop
+       for _, pf := range props.ParamFlags {
+               if adj, ok := paramFlagToPositiveAdj[pf]; ok {
+                       score, tmask = adjustScore(adj, score, tmask)
+               }
+       }
+       for _, rf := range props.ResultFlags {
+               if adj, ok := resultFlagToPositiveAdj[rf]; ok {
+                       score, tmask = adjustScore(adj, score, tmask)
+               }
+       }
+
+       if debugTrace&debugTraceScoring != 0 {
+               fmt.Fprintf(os.Stderr, "=-= largestScore(%v) is %d\n",
+                       fn, score)
+       }
+
+       return score
+}
+
 // DumpInlCallSiteScores is invoked by the inliner if the debug flag
 // "-d=dumpinlcallsitescores" is set; it dumps out a human-readable
 // summary of all (potentially) inlinable callsites in the package,