| // 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)) |
| } |