MergeLocalsTrace int `help:"trace debug output for locals merging"`
MergeLocalsHTrace int `help:"hash-selected trace debug output for locals merging"`
Nil int `help:"print information about nil checks"`
+ NoDeadLocals int `help:"disable deadlocals pass" concurrent:"ok"`
NoOpenDefer int `help:"disable open-coded defers" concurrent:"ok"`
NoRefName int `help:"do not include referenced symbol names in object file" concurrent:"ok"`
PCTab string `help:"print named pc-value table\nOne of: pctospadj, pctofile, pctoline, pctoinline, pctopcdata"`
--- /dev/null
+// 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 _, assigns := range v.defs {
+ 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 !ir.IsTrivialClosure(clo) {
+ 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
+
+ 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 := v.defs[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.
+ 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))
+}
"bytes"
"cmd/compile/internal/base"
"cmd/compile/internal/coverage"
+ "cmd/compile/internal/deadlocals"
"cmd/compile/internal/dwarfgen"
"cmd/compile/internal/escape"
"cmd/compile/internal/inline"
// and doesn't benefit from dead-coding or inlining.
symABIs.GenABIWrappers()
+ deadlocals.Funcs(typecheck.Target.Funcs)
+
// Escape analysis.
// Required for moving heap allocations onto stack,
// which in turn is required by the closure implementation,
}
// emptyFunc should not be inlined.
+//
+//go:noinline
func emptyFunc(x int) {
if false {
fmt.Println(x)
// others.
const n = 2
done := make(chan bool, n)
- empty := func(p *int) {}
+ empty := func(p *int) { _ = p }
for i := 0; i < n; i++ {
ms := i
go func() {
func TestRaceFuncCall(t *testing.T) {
c := make(chan bool, 1)
- f := func(x, y int) {}
+ f := func(x, y int) { _ = y }
x, y := 0, 0
go func() {
y = 42
x := 0
go func() {
func(x int) {
+ _ = x
}(x)
c <- true
}()
x := 0
go func() {
func(x int) {
+ _ = x
mu.Lock()
}(x) // Read of x must be outside of the mutex.
mu.Unlock()
if x := func() int { // ERROR "can inline main.func2" "func literal does not escape"
return 1
}; x() != 1 { // ERROR "inlining call to main.func2"
+ _ = x // prevent simple deadcode elimination after inlining
ppanic("x() != 1")
}
}
if y := func(x int) int { // ERROR "can inline main.func4" "func literal does not escape"
return x + 2
}; y(40) != 42 { // ERROR "inlining call to main.func4"
+ _ = y // prevent simple deadcode elimination after inlining
ppanic("y(40) != 42")
}
}
if y := func() int { // ERROR "can inline main.func21" "func literal does not escape"
return x
}; y() != 42 { // ERROR "inlining call to main.func21"
+ _ = y // prevent simple deadcode elimination after inlining
ppanic("y() != 42")
}
}
return x + y
}() // ERROR "inlining call to main.func23.1"
}; z(1) != 43 { // ERROR "inlining call to main.func23" "inlining call to main.main.func23.func31"
+ _ = z // prevent simple deadcode elimination after inlining
ppanic("z(1) != 43")
}
}
}
}
+//go:noinline
+func notmain() {
+ {
+ // This duplicates the first block in main, but without the "_ = x" for closure x.
+ // This allows dead code elimination of x before escape analysis,
+ // thus "func literal does not escape" should not appear.
+ if x := func() int { // ERROR "can inline notmain.func1"
+ return 1
+ }(); x != 1 { // ERROR "inlining call to notmain.func1"
+ ppanic("x != 1")
+ }
+ if x := func() int { // ERROR "can inline notmain.func2"
+ return 1
+ }; x() != 1 { // ERROR "inlining call to notmain.func2"
+ ppanic("x() != 1")
+ }
+ }
+}
+
//go:noinline
func ppanic(s string) { // ERROR "leaking param: s"
panic(s) // ERROR "s escapes to heap"
g() // ERROR "inlining call to g"
}
f() // ERROR "inlining call to run.func1" "inlining call to g"
+ _ = f
run()
}
f := e
f(nil) // ERROR "inlining call to l.func1"
}
+ _ = e // prevent simple deadcode elimination after inlining
return y, x, nil
}
func q(x int) int { // ERROR "can inline q"
foo := func() int { return x * 2 } // ERROR "can inline q.func1" "func literal does not escape"
+ _ = foo // prevent simple deadcode elimination after inlining
return foo() // ERROR "inlining call to q.func1"
}
return 2*y + x*z
}(x) // ERROR "inlining call to r.func2.1"
}
+ _, _ = foo, bar // prevent simple deadcode elimination after inlining
+
return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2" "inlining call to r.r.func2.func3"
}
foo := func() { // ERROR "can inline s0.func1" "func literal does not escape"
x = x + 1
}
- foo() // ERROR "inlining call to s0.func1"
+ foo() // ERROR "inlining call to s0.func1"
+ _ = foo // prevent simple deadcode elimination after inlining
return x
}
return x
}
x = x + 1
+ _ = foo // prevent simple deadcode elimination after inlining
return foo() // ERROR "inlining call to s1.func1"
}
f := e
f(nil) // ERROR "inlining call to l.func1"
}
+ _ = e // prevent simple deadcode elimination
return y, x, nil
}
func q(x int) int { // ERROR "can inline q"
foo := func() int { return x * 2 } // ERROR "can inline q.func1" "func literal does not escape"
+ _ = foo // prevent simple deadcode elimination
return foo() // ERROR "inlining call to q.func1"
}
return 2*y + x*z
}(x) // ERROR "inlining call to r.func2.1"
}
+ _ = foo // prevent simple deadcode elimination
+ _ = bar // prevent simple deadcode elimination
return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2" "inlining call to r.r.func2.func3"
}
foo := func() { // ERROR "can inline s0.func1" "func literal does not escape"
x = x + 1
}
- foo() // ERROR "inlining call to s0.func1"
+ foo() // ERROR "inlining call to s0.func1"
+ _ = foo // prevent simple deadcode elimination
return x
}
return x
}
x = x + 1
+ _ = foo // prevent simple deadcode elimination
return foo() // ERROR "inlining call to s1.func1"
}