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