Source file src/cmd/compile/internal/deadlocals/deadlocals.go

     1  // Copyright 2024 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  // The deadlocals pass removes assignments to unused local variables.
     6  package deadlocals
     7  
     8  import (
     9  	"cmd/compile/internal/base"
    10  	"cmd/compile/internal/ir"
    11  	"cmd/compile/internal/types"
    12  	"cmd/internal/src"
    13  	"fmt"
    14  	"go/constant"
    15  )
    16  
    17  // Funcs applies the deadlocals pass to fns.
    18  func Funcs(fns []*ir.Func) {
    19  	if base.Flag.N != 0 || base.Debug.NoDeadLocals != 0 {
    20  		return
    21  	}
    22  
    23  	zero := ir.NewBasicLit(base.AutogeneratedPos, types.Types[types.TINT], constant.MakeInt64(0))
    24  
    25  	for _, fn := range fns {
    26  
    27  		if fn.IsClosure() {
    28  			if ir.MatchAstDump(fn, "deadlocals closure") {
    29  				ir.AstDump(fn, "deadlocals closure skipped, "+ir.FuncName(fn))
    30  			}
    31  			continue
    32  		}
    33  
    34  		v := newVisitor(fn)
    35  		v.nodes(fn.Body)
    36  
    37  		for _, k := range v.defsKeys {
    38  			assigns := v.defs[k]
    39  			for _, as := range assigns {
    40  				// Kludge for "missing func info" linker panic.
    41  				// See also closureInitLSym in inline/inl.go.
    42  				if clo, ok := (*as.rhs).(*ir.ClosureExpr); ok && clo.Op() == ir.OCLOSURE {
    43  					if clo.Func.IsClosure() {
    44  						ir.InitLSym(clo.Func, true)
    45  					}
    46  				}
    47  
    48  				*as.lhs = ir.BlankNode
    49  				*as.rhs = zero
    50  			}
    51  			if len(assigns) > 0 {
    52  				// k.Defn might be pointing at one of the
    53  				// assignments we're overwriting.
    54  				k.Defn = nil
    55  			}
    56  		}
    57  		if ir.MatchAstDump(fn, "deadlocals") {
    58  			ir.AstDump(fn, "deadLocals, "+ir.FuncName(fn))
    59  		}
    60  	}
    61  }
    62  
    63  type visitor struct {
    64  	curfn *ir.Func
    65  	// defs[name] contains assignments that can be discarded if name can be discarded.
    66  	// if defs[name] is defined nil, then name is actually used.
    67  	defs     map[*ir.Name][]assign
    68  	defsKeys []*ir.Name // insertion order of keys, for reproducible iteration (and builds)
    69  
    70  	doNode func(ir.Node) bool
    71  }
    72  
    73  type assign struct {
    74  	pos      src.XPos
    75  	lhs, rhs *ir.Node
    76  }
    77  
    78  func newVisitor(fn *ir.Func) *visitor {
    79  	v := &visitor{
    80  		curfn: fn,
    81  		defs:  make(map[*ir.Name][]assign),
    82  	}
    83  	v.doNode = func(n ir.Node) bool {
    84  		v.node(n)
    85  		return false
    86  	}
    87  	return v
    88  }
    89  
    90  func (v *visitor) node(n ir.Node) {
    91  	if n == nil {
    92  		return
    93  	}
    94  
    95  	switch n.Op() {
    96  	default:
    97  		ir.DoChildrenWithHidden(n, v.doNode)
    98  	case ir.OCLOSURE:
    99  		n := n.(*ir.ClosureExpr)
   100  		v.nodes(n.Init())
   101  		for _, cv := range n.Func.ClosureVars {
   102  			v.node(cv)
   103  		}
   104  		v.nodes(n.Func.Body)
   105  
   106  	case ir.ODCL:
   107  		// ignore
   108  	case ir.ONAME:
   109  		n := n.(*ir.Name)
   110  		n = n.Canonical()
   111  		if isLocal(n, false) {
   112  			// Force any lazy definitions.
   113  			s, ok := v.defs[n]
   114  			if !ok {
   115  				v.defsKeys = append(v.defsKeys, n)
   116  			}
   117  			v.defs[n] = nil
   118  			for _, as := range s {
   119  				// do the visit that was skipped in v.assign when as was appended to v.defs[n]
   120  				v.node(*as.rhs)
   121  			}
   122  		}
   123  
   124  	case ir.OAS:
   125  		n := n.(*ir.AssignStmt)
   126  		v.assign(n.Pos(), &n.X, &n.Y, false)
   127  	case ir.OAS2:
   128  		n := n.(*ir.AssignListStmt)
   129  
   130  		// If all LHS vars are blank, treat them as intentional
   131  		// uses of corresponding RHS vars.  If any are non-blank
   132  		// then any blanks are discards.
   133  		hasNonBlank := false
   134  		for i := range n.Lhs {
   135  			if !ir.IsBlank(n.Lhs[i]) {
   136  				hasNonBlank = true
   137  				break
   138  			}
   139  		}
   140  		for i := range n.Lhs {
   141  			v.assign(n.Pos(), &n.Lhs[i], &n.Rhs[i], hasNonBlank)
   142  		}
   143  	}
   144  }
   145  
   146  func (v *visitor) nodes(list ir.Nodes) {
   147  	for _, n := range list {
   148  		v.node(n)
   149  	}
   150  }
   151  
   152  func hasEffects(n ir.Node) bool {
   153  	if n == nil {
   154  		return false
   155  	}
   156  	if len(n.Init()) != 0 {
   157  		return true
   158  	}
   159  
   160  	switch n.Op() {
   161  	// TODO(mdempsky): More.
   162  	case ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OCLOSURE:
   163  		return false
   164  	}
   165  	return true
   166  }
   167  
   168  func (v *visitor) assign(pos src.XPos, lhs, rhs *ir.Node, blankIsNotUse bool) {
   169  	name, ok := (*lhs).(*ir.Name)
   170  	if !ok {
   171  		v.node(*lhs) // XXX: Interpret as variable, not value.
   172  		v.node(*rhs)
   173  		return
   174  	}
   175  	name = name.Canonical()
   176  
   177  	if isLocal(name, blankIsNotUse) && !hasEffects(*rhs) {
   178  		if s, ok := v.defs[name]; !ok || s != nil {
   179  			// !ok || s != nil is FALSE if previously "v.defs[name] = nil" -- that marks a use.
   180  			if !ok {
   181  				v.defsKeys = append(v.defsKeys, name)
   182  			}
   183  			v.defs[name] = append(s, assign{pos, lhs, rhs})
   184  			return // don't visit rhs unless that node ends up live, later.
   185  		}
   186  	}
   187  
   188  	v.node(*rhs)
   189  }
   190  
   191  func isLocal(n *ir.Name, blankIsNotUse bool) bool {
   192  	if ir.IsBlank(n) {
   193  		// Treat single assignments as intentional use (false), anything else is a discard (true).
   194  		return blankIsNotUse
   195  	}
   196  
   197  	switch n.Class {
   198  	case ir.PAUTO, ir.PPARAM:
   199  		return true
   200  	case ir.PPARAMOUT:
   201  		return false
   202  	case ir.PEXTERN, ir.PFUNC:
   203  		return false
   204  	}
   205  	panic(fmt.Sprintf("unexpected Class: %+v", n))
   206  }
   207  

View as plain text