github.com/bir3/gocompiler@v0.9.2202/src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go (about) 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 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 9 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 10 "github.com/bir3/gocompiler/src/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 'funcProps'. 44 func (ffa *funcFlagsAnalyzer) setResults(funcProps *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 funcProps.Flags = rv 67 } 68 69 func (ffa *funcFlagsAnalyzer) getState(n ir.Node) pstate { 70 return ffa.nstate[n] 71 } 72 73 func (ffa *funcFlagsAnalyzer) setState(n ir.Node, st pstate) { 74 if st != psNoInfo { 75 ffa.nstate[n] = st 76 } 77 } 78 79 func (ffa *funcFlagsAnalyzer) updateState(n ir.Node, st pstate) { 80 if st == psNoInfo { 81 delete(ffa.nstate, n) 82 } else { 83 ffa.nstate[n] = st 84 } 85 } 86 87 func (ffa *funcFlagsAnalyzer) panicPathTable() map[ir.Node]pstate { 88 return ffa.nstate 89 } 90 91 // blockCombine merges together states as part of a linear sequence of 92 // statements, where 'pred' and 'succ' are analysis results for a pair 93 // of consecutive statements. Examples: 94 // 95 // case 1: case 2: 96 // panic("foo") if q { return x } <-pred 97 // return x panic("boo") <-succ 98 // 99 // In case 1, since the pred state is "always panic" it doesn't matter 100 // what the succ state is, hence the state for the combination of the 101 // two blocks is "always panics". In case 2, because there is a path 102 // to return that avoids the panic in succ, the state for the 103 // combination of the two statements is "may return". 104 func blockCombine(pred, succ pstate) pstate { 105 switch succ { 106 case psTop: 107 return pred 108 case psMayReturn: 109 if pred == psCallsPanic { 110 return psCallsPanic 111 } 112 return psMayReturn 113 case psNoInfo: 114 return pred 115 case psCallsPanic: 116 if pred == psMayReturn { 117 return psMayReturn 118 } 119 return psCallsPanic 120 } 121 panic("should never execute") 122 } 123 124 // branchCombine combines two states at a control flow branch point where 125 // either p1 or p2 executes (as in an "if" statement). 126 func branchCombine(p1, p2 pstate) pstate { 127 if p1 == psCallsPanic && p2 == psCallsPanic { 128 return psCallsPanic 129 } 130 if p1 == psMayReturn || p2 == psMayReturn { 131 return psMayReturn 132 } 133 return psNoInfo 134 } 135 136 // stateForList walks through a list of statements and computes the 137 // state/diposition for the entire list as a whole, as well 138 // as updating disposition of intermediate nodes. 139 func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate { 140 st := psTop 141 // Walk the list backwards so that we can update the state for 142 // earlier list elements based on what we find out about their 143 // successors. Example: 144 // 145 // if ... { 146 // L10: foo() 147 // L11: <stmt> 148 // L12: panic(...) 149 // } 150 // 151 // After combining the dispositions for line 11 and 12, we want to 152 // update the state for the call at line 10 based on that combined 153 // disposition (if L11 has no path to "return", then the call at 154 // line 10 will be on a panic path). 155 for i := len(list) - 1; i >= 0; i-- { 156 n := list[i] 157 psi := ffa.getState(n) 158 if debugTrace&debugTraceFuncFlags != 0 { 159 fmt.Fprintf(os.Stderr, "=-= %v: stateForList n=%s ps=%s\n", 160 ir.Line(n), n.Op().String(), psi.String()) 161 } 162 st = blockCombine(psi, st) 163 ffa.updateState(n, st) 164 } 165 if st == psTop { 166 st = psNoInfo 167 } 168 return st 169 } 170 171 func isMainMain(fn *ir.Func) bool { 172 s := fn.Sym() 173 return (s.Pkg.Name == "main" && s.Name == "main") 174 } 175 176 func isWellKnownFunc(s *types.Sym, pkg, name string) bool { 177 return s.Pkg.Path == pkg && s.Name == name 178 } 179 180 // isExitCall reports TRUE if the node itself is an unconditional 181 // call to os.Exit(), a panic, or a function that does likewise. 182 func isExitCall(n ir.Node) bool { 183 if n.Op() != ir.OCALLFUNC { 184 return false 185 } 186 cx := n.(*ir.CallExpr) 187 name := ir.StaticCalleeName(cx.Fun) 188 if name == nil { 189 return false 190 } 191 s := name.Sym() 192 if isWellKnownFunc(s, "os", "Exit") || 193 isWellKnownFunc(s, "runtime", "throw") { 194 return true 195 } 196 if funcProps := propsForFunc(name.Func); funcProps != nil { 197 if funcProps.Flags&FuncPropNeverReturns != 0 { 198 return true 199 } 200 } 201 return name.Func.NeverReturns() 202 } 203 204 // pessimize is called to record the fact that we saw something in the 205 // function that renders it entirely impossible to analyze. 206 func (ffa *funcFlagsAnalyzer) pessimize() { 207 ffa.noInfo = true 208 } 209 210 // shouldVisit reports TRUE if this is an interesting node from the 211 // perspective of computing function flags. NB: due to the fact that 212 // ir.CallExpr implements the Stmt interface, we wind up visiting 213 // a lot of nodes that we don't really need to, but these can 214 // simply be screened out as part of the visit. 215 func shouldVisit(n ir.Node) bool { 216 _, isStmt := n.(ir.Stmt) 217 return n.Op() != ir.ODCL && 218 (isStmt || n.Op() == ir.OCALLFUNC || n.Op() == ir.OPANIC) 219 } 220 221 // nodeVisitPost helps implement the propAnalyzer interface; when 222 // called on a given node, it decides the disposition of that node 223 // based on the state(s) of the node's children. 224 func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) { 225 if debugTrace&debugTraceFuncFlags != 0 { 226 fmt.Fprintf(os.Stderr, "=+= nodevis %v %s should=%v\n", 227 ir.Line(n), n.Op().String(), shouldVisit(n)) 228 } 229 if !shouldVisit(n) { 230 return 231 } 232 var st pstate 233 switch n.Op() { 234 case ir.OCALLFUNC: 235 if isExitCall(n) { 236 st = psCallsPanic 237 } 238 case ir.OPANIC: 239 st = psCallsPanic 240 case ir.ORETURN: 241 st = psMayReturn 242 case ir.OBREAK, ir.OCONTINUE: 243 // FIXME: this handling of break/continue is sub-optimal; we 244 // have them as "mayReturn" in order to help with this case: 245 // 246 // for { 247 // if q() { break } 248 // panic(...) 249 // } 250 // 251 // where the effect of the 'break' is to cause the subsequent 252 // panic to be skipped. One possible improvement would be to 253 // track whether the currently enclosing loop is a "for {" or 254 // a for/range with condition, then use mayReturn only for the 255 // former. Note also that "break X" or "continue X" is treated 256 // the same as "goto", since we don't have a good way to track 257 // the target of the branch. 258 st = psMayReturn 259 n := n.(*ir.BranchStmt) 260 if n.Label != nil { 261 ffa.pessimize() 262 } 263 case ir.OBLOCK: 264 n := n.(*ir.BlockStmt) 265 st = ffa.stateForList(n.List) 266 case ir.OCASE: 267 if ccst, ok := n.(*ir.CaseClause); ok { 268 st = ffa.stateForList(ccst.Body) 269 } else if ccst, ok := n.(*ir.CommClause); ok { 270 st = ffa.stateForList(ccst.Body) 271 } else { 272 panic("unexpected") 273 } 274 case ir.OIF: 275 n := n.(*ir.IfStmt) 276 st = branchCombine(ffa.stateForList(n.Body), ffa.stateForList(n.Else)) 277 case ir.OFOR: 278 // Treat for { XXX } like a block. 279 // Treat for <cond> { XXX } like an if statement with no else. 280 n := n.(*ir.ForStmt) 281 bst := ffa.stateForList(n.Body) 282 if n.Cond == nil { 283 st = bst 284 } else { 285 if bst == psMayReturn { 286 st = psMayReturn 287 } 288 } 289 case ir.ORANGE: 290 // Treat for range { XXX } like an if statement with no else. 291 n := n.(*ir.RangeStmt) 292 if ffa.stateForList(n.Body) == psMayReturn { 293 st = psMayReturn 294 } 295 case ir.OGOTO: 296 // punt if we see even one goto. if we built a control 297 // flow graph we could do more, but this is just a tree walk. 298 ffa.pessimize() 299 case ir.OSELECT: 300 // process selects for "may return" but not "always panics", 301 // the latter case seems very improbable. 302 n := n.(*ir.SelectStmt) 303 if len(n.Cases) != 0 { 304 st = psTop 305 for _, c := range n.Cases { 306 st = branchCombine(ffa.stateForList(c.Body), st) 307 } 308 } 309 case ir.OSWITCH: 310 n := n.(*ir.SwitchStmt) 311 if len(n.Cases) != 0 { 312 st = psTop 313 for _, c := range n.Cases { 314 st = branchCombine(ffa.stateForList(c.Body), st) 315 } 316 } 317 318 st, fall := psTop, psNoInfo 319 for i := len(n.Cases) - 1; i >= 0; i-- { 320 cas := n.Cases[i] 321 cst := ffa.stateForList(cas.Body) 322 endsInFallthrough := false 323 if len(cas.Body) != 0 { 324 endsInFallthrough = cas.Body[0].Op() == ir.OFALL 325 } 326 if endsInFallthrough { 327 cst = blockCombine(cst, fall) 328 } 329 st = branchCombine(st, cst) 330 fall = cst 331 } 332 case ir.OFALL: 333 // Not important. 334 case ir.ODCLFUNC, ir.ORECOVER, ir.OAS, ir.OAS2, ir.OAS2FUNC, ir.OASOP, 335 ir.OPRINTLN, ir.OPRINT, ir.OLABEL, ir.OCALLINTER, ir.ODEFER, 336 ir.OSEND, ir.ORECV, ir.OSELRECV2, ir.OGO, ir.OAPPEND, ir.OAS2DOTTYPE, 337 ir.OAS2MAPR, ir.OGETG, ir.ODELETE, ir.OINLMARK, ir.OAS2RECV, 338 ir.OMIN, ir.OMAX, ir.OMAKE, ir.ORECOVERFP, ir.OGETCALLERSP: 339 // these should all be benign/uninteresting 340 case ir.OTAILCALL, ir.OJUMPTABLE, ir.OTYPESW: 341 // don't expect to see these at all. 342 base.Fatalf("unexpected op %s in func %s", 343 n.Op().String(), ir.FuncName(ffa.fn)) 344 default: 345 base.Fatalf("%v: unhandled op %s in func %v", 346 ir.Line(n), n.Op().String(), ir.FuncName(ffa.fn)) 347 } 348 if debugTrace&debugTraceFuncFlags != 0 { 349 fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n", 350 ir.Line(n), n.Op().String(), st.String()) 351 } 352 ffa.setState(n, st) 353 } 354 355 func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) { 356 }