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

View as plain text