1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
8 "cmd/compile/internal/ir"
9 "cmd/compile/internal/typecheck"
14 // These constants enumerate the set of possible ways/scenarios
15 // in which we'll adjust the score of a given callsite.
16 type scoreAdjustTyp uint
19 panicPathAdj scoreAdjustTyp = (1 << iota)
23 passConstToNestedIfAdj
24 passConcreteToItfCallAdj
25 passConcreteToNestedItfCallAdj
27 passFuncToNestedIndCallAdj
28 passInlinableFuncToIndCallAdj
29 passInlinableFuncToNestedIndCallAdj
30 lastAdj scoreAdjustTyp = passInlinableFuncToNestedIndCallAdj
33 // This table records the specific values we use to adjust call
34 // site scores in a given scenario.
35 // NOTE: these numbers are chosen very arbitrarily; ideally
36 // we will go through some sort of turning process to decide
37 // what value for each one produces the best performance.
39 var adjValues = map[scoreAdjustTyp]int{
43 passConstToIfAdj: -20,
44 passConstToNestedIfAdj: -15,
45 passConcreteToItfCallAdj: -30,
46 passConcreteToNestedItfCallAdj: -25,
47 passFuncToIndCallAdj: -25,
48 passFuncToNestedIndCallAdj: -20,
49 passInlinableFuncToIndCallAdj: -45,
50 passInlinableFuncToNestedIndCallAdj: -40,
53 func adjValue(x scoreAdjustTyp) int {
54 if val, ok := adjValues[x]; ok {
57 panic("internal error unregistered adjustment type")
61 var mayMust = [...]struct{ may, must scoreAdjustTyp }{
62 {may: passConstToNestedIfAdj, must: passConstToIfAdj},
63 {may: passConcreteToNestedItfCallAdj, must: passConcreteToItfCallAdj},
64 {may: passFuncToNestedIndCallAdj, must: passFuncToNestedIndCallAdj},
65 {may: passInlinableFuncToNestedIndCallAdj, must: passInlinableFuncToIndCallAdj},
68 func isMay(x scoreAdjustTyp) bool {
69 return mayToMust(x) != 0
72 func isMust(x scoreAdjustTyp) bool {
73 return mustToMay(x) != 0
76 func mayToMust(x scoreAdjustTyp) scoreAdjustTyp {
77 for _, v := range mayMust {
85 func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
86 for _, v := range mayMust {
94 // computeCallSiteScore takes a given call site whose ir node is 'call' and
95 // callee function is 'callee' and with previously computed call site
96 // properties 'csflags', then computes a score for the callsite that
97 // combines the size cost of the callee with heuristics based on
98 // previously parameter and function properties.
99 func computeCallSiteScore(callee *ir.Func, calleeProps *FuncProps, call ir.Node, csflags CSPropBits) (int, scoreAdjustTyp) {
100 // Start with the size-based score for the callee.
101 score := int(callee.Inl.Cost)
102 var tmask scoreAdjustTyp
104 if debugTrace&debugTraceScoring != 0 {
105 fmt.Fprintf(os.Stderr, "=-= scoring call to %s at %s , initial=%d\n",
106 callee.Sym().Name, fmtFullPos(call.Pos()), score)
109 // First some score adjustments to discourage inlining in selected cases.
110 if csflags&CallSiteOnPanicPath != 0 {
111 score, tmask = adjustScore(panicPathAdj, score, tmask)
113 if csflags&CallSiteInInitFunc != 0 {
114 score, tmask = adjustScore(initFuncAdj, score, tmask)
117 // Then adjustments to encourage inlining in selected cases.
118 if csflags&CallSiteInLoop != 0 {
119 score, tmask = adjustScore(inLoopAdj, score, tmask)
122 // Walk through the actual expressions being passed at the call.
123 calleeRecvrParms := callee.Type().RecvParams()
124 ce := call.(*ir.CallExpr)
125 for idx := range ce.Args {
127 if calleeRecvrParms[idx].Sym == nil ||
128 calleeRecvrParms[idx].Sym.IsBlank() {
132 pflag := calleeProps.ParamFlags[idx]
133 if debugTrace&debugTraceScoring != 0 {
134 fmt.Fprintf(os.Stderr, "=-= arg %d of %d: val %v flags=%s\n",
135 idx, len(ce.Args), arg, pflag.String())
137 _, islit := isLiteral(arg)
138 iscci := isConcreteConvIface(arg)
139 fname, isfunc, _ := isFuncName(arg)
140 if debugTrace&debugTraceScoring != 0 {
141 fmt.Fprintf(os.Stderr, "=-= isLit=%v iscci=%v isfunc=%v for arg %v\n", islit, iscci, isfunc, arg)
145 if pflag&ParamMayFeedIfOrSwitch != 0 {
146 score, tmask = adjustScore(passConstToNestedIfAdj, score, tmask)
148 if pflag&ParamFeedsIfOrSwitch != 0 {
149 score, tmask = adjustScore(passConstToIfAdj, score, tmask)
154 // FIXME: ideally here it would be nice to make a
155 // distinction between the inlinable case and the
156 // non-inlinable case, but this is hard to do. Example:
158 // type I interface { Tiny() int; Giant() }
159 // type Conc struct { x int }
160 // func (c *Conc) Tiny() int { return 42 }
161 // func (c *Conc) Giant() { <huge amounts of code> }
163 // func passConcToItf(c *Conc) {
164 // makesItfMethodCall(c)
167 // In the code above, function properties will only tell
168 // us that 'makesItfMethodCall' invokes a method on its
169 // interface parameter, but we don't know whether it calls
170 // "Tiny" or "Giant". If we knew if called "Tiny", then in
171 // theory in addition to converting the interface call to
172 // a direct call, we could also inline (in which case
173 // we'd want to decrease the score even more).
175 // One thing we could do (not yet implemented) is iterate
176 // through all of the methods of "*Conc" that allow it to
177 // satisfy I, and if all are inlinable, then exploit that.
178 if pflag&ParamMayFeedInterfaceMethodCall != 0 {
179 score, tmask = adjustScore(passConcreteToNestedItfCallAdj, score, tmask)
181 if pflag&ParamFeedsInterfaceMethodCall != 0 {
182 score, tmask = adjustScore(passConcreteToItfCallAdj, score, tmask)
187 mayadj := passFuncToNestedIndCallAdj
188 mustadj := passFuncToIndCallAdj
189 if fn := fname.Func; fn != nil && typecheck.HaveInlineBody(fn) {
190 mayadj = passInlinableFuncToNestedIndCallAdj
191 mustadj = passInlinableFuncToIndCallAdj
193 if pflag&ParamMayFeedIndirectCall != 0 {
194 score, tmask = adjustScore(mayadj, score, tmask)
196 if pflag&ParamFeedsIndirectCall != 0 {
197 score, tmask = adjustScore(mustadj, score, tmask)
205 func adjustScore(typ scoreAdjustTyp, score int, mask scoreAdjustTyp) (int, scoreAdjustTyp) {
211 may := mustToMay(typ)
213 // promote may to must, so undo may
214 score -= adjValue(may)
217 } else if isMay(typ) {
218 must := mayToMust(typ)
219 if mask&(must|typ) != 0 {
224 if debugTrace&debugTraceScoring != 0 {
225 fmt.Fprintf(os.Stderr, "=-= applying adj %d for %s\n",
226 adjValue(typ), typ.String())
228 score += adjValue(typ)