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/base"
9 "cmd/compile/internal/ir"
10 "cmd/compile/internal/types"
15 // funcFlagsAnalyzer computes the "Flags" value for the FuncProps
16 // object we're computing. The main item of interest here is "nstate",
17 // which stores the disposition of a given ir Node with respect to the
18 // flags/properties we're trying to compute.
19 type funcFlagsAnalyzer struct {
21 nstate map[ir.Node]pstate
22 noInfo bool // set if we see something inscrutable/un-analyzable
25 // pstate keeps track of the disposition of a given node and its
26 // children with respect to panic/exit calls.
30 psNoInfo pstate = iota // nothing interesting about this node
31 psCallsPanic // node causes call to panic or os.Exit
32 psMayReturn // executing node may trigger a "return" stmt
33 psTop // dataflow lattice "top" element
36 func makeFuncFlagsAnalyzer(fn *ir.Func) *funcFlagsAnalyzer {
37 return &funcFlagsAnalyzer{
39 nstate: make(map[ir.Node]pstate),
43 // setResults transfers func flag results to 'fp'.
44 func (ffa *funcFlagsAnalyzer) setResults(fp *FuncProps) {
46 if !ffa.noInfo && ffa.stateForList(ffa.fn.Body) == psCallsPanic {
47 rv = FuncPropNeverReturns
49 // This is slightly hacky and not at all required, but include a
50 // special case for main.main, which often ends in a call to
51 // os.Exit. People who write code like this (very common I
61 // will be constantly surprised when foo() is inlined in many
62 // other spots in the program but not in main().
63 if isMainMain(ffa.fn) {
64 rv &^= FuncPropNeverReturns
69 func (ffa *funcFlagsAnalyzer) getstate(n ir.Node) pstate {
70 val, ok := ffa.nstate[n]
72 base.Fatalf("funcFlagsAnalyzer: fn %q node %s line %s: internal error, no setting for node:\n%+v\n", ffa.fn.Sym().Name, n.Op().String(), ir.Line(n), n)
77 func (ffa *funcFlagsAnalyzer) setstate(n ir.Node, st pstate) {
78 if _, ok := ffa.nstate[n]; ok {
79 base.Fatalf("funcFlagsAnalyzer: fn %q internal error, existing setting for node:\n%+v\n", ffa.fn.Sym().Name, n)
85 func (ffa *funcFlagsAnalyzer) setstateSoft(n ir.Node, st pstate) {
89 // blockCombine merges together states as part of a linear sequence of
90 // statements, where 'pred' and 'succ' are analysis results for a pair
91 // of consecutive statements. Examples:
94 // panic("foo") if q { return x } <-pred
95 // return x panic("boo") <-succ
97 // In case 1, since the pred state is "always panic" it doesn't matter
98 // what the succ state is, hence the state for the combination of the
99 // two blocks is "always panics". In case 2, because there is a path
100 // to return that avoids the panic in succ, the state for the
101 // combination of the two statements is "may return".
102 func blockCombine(pred, succ pstate) pstate {
107 if pred == psCallsPanic {
114 if pred == psMayReturn {
119 panic("should never execute")
122 // branchCombine combines two states at a control flow branch point where
123 // either p1 or p2 executes (as in an "if" statement).
124 func branchCombine(p1, p2 pstate) pstate {
125 if p1 == psCallsPanic && p2 == psCallsPanic {
128 if p1 == psMayReturn || p2 == psMayReturn {
134 // stateForList walks through a list of statements and computes the
135 // state/diposition for the entire list as a whole.
136 func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate {
138 for i := range list {
140 psi := ffa.getstate(n)
141 if debugTrace&debugTraceFuncFlags != 0 {
142 fmt.Fprintf(os.Stderr, "=-= %v: stateForList n=%s ps=%s\n",
143 ir.Line(n), n.Op().String(), psi.String())
145 st = blockCombine(st, psi)
153 func isMainMain(fn *ir.Func) bool {
155 return (s.Pkg.Name == "main" && s.Name == "main")
158 func isWellKnownFunc(s *types.Sym, pkg, name string) bool {
159 return s.Pkg.Path == pkg && s.Name == name
162 // isExitCall reports TRUE if the node itself is an unconditional
163 // call to os.Exit(), a panic, or a function that does likewise.
164 func isExitCall(n ir.Node) bool {
165 if n.Op() != ir.OCALLFUNC {
168 cx := n.(*ir.CallExpr)
169 name := ir.StaticCalleeName(cx.X)
174 if isWellKnownFunc(s, "os", "Exit") ||
175 isWellKnownFunc(s, "runtime", "throw") {
178 if fp := propsForFunc(name.Func); fp != nil {
179 if fp.Flags&FuncPropNeverReturns != 0 {
186 // pessimize is called to record the fact that we saw something in the
187 // function that renders it entirely impossible to analyze.
188 func (ffa *funcFlagsAnalyzer) pessimize() {
192 // shouldVisit reports TRUE if this is an interesting node from the
193 // perspective of computing function flags. NB: due to the fact that
194 // ir.CallExpr implements the Stmt interface, we wind up visiting
195 // a lot of nodes that we don't really need to, but these can
196 // simply be screened out as part of the visit.
197 func shouldVisit(n ir.Node) bool {
198 _, isStmt := n.(ir.Stmt)
199 return n.Op() != ir.ODCL &&
200 (isStmt || n.Op() == ir.OCALLFUNC || n.Op() == ir.OPANIC)
203 // nodeVisitPost helps implement the propAnalyzer interface; when
204 // called on a given node, it decides the disposition of that node
205 // based on the state(s) of the node's children.
206 func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) {
207 if debugTrace&debugTraceFuncFlags != 0 {
208 fmt.Fprintf(os.Stderr, "=+= nodevis %v %s should=%v\n",
209 ir.Line(n), n.Op().String(), shouldVisit(n))
212 // invoke soft set, since node may be shared (e.g. ONAME)
213 ffa.setstateSoft(n, psNoInfo)
226 case ir.OBREAK, ir.OCONTINUE:
227 // FIXME: this handling of break/continue is sub-optimal; we
228 // have them as "mayReturn" in order to help with this case:
235 // where the effect of the 'break' is to cause the subsequent
236 // panic to be skipped. One possible improvement would be to
237 // track whether the currently enclosing loop is a "for {" or
238 // a for/range with condition, then use mayReturn only for the
239 // former. Note also that "break X" or "continue X" is treated
240 // the same as "goto", since we don't have a good way to track
241 // the target of the branch.
243 n := n.(*ir.BranchStmt)
248 n := n.(*ir.BlockStmt)
249 st = ffa.stateForList(n.List)
251 if ccst, ok := n.(*ir.CaseClause); ok {
252 st = ffa.stateForList(ccst.Body)
253 } else if ccst, ok := n.(*ir.CommClause); ok {
254 st = ffa.stateForList(ccst.Body)
260 st = branchCombine(ffa.stateForList(n.Body), ffa.stateForList(n.Else))
262 // Treat for { XXX } like a block.
263 // Treat for <cond> { XXX } like an if statement with no else.
265 bst := ffa.stateForList(n.Body)
269 if bst == psMayReturn {
274 // Treat for range { XXX } like an if statement with no else.
275 n := n.(*ir.RangeStmt)
276 if ffa.stateForList(n.Body) == psMayReturn {
280 // punt if we see even one goto. if we built a control
281 // flow graph we could do more, but this is just a tree walk.
284 // process selects for "may return" but not "always panics",
285 // the latter case seems very improbable.
286 n := n.(*ir.SelectStmt)
287 if len(n.Cases) != 0 {
289 for _, c := range n.Cases {
290 st = branchCombine(ffa.stateForList(c.Body), st)
294 n := n.(*ir.SwitchStmt)
295 if len(n.Cases) != 0 {
297 for _, c := range n.Cases {
298 st = branchCombine(ffa.stateForList(c.Body), st)
302 st, fall := psTop, psNoInfo
303 for i := len(n.Cases) - 1; i >= 0; i-- {
305 cst := ffa.stateForList(cas.Body)
306 endsInFallthrough := false
307 if len(cas.Body) != 0 {
308 endsInFallthrough = cas.Body[0].Op() == ir.OFALL
310 if endsInFallthrough {
311 cst = blockCombine(cst, fall)
313 st = branchCombine(st, cst)
318 case ir.ODCLFUNC, ir.ORECOVER, ir.OAS, ir.OAS2, ir.OAS2FUNC, ir.OASOP,
319 ir.OPRINTN, ir.OPRINT, ir.OLABEL, ir.OCALLINTER, ir.ODEFER,
320 ir.OSEND, ir.ORECV, ir.OSELRECV2, ir.OGO, ir.OAPPEND, ir.OAS2DOTTYPE,
321 ir.OAS2MAPR, ir.OGETG, ir.ODELETE, ir.OINLMARK, ir.OAS2RECV,
322 ir.OMIN, ir.OMAX, ir.OMAKE, ir.ORECOVERFP, ir.OGETCALLERSP:
323 // these should all be benign/uninteresting
324 case ir.OTAILCALL, ir.OJUMPTABLE, ir.OTYPESW:
325 // don't expect to see these at all.
326 base.Fatalf("unexpected op %s in func %s",
327 n.Op().String(), ir.FuncName(ffa.fn))
329 base.Fatalf("%v: unhandled op %s in func %v",
330 ir.Line(n), n.Op().String(), ir.FuncName(ffa.fn))
332 if debugTrace&debugTraceFuncFlags != 0 {
333 fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n",
334 ir.Line(n), n.Op().String(), st.String())
339 func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) {