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 'funcProps'.
44 func (ffa *funcFlagsAnalyzer) setResults(funcProps *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) updatestate(n ir.Node, st pstate) {
86 if _, ok := ffa.nstate[n]; !ok {
87 base.Fatalf("funcFlagsAnalyzer: fn %q internal error, expected existing setting for node:\n%+v\n", ffa.fn.Sym().Name, n)
93 func (ffa *funcFlagsAnalyzer) setstateSoft(n ir.Node, st pstate) {
97 func (ffa *funcFlagsAnalyzer) panicPathTable() map[ir.Node]pstate {
101 // blockCombine merges together states as part of a linear sequence of
102 // statements, where 'pred' and 'succ' are analysis results for a pair
103 // of consecutive statements. Examples:
106 // panic("foo") if q { return x } <-pred
107 // return x panic("boo") <-succ
109 // In case 1, since the pred state is "always panic" it doesn't matter
110 // what the succ state is, hence the state for the combination of the
111 // two blocks is "always panics". In case 2, because there is a path
112 // to return that avoids the panic in succ, the state for the
113 // combination of the two statements is "may return".
114 func blockCombine(pred, succ pstate) pstate {
119 if pred == psCallsPanic {
126 if pred == psMayReturn {
131 panic("should never execute")
134 // branchCombine combines two states at a control flow branch point where
135 // either p1 or p2 executes (as in an "if" statement).
136 func branchCombine(p1, p2 pstate) pstate {
137 if p1 == psCallsPanic && p2 == psCallsPanic {
140 if p1 == psMayReturn || p2 == psMayReturn {
146 // stateForList walks through a list of statements and computes the
147 // state/diposition for the entire list as a whole, as well
148 // as updating disposition of intermediate nodes.
149 func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate {
151 for i := range list {
153 psi := ffa.getstate(n)
154 if debugTrace&debugTraceFuncFlags != 0 {
155 fmt.Fprintf(os.Stderr, "=-= %v: stateForList n=%s ps=%s\n",
156 ir.Line(n), n.Op().String(), psi.String())
158 st = blockCombine(st, psi)
159 ffa.updatestate(n, st)
167 func isMainMain(fn *ir.Func) bool {
169 return (s.Pkg.Name == "main" && s.Name == "main")
172 func isWellKnownFunc(s *types.Sym, pkg, name string) bool {
173 return s.Pkg.Path == pkg && s.Name == name
176 // isExitCall reports TRUE if the node itself is an unconditional
177 // call to os.Exit(), a panic, or a function that does likewise.
178 func isExitCall(n ir.Node) bool {
179 if n.Op() != ir.OCALLFUNC {
182 cx := n.(*ir.CallExpr)
183 name := ir.StaticCalleeName(cx.Fun)
188 if isWellKnownFunc(s, "os", "Exit") ||
189 isWellKnownFunc(s, "runtime", "throw") {
192 if funcProps := propsForFunc(name.Func); funcProps != nil {
193 if funcProps.Flags&FuncPropNeverReturns != 0 {
197 return name.Func.NeverReturns()
200 // pessimize is called to record the fact that we saw something in the
201 // function that renders it entirely impossible to analyze.
202 func (ffa *funcFlagsAnalyzer) pessimize() {
206 // shouldVisit reports TRUE if this is an interesting node from the
207 // perspective of computing function flags. NB: due to the fact that
208 // ir.CallExpr implements the Stmt interface, we wind up visiting
209 // a lot of nodes that we don't really need to, but these can
210 // simply be screened out as part of the visit.
211 func shouldVisit(n ir.Node) bool {
212 _, isStmt := n.(ir.Stmt)
213 return n.Op() != ir.ODCL &&
214 (isStmt || n.Op() == ir.OCALLFUNC || n.Op() == ir.OPANIC)
217 // nodeVisitPost helps implement the propAnalyzer interface; when
218 // called on a given node, it decides the disposition of that node
219 // based on the state(s) of the node's children.
220 func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) {
221 if debugTrace&debugTraceFuncFlags != 0 {
222 fmt.Fprintf(os.Stderr, "=+= nodevis %v %s should=%v\n",
223 ir.Line(n), n.Op().String(), shouldVisit(n))
226 // invoke soft set, since node may be shared (e.g. ONAME)
227 ffa.setstateSoft(n, psNoInfo)
240 case ir.OBREAK, ir.OCONTINUE:
241 // FIXME: this handling of break/continue is sub-optimal; we
242 // have them as "mayReturn" in order to help with this case:
249 // where the effect of the 'break' is to cause the subsequent
250 // panic to be skipped. One possible improvement would be to
251 // track whether the currently enclosing loop is a "for {" or
252 // a for/range with condition, then use mayReturn only for the
253 // former. Note also that "break X" or "continue X" is treated
254 // the same as "goto", since we don't have a good way to track
255 // the target of the branch.
257 n := n.(*ir.BranchStmt)
262 n := n.(*ir.BlockStmt)
263 st = ffa.stateForList(n.List)
265 if ccst, ok := n.(*ir.CaseClause); ok {
266 st = ffa.stateForList(ccst.Body)
267 } else if ccst, ok := n.(*ir.CommClause); ok {
268 st = ffa.stateForList(ccst.Body)
274 st = branchCombine(ffa.stateForList(n.Body), ffa.stateForList(n.Else))
276 // Treat for { XXX } like a block.
277 // Treat for <cond> { XXX } like an if statement with no else.
279 bst := ffa.stateForList(n.Body)
283 if bst == psMayReturn {
288 // Treat for range { XXX } like an if statement with no else.
289 n := n.(*ir.RangeStmt)
290 if ffa.stateForList(n.Body) == psMayReturn {
294 // punt if we see even one goto. if we built a control
295 // flow graph we could do more, but this is just a tree walk.
298 // process selects for "may return" but not "always panics",
299 // the latter case seems very improbable.
300 n := n.(*ir.SelectStmt)
301 if len(n.Cases) != 0 {
303 for _, c := range n.Cases {
304 st = branchCombine(ffa.stateForList(c.Body), st)
308 n := n.(*ir.SwitchStmt)
309 if len(n.Cases) != 0 {
311 for _, c := range n.Cases {
312 st = branchCombine(ffa.stateForList(c.Body), st)
316 st, fall := psTop, psNoInfo
317 for i := len(n.Cases) - 1; i >= 0; i-- {
319 cst := ffa.stateForList(cas.Body)
320 endsInFallthrough := false
321 if len(cas.Body) != 0 {
322 endsInFallthrough = cas.Body[0].Op() == ir.OFALL
324 if endsInFallthrough {
325 cst = blockCombine(cst, fall)
327 st = branchCombine(st, cst)
332 case ir.ODCLFUNC, ir.ORECOVER, ir.OAS, ir.OAS2, ir.OAS2FUNC, ir.OASOP,
333 ir.OPRINTLN, ir.OPRINT, ir.OLABEL, ir.OCALLINTER, ir.ODEFER,
334 ir.OSEND, ir.ORECV, ir.OSELRECV2, ir.OGO, ir.OAPPEND, ir.OAS2DOTTYPE,
335 ir.OAS2MAPR, ir.OGETG, ir.ODELETE, ir.OINLMARK, ir.OAS2RECV,
336 ir.OMIN, ir.OMAX, ir.OMAKE, ir.ORECOVERFP, ir.OGETCALLERSP:
337 // these should all be benign/uninteresting
338 case ir.OTAILCALL, ir.OJUMPTABLE, ir.OTYPESW:
339 // don't expect to see these at all.
340 base.Fatalf("unexpected op %s in func %s",
341 n.Op().String(), ir.FuncName(ffa.fn))
343 base.Fatalf("%v: unhandled op %s in func %v",
344 ir.Line(n), n.Op().String(), ir.FuncName(ffa.fn))
346 if debugTrace&debugTraceFuncFlags != 0 {
347 fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n",
348 ir.Line(n), n.Op().String(), st.String())
353 func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) {