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  }