...

Source file src/cmd/compile/internal/ir/reassignment.go

Documentation: cmd/compile/internal/ir

     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 ir
     6  
     7  import (
     8  	"cmd/compile/internal/base"
     9  	"cmd/compile/internal/types"
    10  )
    11  
    12  // A ReassignOracle efficiently answers queries about whether local
    13  // variables are reassigned. This helper works by looking for function
    14  // params and short variable declarations (e.g.
    15  // https://go.dev/ref/spec#Short_variable_declarations) that are
    16  // neither address taken nor subsequently re-assigned. It is intended
    17  // to operate much like "ir.StaticValue" and "ir.Reassigned", but in a
    18  // way that does just a single walk of the containing function (as
    19  // opposed to a new walk on every call).
    20  type ReassignOracle struct {
    21  	fn *Func
    22  	// maps candidate name to its defining assignment (or
    23  	// for params, defining func).
    24  	singleDef map[*Name]Node
    25  
    26  	// funcAssigns tracks all known simple assignments (OAS) to
    27  	// func-typed PAUTO variables. Only func-typed variables are
    28  	// tracked because this data is used exclusively for callee
    29  	// resolution in escape analysis. Deletion means the candidate was
    30  	// invalidated (e.g., addr-taken, non-simple assignment form, or too
    31  	// many assignments). Assignments inside nested closures are accepted
    32  	// because the only alternative value is nil, which panics on call.
    33  	funcAssigns map[*Name][]*AssignStmt
    34  }
    35  
    36  // Init initializes the oracle based on the IR in function fn, laying
    37  // the groundwork for future calls to the StaticValue and Reassigned
    38  // methods. If the fn's IR is subsequently modified, Init must be
    39  // called again.
    40  func (ro *ReassignOracle) Init(fn *Func) {
    41  	ro.fn = fn
    42  
    43  	// Collect candidate map. Start by adding function parameters
    44  	// explicitly.
    45  	ro.singleDef = make(map[*Name]Node)
    46  	ro.funcAssigns = make(map[*Name][]*AssignStmt)
    47  	sig := fn.Type()
    48  	numParams := sig.NumRecvs() + sig.NumParams()
    49  	for _, param := range fn.Dcl[:numParams] {
    50  		if IsBlank(param) {
    51  			continue
    52  		}
    53  		// For params, use func itself as defining node.
    54  		ro.singleDef[param] = fn
    55  	}
    56  
    57  	// Walk the function body to discover any locals assigned
    58  	// via ":=" syntax (e.g. "a := <expr>").
    59  	var findLocals func(n Node) bool
    60  	findLocals = func(n Node) bool {
    61  		if nn, ok := n.(*Name); ok {
    62  			if nn.Class == PAUTO && !nn.Addrtaken() {
    63  				isFunc := nn.Type().Kind() == types.TFUNC
    64  				if nn.Defn == nil {
    65  					// Bare declaration (e.g., "var f func()").
    66  					if isFunc {
    67  						ro.funcAssigns[nn] = nil
    68  					}
    69  				} else if _, ok := nn.Defn.(*AssignStmt); ok {
    70  					ro.singleDef[nn] = nn.Defn
    71  					if isFunc {
    72  						ro.funcAssigns[nn] = nil
    73  					}
    74  				} else {
    75  					ro.singleDef[nn] = nn.Defn
    76  				}
    77  			}
    78  		} else if nn, ok := n.(*ClosureExpr); ok {
    79  			Any(nn.Func, findLocals)
    80  		}
    81  		return false
    82  	}
    83  	Any(fn, findLocals)
    84  
    85  	outerName := func(x Node) *Name {
    86  		if x == nil {
    87  			return nil
    88  		}
    89  		n, ok := OuterValue(x).(*Name)
    90  		if ok {
    91  			return n.Canonical()
    92  		}
    93  		return nil
    94  	}
    95  
    96  	// pruneIfNeeded examines node nn appearing on the left hand side
    97  	// of assignment statement asn to see if it contains a reassignment
    98  	// to any nodes in our candidate maps; if a reassignment is found,
    99  	// the corresponding name is deleted.
   100  	pruneIfNeeded := func(nn Node, asn Node) {
   101  		oname := outerName(nn)
   102  		if oname == nil {
   103  			return
   104  		}
   105  		if defn, ok := ro.singleDef[oname]; ok {
   106  			// any assignment to a param invalidates the entry.
   107  			paramAssigned := oname.Class == PPARAM
   108  			// assignment to local ok iff assignment is its orig def.
   109  			localAssigned := (oname.Class == PAUTO && asn != defn)
   110  			if paramAssigned || localAssigned {
   111  				// We found an assignment to name N that doesn't
   112  				// correspond to its original definition; remove
   113  				// from candidates.
   114  				delete(ro.singleDef, oname)
   115  			}
   116  		}
   117  		if _, ok := ro.funcAssigns[oname]; ok {
   118  			as, isOAS := asn.(*AssignStmt)
   119  			if isOAS && isNilAssign(as) {
   120  				// Zero-value assignment (nil, bare decl), skip.
   121  			} else if !isOAS {
   122  				// Not a simple assignment: invalidate.
   123  				delete(ro.funcAssigns, oname)
   124  			} else {
   125  				ro.funcAssigns[oname] = append(ro.funcAssigns[oname], as)
   126  			}
   127  		}
   128  	}
   129  
   130  	// Prune away anything that looks assigned. This code modeled after
   131  	// similar code in ir.Reassigned; any changes there should be made
   132  	// here as well.
   133  	var do func(n Node) bool
   134  	do = func(n Node) bool {
   135  		switch n.Op() {
   136  		case OAS:
   137  			asn := n.(*AssignStmt)
   138  			pruneIfNeeded(asn.X, n)
   139  		case OAS2, OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV, OSELRECV2:
   140  			asn := n.(*AssignListStmt)
   141  			for _, p := range asn.Lhs {
   142  				pruneIfNeeded(p, n)
   143  			}
   144  		case OASOP:
   145  			asn := n.(*AssignOpStmt)
   146  			pruneIfNeeded(asn.X, n)
   147  		case ORANGE:
   148  			rs := n.(*RangeStmt)
   149  			pruneIfNeeded(rs.Key, n)
   150  			pruneIfNeeded(rs.Value, n)
   151  		case OCLOSURE:
   152  			n := n.(*ClosureExpr)
   153  			Any(n.Func, do)
   154  		}
   155  		return false
   156  	}
   157  	Any(fn, do)
   158  }
   159  
   160  // StaticValue method has the same semantics as the ir package function
   161  // of the same name; see comments on [StaticValue].
   162  func (ro *ReassignOracle) StaticValue(n Node) Node {
   163  	arg := n
   164  	for {
   165  		if n.Op() == OCONVNOP {
   166  			n = n.(*ConvExpr).X
   167  			continue
   168  		}
   169  
   170  		if n.Op() == OINLCALL {
   171  			n = n.(*InlinedCallExpr).SingleResult()
   172  			continue
   173  		}
   174  
   175  		if n.Op() == OPAREN {
   176  			n = n.(*ParenExpr).X
   177  			continue
   178  		}
   179  
   180  		n1 := ro.staticValue1(n)
   181  		if n1 == nil {
   182  			if consistencyCheckEnabled {
   183  				checkStaticValueResult(arg, n)
   184  			}
   185  			return n
   186  		}
   187  		n = n1
   188  	}
   189  }
   190  
   191  func (ro *ReassignOracle) staticValue1(nn Node) Node {
   192  	if nn.Op() != ONAME {
   193  		return nil
   194  	}
   195  	n := nn.(*Name).Canonical()
   196  	if n.Class != PAUTO {
   197  		return nil
   198  	}
   199  
   200  	defn := n.Defn
   201  	if defn == nil {
   202  		return nil
   203  	}
   204  
   205  	var rhs Node
   206  FindRHS:
   207  	switch defn.Op() {
   208  	case OAS:
   209  		defn := defn.(*AssignStmt)
   210  		rhs = defn.Y
   211  	case OAS2:
   212  		defn := defn.(*AssignListStmt)
   213  		for i, lhs := range defn.Lhs {
   214  			if lhs == n {
   215  				rhs = defn.Rhs[i]
   216  				break FindRHS
   217  			}
   218  		}
   219  		base.FatalfAt(defn.Pos(), "%v missing from LHS of %v", n, defn)
   220  	default:
   221  		return nil
   222  	}
   223  	if rhs == nil {
   224  		base.FatalfAt(defn.Pos(), "RHS is nil: %v", defn)
   225  	}
   226  
   227  	if _, ok := ro.singleDef[n]; !ok {
   228  		return nil
   229  	}
   230  
   231  	return rhs
   232  }
   233  
   234  // Reassigned method has the same semantics as the ir package function
   235  // of the same name; see comments on [Reassigned] for more info.
   236  func (ro *ReassignOracle) Reassigned(n *Name) bool {
   237  	_, ok := ro.singleDef[n]
   238  	result := !ok
   239  	if consistencyCheckEnabled {
   240  		checkReassignedResult(n, result)
   241  	}
   242  	return result
   243  }
   244  
   245  // FuncAssignments returns all known simple assignments to a func-typed
   246  // variable. For variables defined with := and a non-zero value, the
   247  // defining assignment is included. Returns nil if the variable is not
   248  // func-typed, was invalidated (addr-taken, non-simple assignment,
   249  // too many assignments), or has no tracked assignments. Assignments
   250  // inside nested closures are accepted because the only alternative
   251  // value is nil, which panics on call.
   252  func (ro *ReassignOracle) FuncAssignments(name *Name) []*AssignStmt {
   253  	return ro.funcAssigns[name.Canonical()]
   254  }
   255  
   256  // isNilAssign reports whether as has a nil or absent RHS.
   257  func isNilAssign(as *AssignStmt) bool {
   258  	if as.Y == nil {
   259  		return true
   260  	}
   261  	y := as.Y
   262  	for y.Op() == OCONVNOP {
   263  		y = y.(*ConvExpr).X
   264  	}
   265  	return IsNil(y)
   266  }
   267  

View as plain text