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"
13 // paramsAnalyzer holds state information for the phase that computes
14 // flags for a Go functions parameters, for use in inline heuristics.
15 // Note that the params slice below includes entries for blanks.
16 type paramsAnalyzer struct {
18 values []ParamPropBits
24 // dclParams returns a slice containing the non-blank, named params
25 // for the specific function (plus rcvr as well if applicable) in
27 func dclParams(fn *ir.Func) []*ir.Name {
28 params := []*ir.Name{}
29 for _, n := range fn.Dcl {
30 if n.Op() != ir.ONAME {
33 if n.Class != ir.PPARAM {
36 params = append(params, n)
41 // getParams returns an *ir.Name slice containing all params for the
42 // function (plus rcvr as well if applicable). Note that this slice
43 // includes entries for blanks; entries in the returned slice corresponding
44 // to blanks or unnamed params will be nil.
45 func getParams(fn *ir.Func) []*ir.Name {
46 dclparms := dclParams(fn)
48 recvrParms := fn.Type().RecvParams()
49 params := make([]*ir.Name, len(recvrParms))
50 for i := range recvrParms {
52 if recvrParms[i].Sym != nil &&
53 !recvrParms[i].Sym.IsBlank() {
62 func makeParamsAnalyzer(fn *ir.Func) *paramsAnalyzer {
63 params := getParams(fn) // includes receiver if applicable
64 vals := make([]ParamPropBits, len(params))
65 top := make([]bool, len(params))
66 for i, pn := range params {
71 if !pt.IsScalar() && !pt.HasNil() {
72 // existing properties not applicable here (for things
73 // like structs, arrays, slices, etc).
76 // If param is reassigned, skip it.
77 if ir.Reassigned(pn) {
83 if debugTrace&debugTraceParams != 0 {
84 fmt.Fprintf(os.Stderr, "=-= param analysis of func %v:\n",
89 n = params[i].Sym().String()
91 fmt.Fprintf(os.Stderr, "=-= %d: %q %s\n",
92 i, n, vals[i].String())
96 return ¶msAnalyzer{
101 condLevelTracker: new(condLevelTracker),
105 func (pa *paramsAnalyzer) setResults(fp *FuncProps) {
106 fp.ParamFlags = pa.values
109 func (pa *paramsAnalyzer) findParamIdx(n *ir.Name) int {
113 for i := range pa.params {
114 if pa.params[i] == n {
121 type testfType func(x ir.Node, param *ir.Name, idx int) (bool, bool)
123 // paramsAnalyzer invokes function 'testf' on the specified expression
124 // 'x' for each parameter, and if the result is TRUE, or's 'flag' into
125 // the flags for that param.
126 func (pa *paramsAnalyzer) checkParams(x ir.Node, flag ParamPropBits, mayflag ParamPropBits, testf testfType) {
127 for idx, p := range pa.params {
128 if !pa.top[idx] && pa.values[idx] == ParamNoInfo {
131 result, may := testf(x, p, idx)
132 if debugTrace&debugTraceParams != 0 {
133 fmt.Fprintf(os.Stderr, "=-= test expr %v param %s result=%v flag=%s\n", x, p.Sym().Name, result, flag.String())
137 if pa.condLevel != 0 || may {
146 // foldCheckParams checks expression 'x' (an 'if' condition or
147 // 'switch' stmt expr) to see if the expr would fold away if a
148 // specific parameter had a constant value.
149 func (pa *paramsAnalyzer) foldCheckParams(x ir.Node) {
150 pa.checkParams(x, ParamFeedsIfOrSwitch, ParamMayFeedIfOrSwitch,
151 func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
152 return ShouldFoldIfNameConstant(x, []*ir.Name{p}), false
156 // callCheckParams examines the target of call expression 'ce' to see
157 // if it is making a call to the value passed in for some parameter.
158 func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) {
161 if ce.Op() != ir.OCALLINTER {
164 sel := ce.X.(*ir.SelectorExpr)
165 r := ir.StaticValue(sel.X)
166 if r.Op() != ir.ONAME {
170 if name.Class != ir.PPARAM {
173 pa.checkParams(r, ParamFeedsInterfaceMethodCall,
174 ParamMayFeedInterfaceMethodCall,
175 func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
177 return name == p, false
180 if ce.X.Op() != ir.ONAME {
183 called := ir.StaticValue(ce.X)
184 if called.Op() != ir.ONAME {
187 name := called.(*ir.Name)
188 if name.Class == ir.PPARAM {
189 pa.checkParams(called, ParamFeedsIndirectCall,
190 ParamMayFeedIndirectCall,
191 func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
193 return name == p, false
196 cname, isFunc, _ := isFuncName(called)
198 pa.deriveFlagsFromCallee(ce, cname.Func)
204 // deriveFlagsFromCallee tries to derive flags for the current
205 // function based on a call this function makes to some other
206 // function. Example:
208 // /* Simple */ /* Derived from callee */
209 // func foo(f func(int)) { func foo(f func(int)) {
212 // func bar(x int, f func()) {
216 // Here we can set the "param feeds indirect call" flag for
217 // foo's param 'f' since we know that bar has that flag set for
218 // its second param, and we're passing that param a function.
219 func (pa *paramsAnalyzer) deriveFlagsFromCallee(ce *ir.CallExpr, callee *ir.Func) {
220 calleeProps := propsForFunc(callee)
221 if calleeProps == nil {
224 if debugTrace&debugTraceParams != 0 {
225 fmt.Fprintf(os.Stderr, "=-= callee props for %v:\n%s",
226 callee.Sym().Name, calleeProps.String())
229 must := []ParamPropBits{ParamFeedsInterfaceMethodCall, ParamFeedsIndirectCall, ParamFeedsIfOrSwitch}
230 may := []ParamPropBits{ParamMayFeedInterfaceMethodCall, ParamMayFeedIndirectCall, ParamMayFeedIfOrSwitch}
232 for pidx, arg := range ce.Args {
233 // Does the callee param have any interesting properties?
234 // If not we can skip this one.
235 pflag := calleeProps.ParamFlags[pidx]
239 // See if one of the caller's parameters is flowing unmodified
240 // into this actual expression.
241 r := ir.StaticValue(arg)
242 if r.Op() != ir.ONAME {
246 if name.Class != ir.PPARAM {
249 callerParamIdx := pa.findParamIdx(name)
250 if callerParamIdx == -1 || pa.params[callerParamIdx] == nil {
251 panic("something went wrong")
253 if !pa.top[callerParamIdx] &&
254 pa.values[callerParamIdx] == ParamNoInfo {
257 if debugTrace&debugTraceParams != 0 {
258 fmt.Fprintf(os.Stderr, "=-= pflag for arg %d is %s\n",
259 pidx, pflag.String())
261 for i := range must {
264 if pflag&mustv != 0 && pa.condLevel == 0 {
265 pa.values[callerParamIdx] |= mustv
266 } else if pflag&(mustv|mayv) != 0 {
267 pa.values[callerParamIdx] |= mayv
270 pa.top[callerParamIdx] = false
274 func (pa *paramsAnalyzer) nodeVisitPost(n ir.Node) {
275 if len(pa.values) == 0 {
278 pa.condLevelTracker.post(n)
281 ce := n.(*ir.CallExpr)
282 pa.callCheckParams(ce)
284 ce := n.(*ir.CallExpr)
285 pa.callCheckParams(ce)
287 ifst := n.(*ir.IfStmt)
288 pa.foldCheckParams(ifst.Cond)
290 swst := n.(*ir.SwitchStmt)
292 pa.foldCheckParams(swst.Tag)
297 func (pa *paramsAnalyzer) nodeVisitPre(n ir.Node) {
298 if len(pa.values) == 0 {
301 pa.condLevelTracker.pre(n)
304 // condLevelTracker helps keeps track very roughly of "level of conditional
305 // nesting", e.g. how many "if" statements you have to go through to
306 // get to the point where a given stmt executes. Example:
308 // cond nesting level
318 // The intent here is to provide some sort of very abstract relative
319 // hotness metric, e.g. "G = 1" above is expected to be executed more
320 // often than "G = 0" (in the aggregate, across large numbers of
322 type condLevelTracker struct {
326 func (c *condLevelTracker) pre(n ir.Node) {
327 // Increment level of "conditional testing" if we see
328 // an "if" or switch statement, and decrement if in
331 case ir.OIF, ir.OSWITCH:
333 case ir.OFOR, ir.ORANGE:
338 func (c *condLevelTracker) post(n ir.Node) {
340 case ir.OFOR, ir.ORANGE: