From: Than McIntosh Date: Fri, 15 Sep 2023 19:48:49 +0000 (-0400) Subject: cmd/compile/inline/inleur: use "largest possible score" to revise inlinability X-Git-Tag: go1.22rc1~393 X-Git-Url: http://www.git.cypherpunks.ru/?p=gostls13.git;a=commitdiff_plain;h=04d64a3b36f872e97d965197337d001d5361d71c cmd/compile/inline/inleur: use "largest possible score" to revise inlinability 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 LUCI-TryBot-Result: Go LUCI --- diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index f3ad19d241..6283c4c3a4 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -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 diff --git a/src/cmd/compile/internal/inline/inlheur/analyze.go b/src/cmd/compile/internal/inline/inlheur/analyze.go index 9af7e1207d..3ef750bf61 100644 --- a/src/cmd/compile/internal/inline/inlheur/analyze.go +++ b/src/cmd/compile/internal/inline/inlheur/analyze.go @@ -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. diff --git a/src/cmd/compile/internal/inline/inlheur/scoring.go b/src/cmd/compile/internal/inline/inlheur/scoring.go index d45d5f005e..b490d234ba 100644 --- a/src/cmd/compile/internal/inline/inlheur/scoring.go +++ b/src/cmd/compile/internal/inline/inlheur/scoring.go @@ -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,