blob: 238450416a94638aae120ac1fb9e7537fcbda2ea [file] [log] [blame] [edit]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The deadlocals pass removes assignments to unused local variables.
package deadlocals
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/types"
"cmd/internal/src"
"fmt"
"go/constant"
)
// Funcs applies the deadlocals pass to fns.
func Funcs(fns []*ir.Func) {
if base.Flag.N != 0 || base.Debug.NoDeadLocals != 0 {
return
}
zero := ir.NewBasicLit(base.AutogeneratedPos, types.Types[types.TINT], constant.MakeInt64(0))
for _, fn := range fns {
if fn.IsClosure() {
continue
}
v := newVisitor(fn)
v.nodes(fn.Body)
for _, k := range v.defsKeys {
assigns := v.defs[k]
for _, as := range assigns {
// Kludge for "missing func info" linker panic.
// See also closureInitLSym in inline/inl.go.
if clo, ok := (*as.rhs).(*ir.ClosureExpr); ok && clo.Op() == ir.OCLOSURE {
if clo.Func.IsClosure() {
ir.InitLSym(clo.Func, true)
}
}
*as.lhs = ir.BlankNode
*as.rhs = zero
}
}
}
}
type visitor struct {
curfn *ir.Func
// defs[name] contains assignments that can be discarded if name can be discarded.
// if defs[name] is defined nil, then name is actually used.
defs map[*ir.Name][]assign
defsKeys []*ir.Name // insertion order of keys, for reproducible iteration (and builds)
doNode func(ir.Node) bool
}
type assign struct {
pos src.XPos
lhs, rhs *ir.Node
}
func newVisitor(fn *ir.Func) *visitor {
v := &visitor{
curfn: fn,
defs: make(map[*ir.Name][]assign),
}
v.doNode = func(n ir.Node) bool {
v.node(n)
return false
}
return v
}
func (v *visitor) node(n ir.Node) {
if n == nil {
return
}
switch n.Op() {
default:
ir.DoChildrenWithHidden(n, v.doNode)
case ir.OCLOSURE:
n := n.(*ir.ClosureExpr)
v.nodes(n.Init())
for _, cv := range n.Func.ClosureVars {
v.node(cv)
}
v.nodes(n.Func.Body)
case ir.ODCL:
// ignore
case ir.ONAME:
n := n.(*ir.Name)
n = n.Canonical()
if isLocal(n, false) {
// Force any lazy definitions.
s, ok := v.defs[n]
if !ok {
v.defsKeys = append(v.defsKeys, n)
}
v.defs[n] = nil
for _, as := range s {
// do the visit that was skipped in v.assign when as was appended to v.defs[n]
v.node(*as.rhs)
}
}
case ir.OAS:
n := n.(*ir.AssignStmt)
v.assign(n.Pos(), &n.X, &n.Y, false)
case ir.OAS2:
n := n.(*ir.AssignListStmt)
// If all LHS vars are blank, treat them as intentional
// uses of corresponding RHS vars. If any are non-blank
// then any blanks are discards.
hasNonBlank := false
for i := range n.Lhs {
if !ir.IsBlank(n.Lhs[i]) {
hasNonBlank = true
break
}
}
for i := range n.Lhs {
v.assign(n.Pos(), &n.Lhs[i], &n.Rhs[i], hasNonBlank)
}
}
}
func (v *visitor) nodes(list ir.Nodes) {
for _, n := range list {
v.node(n)
}
}
func hasEffects(n ir.Node) bool {
if n == nil {
return false
}
if len(n.Init()) != 0 {
return true
}
switch n.Op() {
// TODO(mdempsky): More.
case ir.ONAME, ir.OLITERAL, ir.ONIL, ir.OCLOSURE:
return false
}
return true
}
func (v *visitor) assign(pos src.XPos, lhs, rhs *ir.Node, blankIsNotUse bool) {
name, ok := (*lhs).(*ir.Name)
if !ok {
v.node(*lhs) // XXX: Interpret as variable, not value.
v.node(*rhs)
return
}
name = name.Canonical()
if isLocal(name, blankIsNotUse) && !hasEffects(*rhs) {
if s, ok := v.defs[name]; !ok || s != nil {
// !ok || s != nil is FALSE if previously "v.defs[name] = nil" -- that marks a use.
if !ok {
v.defsKeys = append(v.defsKeys, name)
}
v.defs[name] = append(s, assign{pos, lhs, rhs})
return // don't visit rhs unless that node ends up live, later.
}
}
v.node(*rhs)
}
func isLocal(n *ir.Name, blankIsNotUse bool) bool {
if ir.IsBlank(n) {
// Treat single assignments as intentional use (false), anything else is a discard (true).
return blankIsNotUse
}
switch n.Class {
case ir.PAUTO, ir.PPARAM:
return true
case ir.PPARAMOUT:
return false
case ir.PEXTERN, ir.PFUNC:
return false
}
panic(fmt.Sprintf("unexpected Class: %+v", n))
}