|
| 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. |
| 4 | + |
| 5 | +package inlheur |
| 6 | + |
| 7 | +import ( |
| 8 | + "cmd/compile/internal/base" |
| 9 | + "cmd/compile/internal/ir" |
| 10 | + "cmd/compile/internal/types" |
| 11 | + "fmt" |
| 12 | + "os" |
| 13 | +) |
| 14 | + |
| 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 { |
| 20 | + fn *ir.Func |
| 21 | + nstate map[ir.Node]pstate |
| 22 | + noInfo bool // set if we see something inscrutable/un-analyzable |
| 23 | +} |
| 24 | + |
| 25 | +// pstate keeps track of the disposition of a given node and its |
| 26 | +// children with respect to panic/exit calls. |
| 27 | +type pstate int |
| 28 | + |
| 29 | +const ( |
| 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 |
| 34 | +) |
| 35 | + |
| 36 | +func makeFuncFlagsAnalyzer(fn *ir.Func) *funcFlagsAnalyzer { |
| 37 | + return &funcFlagsAnalyzer{ |
| 38 | + fn: fn, |
| 39 | + nstate: make(map[ir.Node]pstate), |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +// setResults transfers func flag results to 'fp'. |
| 44 | +func (ffa *funcFlagsAnalyzer) setResults(fp *FuncProps) { |
| 45 | + var rv FuncPropBits |
| 46 | + if !ffa.noInfo && ffa.stateForList(ffa.fn.Body) == psCallsPanic { |
| 47 | + rv = FuncPropNeverReturns |
| 48 | + } |
| 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 |
| 52 | + // imagine) |
| 53 | + // |
| 54 | + // func main() { |
| 55 | + // rc = perform() |
| 56 | + // ... |
| 57 | + // foo() |
| 58 | + // os.Exit(rc) |
| 59 | + // } |
| 60 | + // |
| 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 |
| 65 | + } |
| 66 | + fp.Flags = rv |
| 67 | +} |
| 68 | + |
| 69 | +func (ffa *funcFlagsAnalyzer) getstate(n ir.Node) pstate { |
| 70 | + val, ok := ffa.nstate[n] |
| 71 | + if !ok { |
| 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) |
| 73 | + } |
| 74 | + return val |
| 75 | +} |
| 76 | + |
| 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) |
| 80 | + } else { |
| 81 | + ffa.nstate[n] = st |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +func (ffa *funcFlagsAnalyzer) setstateSoft(n ir.Node, st pstate) { |
| 86 | + ffa.nstate[n] = st |
| 87 | +} |
| 88 | + |
| 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: |
| 92 | +// |
| 93 | +// case 1: case 2: |
| 94 | +// panic("foo") if q { return x } <-pred |
| 95 | +// return x panic("boo") <-succ |
| 96 | +// |
| 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 { |
| 103 | + switch succ { |
| 104 | + case psTop: |
| 105 | + return pred |
| 106 | + case psMayReturn: |
| 107 | + if pred == psCallsPanic { |
| 108 | + return psCallsPanic |
| 109 | + } |
| 110 | + return psMayReturn |
| 111 | + case psNoInfo: |
| 112 | + return pred |
| 113 | + case psCallsPanic: |
| 114 | + if pred == psMayReturn { |
| 115 | + return psMayReturn |
| 116 | + } |
| 117 | + return psCallsPanic |
| 118 | + } |
| 119 | + panic("should never execute") |
| 120 | +} |
| 121 | + |
| 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 { |
| 126 | + return psCallsPanic |
| 127 | + } |
| 128 | + if p1 == psMayReturn || p2 == psMayReturn { |
| 129 | + return psMayReturn |
| 130 | + } |
| 131 | + return psNoInfo |
| 132 | +} |
| 133 | + |
| 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 { |
| 137 | + st := psTop |
| 138 | + for i := range list { |
| 139 | + n := list[i] |
| 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()) |
| 144 | + } |
| 145 | + st = blockCombine(st, psi) |
| 146 | + } |
| 147 | + if st == psTop { |
| 148 | + st = psNoInfo |
| 149 | + } |
| 150 | + return st |
| 151 | +} |
| 152 | + |
| 153 | +func isMainMain(fn *ir.Func) bool { |
| 154 | + s := fn.Sym() |
| 155 | + return (s.Pkg.Name == "main" && s.Name == "main") |
| 156 | +} |
| 157 | + |
| 158 | +func isWellKnownFunc(s *types.Sym, pkg, name string) bool { |
| 159 | + return s.Pkg.Path == pkg && s.Name == name |
| 160 | +} |
| 161 | + |
| 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 { |
| 166 | + return false |
| 167 | + } |
| 168 | + cx := n.(*ir.CallExpr) |
| 169 | + name := ir.StaticCalleeName(cx.X) |
| 170 | + if name == nil { |
| 171 | + return false |
| 172 | + } |
| 173 | + s := name.Sym() |
| 174 | + if isWellKnownFunc(s, "os", "Exit") || |
| 175 | + isWellKnownFunc(s, "runtime", "throw") { |
| 176 | + return true |
| 177 | + } |
| 178 | + // FIXME: consult results of flags computation for |
| 179 | + // previously analyzed Go functions, including props |
| 180 | + // read from export data for functions in other packages. |
| 181 | + return false |
| 182 | +} |
| 183 | + |
| 184 | +// pessimize is called to record the fact that we saw something in the |
| 185 | +// function that renders it entirely impossible to analyze. |
| 186 | +func (ffa *funcFlagsAnalyzer) pessimize() { |
| 187 | + ffa.noInfo = true |
| 188 | +} |
| 189 | + |
| 190 | +// shouldVisit reports TRUE if this is an interesting node from the |
| 191 | +// perspective of computing function flags. NB: due to the fact that |
| 192 | +// ir.CallExpr implements the Stmt interface, we wind up visiting |
| 193 | +// a lot of nodes that we don't really need to, but these can |
| 194 | +// simply be screened out as part of the visit. |
| 195 | +func shouldVisit(n ir.Node) bool { |
| 196 | + _, isStmt := n.(ir.Stmt) |
| 197 | + return n.Op() != ir.ODCL && |
| 198 | + (isStmt || n.Op() == ir.OCALLFUNC || n.Op() == ir.OPANIC) |
| 199 | +} |
| 200 | + |
| 201 | +// nodeVisitPost helps implement the propAnalyzer interface; when |
| 202 | +// called on a given node, it decides the disposition of that node |
| 203 | +// based on the state(s) of the node's children. |
| 204 | +func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) { |
| 205 | + if debugTrace&debugTraceFuncFlags != 0 { |
| 206 | + fmt.Fprintf(os.Stderr, "=+= nodevis %v %s should=%v\n", |
| 207 | + ir.Line(n), n.Op().String(), shouldVisit(n)) |
| 208 | + } |
| 209 | + if !shouldVisit(n) { |
| 210 | + // invoke soft set, since node may be shared (e.g. ONAME) |
| 211 | + ffa.setstateSoft(n, psNoInfo) |
| 212 | + return |
| 213 | + } |
| 214 | + var st pstate |
| 215 | + switch n.Op() { |
| 216 | + case ir.OCALLFUNC: |
| 217 | + if isExitCall(n) { |
| 218 | + st = psCallsPanic |
| 219 | + } |
| 220 | + case ir.OPANIC: |
| 221 | + st = psCallsPanic |
| 222 | + case ir.ORETURN: |
| 223 | + st = psMayReturn |
| 224 | + case ir.OBREAK, ir.OCONTINUE: |
| 225 | + // FIXME: this handling of break/continue is sub-optimal; we |
| 226 | + // have them as "mayReturn" in order to help with this case: |
| 227 | + // |
| 228 | + // for { |
| 229 | + // if q() { break } |
| 230 | + // panic(...) |
| 231 | + // } |
| 232 | + // |
| 233 | + // where the effect of the 'break' is to cause the subsequent |
| 234 | + // panic to be skipped. One possible improvement would be to |
| 235 | + // track whether the currently enclosing loop is a "for {" or |
| 236 | + // a for/range with condition, then use mayReturn only for the |
| 237 | + // former. Note also that "break X" or "continue X" is treated |
| 238 | + // the same as "goto", since we don't have a good way to track |
| 239 | + // the target of the branch. |
| 240 | + st = psMayReturn |
| 241 | + n := n.(*ir.BranchStmt) |
| 242 | + if n.Label != nil { |
| 243 | + ffa.pessimize() |
| 244 | + } |
| 245 | + case ir.OBLOCK: |
| 246 | + n := n.(*ir.BlockStmt) |
| 247 | + st = ffa.stateForList(n.List) |
| 248 | + case ir.OCASE: |
| 249 | + if ccst, ok := n.(*ir.CaseClause); ok { |
| 250 | + st = ffa.stateForList(ccst.Body) |
| 251 | + } else if ccst, ok := n.(*ir.CommClause); ok { |
| 252 | + st = ffa.stateForList(ccst.Body) |
| 253 | + } else { |
| 254 | + panic("unexpected") |
| 255 | + } |
| 256 | + case ir.OIF: |
| 257 | + n := n.(*ir.IfStmt) |
| 258 | + st = branchCombine(ffa.stateForList(n.Body), ffa.stateForList(n.Else)) |
| 259 | + case ir.OFOR: |
| 260 | + // Treat for { XXX } like a block. |
| 261 | + // Treat for <cond> { XXX } like an if statement with no else. |
| 262 | + n := n.(*ir.ForStmt) |
| 263 | + bst := ffa.stateForList(n.Body) |
| 264 | + if n.Cond == nil { |
| 265 | + st = bst |
| 266 | + } else { |
| 267 | + if bst == psMayReturn { |
| 268 | + st = psMayReturn |
| 269 | + } |
| 270 | + } |
| 271 | + case ir.ORANGE: |
| 272 | + // Treat for range { XXX } like an if statement with no else. |
| 273 | + n := n.(*ir.RangeStmt) |
| 274 | + if ffa.stateForList(n.Body) == psMayReturn { |
| 275 | + st = psMayReturn |
| 276 | + } |
| 277 | + case ir.OGOTO: |
| 278 | + // punt if we see even one goto. if we built a control |
| 279 | + // flow graph we could do more, but this is just a tree walk. |
| 280 | + ffa.pessimize() |
| 281 | + case ir.OSELECT: |
| 282 | + // process selects for "may return" but not "always panics", |
| 283 | + // the latter case seems very improbable. |
| 284 | + n := n.(*ir.SelectStmt) |
| 285 | + if len(n.Cases) != 0 { |
| 286 | + st = psTop |
| 287 | + for _, c := range n.Cases { |
| 288 | + st = branchCombine(ffa.stateForList(c.Body), st) |
| 289 | + } |
| 290 | + } |
| 291 | + case ir.OSWITCH: |
| 292 | + n := n.(*ir.SwitchStmt) |
| 293 | + if len(n.Cases) != 0 { |
| 294 | + st = psTop |
| 295 | + for _, c := range n.Cases { |
| 296 | + st = branchCombine(ffa.stateForList(c.Body), st) |
| 297 | + } |
| 298 | + } |
| 299 | + |
| 300 | + st, fall := psTop, psNoInfo |
| 301 | + for i := len(n.Cases) - 1; i >= 0; i-- { |
| 302 | + cas := n.Cases[i] |
| 303 | + cst := ffa.stateForList(cas.Body) |
| 304 | + endsInFallthrough := false |
| 305 | + if len(cas.Body) != 0 { |
| 306 | + endsInFallthrough = cas.Body[0].Op() == ir.OFALL |
| 307 | + } |
| 308 | + if endsInFallthrough { |
| 309 | + cst = blockCombine(cst, fall) |
| 310 | + } |
| 311 | + st = branchCombine(st, cst) |
| 312 | + fall = cst |
| 313 | + } |
| 314 | + case ir.OFALL: |
| 315 | + // Not important. |
| 316 | + case ir.ODCLFUNC, ir.ORECOVER, ir.OAS, ir.OAS2, ir.OAS2FUNC, ir.OASOP, |
| 317 | + ir.OPRINTN, ir.OPRINT, ir.OLABEL, ir.OCALLINTER, ir.ODEFER, |
| 318 | + ir.OSEND, ir.ORECV, ir.OSELRECV2, ir.OGO, ir.OAPPEND, ir.OAS2DOTTYPE, |
| 319 | + ir.OAS2MAPR, ir.OGETG, ir.ODELETE, ir.OINLMARK, ir.OAS2RECV, |
| 320 | + ir.OMIN, ir.OMAX, ir.OMAKE, ir.ORECOVERFP, ir.OGETCALLERSP: |
| 321 | + // these should all be benign/uninteresting |
| 322 | + case ir.OTAILCALL, ir.OJUMPTABLE, ir.OTYPESW: |
| 323 | + // don't expect to see these at all. |
| 324 | + base.Fatalf("unexpected op %s in func %s", |
| 325 | + n.Op().String(), ir.FuncName(ffa.fn)) |
| 326 | + default: |
| 327 | + base.Fatalf("%v: unhandled op %s in func %v", |
| 328 | + ir.Line(n), n.Op().String(), ir.FuncName(ffa.fn)) |
| 329 | + } |
| 330 | + if debugTrace&debugTraceFuncFlags != 0 { |
| 331 | + fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n", |
| 332 | + ir.Line(n), n.Op().String(), st.String()) |
| 333 | + } |
| 334 | + ffa.setstate(n, st) |
| 335 | +} |
| 336 | + |
| 337 | +func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) { |
| 338 | +} |
0 commit comments