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

View as plain text