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"
15 // returnsAnalyzer stores state information for the process of
16 // computing flags/properties for the return values of a specific Go
17 // function, as part of inline heuristics synthesis.
18 type returnsAnalyzer struct {
20 props []ResultPropBits
22 canInline func(*ir.Func)
25 // resultVal captures information about a specific result returned from
26 // the function we're analyzing; we are interested in cases where
27 // the func always returns the same constant, or always returns
28 // the same function, etc. This container stores info on a the specific
29 // scenarios we're looking for.
30 type resultVal struct {
35 derived bool // see deriveReturnFlagsFromCallee below
38 func makeResultsAnalyzer(fn *ir.Func, canInline func(*ir.Func)) *returnsAnalyzer {
39 results := fn.Type().Results()
40 props := make([]ResultPropBits, len(results))
41 vals := make([]resultVal, len(results))
42 for i := range results {
44 if !rt.IsScalar() && !rt.HasNil() {
45 // existing properties not applicable here (for things
46 // like structs, arrays, slices, etc).
47 props[i] = ResultNoInfo
50 // set the "top" flag (as in "top element of data flow lattice")
51 // meaning "we have no info yet, but we might later on".
54 return &returnsAnalyzer{
61 // setResults transfers the calculated result properties for this
63 func (ra *returnsAnalyzer) setResults(fp *FuncProps) {
64 // Promote ResultAlwaysSameFunc to ResultAlwaysSameInlinableFunc
65 for i := range ra.values {
66 if ra.props[i] == ResultAlwaysSameFunc && !ra.values[i].derived {
67 f := ra.values[i].fn.Func
68 // If the function being returns is a closure that hasn't
69 // yet been checked by CanInline, invoke it now. NB: this
70 // is hacky, it would be better if things were structured
71 // so that all closures were visited ahead of time.
72 if ra.values[i].fnClo {
73 if f != nil && !f.InlinabilityChecked() {
78 ra.props[i] = ResultAlwaysSameInlinableFunc
82 fp.ResultFlags = ra.props
85 func (ra *returnsAnalyzer) pessimize() {
86 for i := range ra.props {
87 ra.props[i] = ResultNoInfo
91 func (ra *returnsAnalyzer) nodeVisitPre(n ir.Node) {
94 func (ra *returnsAnalyzer) nodeVisitPost(n ir.Node) {
95 if len(ra.values) == 0 {
98 if n.Op() != ir.ORETURN {
101 if debugTrace&debugTraceResults != 0 {
102 fmt.Fprintf(os.Stderr, "=+= returns nodevis %v %s\n",
103 ir.Line(n), n.Op().String())
106 // No support currently for named results, so if we see an empty
107 // "return" stmt, be conservative.
108 rs := n.(*ir.ReturnStmt)
109 if len(rs.Results) != len(ra.values) {
113 for i, r := range rs.Results {
114 ra.analyzeResult(i, r)
118 // isFuncName returns the *ir.Name for the func or method
119 // corresponding to node 'n', along with a boolean indicating success,
120 // and another boolean indicating whether the func is closure.
121 func isFuncName(n ir.Node) (*ir.Name, bool, bool) {
122 sv := ir.StaticValue(n)
123 if sv.Op() == ir.ONAME {
124 name := sv.(*ir.Name)
125 if name.Sym() != nil && name.Class == ir.PFUNC {
126 return name, true, false
129 if sv.Op() == ir.OCLOSURE {
130 cloex := sv.(*ir.ClosureExpr)
131 return cloex.Func.Nname, true, true
133 if sv.Op() == ir.OMETHEXPR {
134 if mn := ir.MethodExprName(sv); mn != nil {
135 return mn, true, false
138 return nil, false, false
141 // analyzeResult examines the expression 'n' being returned as the
142 // 'ii'th argument in some return statement to see whether has
143 // interesting characteristics (for example, returns a constant), then
144 // applies a dataflow "meet" operation to combine this result with any
145 // previous result (for the given return slot) that we've already
147 func (ra *returnsAnalyzer) analyzeResult(ii int, n ir.Node) {
148 isAllocMem := isAllocatedMem(n)
149 isConcConvItf := isConcreteConvIface(n)
150 lit, isConst := isLiteral(n)
151 rfunc, isFunc, isClo := isFuncName(n)
153 dprops, isDerivedFromCall := deriveReturnFlagsFromCallee(n)
155 var newlit constant.Value
158 if debugTrace&debugTraceResults != 0 {
159 fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult n=%s ismem=%v isconcconv=%v isconst=%v isfunc=%v isclo=%v\n", ir.Line(n), n.Op().String(), isAllocMem, isConcConvItf, isConst, isFunc, isClo)
162 if ra.values[ii].top {
163 ra.values[ii].top = false
164 // this is the first return we've seen; record
165 // whatever properties it has.
168 newp = ResultIsAllocatedMem
170 newp = ResultIsConcreteTypeConvertedToInterface
172 newp = ResultAlwaysSameFunc
175 newp = ResultAlwaysSameConstant
177 case isDerivedFromCall:
179 ra.values[ii].derived = true
182 if !ra.values[ii].derived {
183 // this is not the first return we've seen; apply
184 // what amounts of a "meet" operator to combine
185 // the properties we see here with what we saw on
186 // the previous returns.
188 case ResultIsAllocatedMem:
189 if isAllocatedMem(n) {
190 newp = ResultIsAllocatedMem
192 case ResultIsConcreteTypeConvertedToInterface:
193 if isConcreteConvIface(n) {
194 newp = ResultIsConcreteTypeConvertedToInterface
196 case ResultAlwaysSameConstant:
197 if isConst && isSameLiteral(lit, ra.values[ii].lit) {
198 newp = ResultAlwaysSameConstant
201 case ResultAlwaysSameFunc:
202 if isFunc && isSameFuncName(rfunc, ra.values[ii].fn) {
203 newp = ResultAlwaysSameFunc
209 ra.values[ii].fn = newfunc
210 ra.values[ii].fnClo = isClo
211 ra.values[ii].lit = newlit
214 if debugTrace&debugTraceResults != 0 {
215 fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult newp=%s\n",
220 func isAllocatedMem(n ir.Node) bool {
221 sv := ir.StaticValue(n)
223 case ir.OMAKESLICE, ir.ONEW, ir.OPTRLIT, ir.OSLICELIT:
229 // deriveReturnFlagsFromCallee tries to set properties for a given
230 // return result where we're returning call expression; return value
231 // is a return property value and a boolean indicating whether the
232 // prop is valid. Examples:
234 // func foo() int { return bar() }
235 // func bar() int { return 42 }
236 // func blix() int { return 43 }
237 // func two(y int) int {
238 // if y < 0 { return bar() } else { return blix() }
241 // Since "foo" always returns the result of a call to "bar", we can
242 // set foo's return property to that of bar. In the case of "two", however,
243 // even though each return path returns a constant, we don't know
244 // whether the constants are identical, hence we need to be conservative.
245 func deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
246 if n.Op() != ir.OCALLFUNC {
249 ce := n.(*ir.CallExpr)
250 if ce.Fun.Op() != ir.ONAME {
253 called := ir.StaticValue(ce.Fun)
254 if called.Op() != ir.ONAME {
257 cname, isFunc, _ := isFuncName(called)
261 calleeProps := propsForFunc(cname.Func)
262 if calleeProps == nil {
265 if len(calleeProps.ResultFlags) != 1 {
268 return calleeProps.ResultFlags[0], true
271 func isLiteral(n ir.Node) (constant.Value, bool) {
272 sv := ir.StaticValue(n)
277 return sv.Val(), true
282 // isSameLiteral checks to see if 'v1' and 'v2' correspond to the same
283 // literal value, or if they are both nil.
284 func isSameLiteral(v1, v2 constant.Value) bool {
285 if v1 == nil && v2 == nil {
288 if v1 == nil || v2 == nil {
291 return constant.Compare(v1, token.EQL, v2)
294 func isConcreteConvIface(n ir.Node) bool {
295 sv := ir.StaticValue(n)
296 if sv.Op() != ir.OCONVIFACE {
299 return !sv.(*ir.ConvExpr).X.Type().IsInterface()
302 func isSameFuncName(v1, v2 *ir.Name) bool {
303 // NB: there are a few corner cases where pointer equality
304 // doesn't work here, but this should be good enough for
305 // our purposes here.