]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: move early deadcode into unified writer
authorMatthew Dempsky <mdempsky@google.com>
Wed, 9 Aug 2023 11:05:35 +0000 (04:05 -0700)
committerGopher Robot <gobot@golang.org>
Fri, 11 Aug 2023 18:03:52 +0000 (18:03 +0000)
This CL moves the early deadcode elimination pass into the unified
writer. This allows shrinking the export data, by simplifying
expressions and removing unreachable statements. It also means we
don't need to repeatedly apply deadcode elimination on inlined calls
or instantiated generics.

Change-Id: I19bdb04861e50815fccdab39790f4aaa076121fd
Reviewed-on: https://go-review.googlesource.com/c/go/+/517775
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/compile/internal/deadcode/deadcode.go [deleted file]
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/noder/reader.go
src/cmd/compile/internal/noder/writer.go

diff --git a/src/cmd/compile/internal/deadcode/deadcode.go b/src/cmd/compile/internal/deadcode/deadcode.go
deleted file mode 100644 (file)
index 46a2239..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright 2009 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.
-
-package deadcode
-
-import (
-       "go/constant"
-       "go/token"
-
-       "cmd/compile/internal/base"
-       "cmd/compile/internal/ir"
-)
-
-func Func(fn *ir.Func) {
-       stmts(&fn.Body)
-
-       if len(fn.Body) == 0 {
-               return
-       }
-
-       for _, n := range fn.Body {
-               if len(n.Init()) > 0 {
-                       return
-               }
-               switch n.Op() {
-               case ir.OIF:
-                       n := n.(*ir.IfStmt)
-                       if !ir.IsConst(n.Cond, constant.Bool) || len(n.Body) > 0 || len(n.Else) > 0 {
-                               return
-                       }
-               case ir.OFOR:
-                       n := n.(*ir.ForStmt)
-                       if !ir.IsConst(n.Cond, constant.Bool) || ir.BoolVal(n.Cond) {
-                               return
-                       }
-               default:
-                       return
-               }
-       }
-
-       ir.VisitList(fn.Body, markHiddenClosureDead)
-       fn.Body = []ir.Node{ir.NewBlockStmt(base.Pos, nil)}
-}
-
-func stmts(nn *ir.Nodes) {
-       var lastLabel = -1
-       for i, n := range *nn {
-               if n != nil && n.Op() == ir.OLABEL {
-                       lastLabel = i
-               }
-       }
-       for i, n := range *nn {
-               // Cut is set to true when all nodes after i'th position
-               // should be removed.
-               // In other words, it marks whole slice "tail" as dead.
-               cut := false
-               if n == nil {
-                       continue
-               }
-               if n.Op() == ir.OIF {
-                       n := n.(*ir.IfStmt)
-                       n.Cond = expr(n.Cond)
-                       if ir.IsConst(n.Cond, constant.Bool) {
-                               var body ir.Nodes
-                               if ir.BoolVal(n.Cond) {
-                                       ir.VisitList(n.Else, markHiddenClosureDead)
-                                       n.Else = ir.Nodes{}
-                                       body = n.Body
-                               } else {
-                                       ir.VisitList(n.Body, markHiddenClosureDead)
-                                       n.Body = ir.Nodes{}
-                                       body = n.Else
-                               }
-                               // If "then" or "else" branch ends with panic or return statement,
-                               // it is safe to remove all statements after this node.
-                               // isterminating is not used to avoid goto-related complications.
-                               // We must be careful not to deadcode-remove labels, as they
-                               // might be the target of a goto. See issue 28616.
-                               if body := body; len(body) != 0 {
-                                       switch body[(len(body) - 1)].Op() {
-                                       case ir.ORETURN, ir.OTAILCALL, ir.OPANIC:
-                                               if i > lastLabel {
-                                                       cut = true
-                                               }
-                                       }
-                               }
-                       }
-               }
-               if n.Op() == ir.OSWITCH {
-                       n := n.(*ir.SwitchStmt)
-                       // Use a closure wrapper here so we can use "return" to abort the analysis.
-                       func() {
-                               if n.Tag != nil && n.Tag.Op() == ir.OTYPESW {
-                                       return // no special type-switch case yet.
-                               }
-                               var x constant.Value // value we're switching on
-                               if n.Tag != nil {
-                                       if ir.ConstType(n.Tag) == constant.Unknown {
-                                               return
-                                       }
-                                       x = n.Tag.Val()
-                               } else {
-                                       x = constant.MakeBool(true) // switch { ... }  =>  switch true { ... }
-                               }
-                               var def *ir.CaseClause
-                               for _, cas := range n.Cases {
-                                       if len(cas.List) == 0 { // default case
-                                               def = cas
-                                               continue
-                                       }
-                                       for _, c := range cas.List {
-                                               if ir.ConstType(c) == constant.Unknown {
-                                                       return // can't statically tell if it matches or not - give up.
-                                               }
-                                               if constant.Compare(x, token.EQL, c.Val()) {
-                                                       for _, n := range cas.Body {
-                                                               if n.Op() == ir.OFALL {
-                                                                       return // fallthrough makes it complicated - abort.
-                                                               }
-                                                       }
-                                                       // This switch entry is the one that always triggers.
-                                                       for _, cas2 := range n.Cases {
-                                                               for _, c2 := range cas2.List {
-                                                                       ir.Visit(c2, markHiddenClosureDead)
-                                                               }
-                                                               if cas2 != cas {
-                                                                       ir.VisitList(cas2.Body, markHiddenClosureDead)
-                                                               }
-                                                       }
-
-                                                       // Rewrite to switch { case true: ... }
-                                                       n.Tag = nil
-                                                       cas.List[0] = ir.NewBool(c.Pos(), true)
-                                                       cas.List = cas.List[:1]
-                                                       n.Cases[0] = cas
-                                                       n.Cases = n.Cases[:1]
-                                                       return
-                                               }
-                                       }
-                               }
-                               if def != nil {
-                                       for _, n := range def.Body {
-                                               if n.Op() == ir.OFALL {
-                                                       return // fallthrough makes it complicated - abort.
-                                               }
-                                       }
-                                       for _, cas := range n.Cases {
-                                               if cas != def {
-                                                       ir.VisitList(cas.List, markHiddenClosureDead)
-                                                       ir.VisitList(cas.Body, markHiddenClosureDead)
-                                               }
-                                       }
-                                       n.Cases[0] = def
-                                       n.Cases = n.Cases[:1]
-                                       return
-                               }
-
-                               // TODO: handle case bodies ending with panic/return as we do in the IF case above.
-
-                               // entire switch is a nop - no case ever triggers
-                               for _, cas := range n.Cases {
-                                       ir.VisitList(cas.List, markHiddenClosureDead)
-                                       ir.VisitList(cas.Body, markHiddenClosureDead)
-                               }
-                               n.Cases = n.Cases[:0]
-                       }()
-               }
-
-               if len(n.Init()) != 0 {
-                       stmts(n.(ir.InitNode).PtrInit())
-               }
-               switch n.Op() {
-               case ir.OBLOCK:
-                       n := n.(*ir.BlockStmt)
-                       stmts(&n.List)
-               case ir.OFOR:
-                       n := n.(*ir.ForStmt)
-                       stmts(&n.Body)
-               case ir.OIF:
-                       n := n.(*ir.IfStmt)
-                       stmts(&n.Body)
-                       stmts(&n.Else)
-               case ir.ORANGE:
-                       n := n.(*ir.RangeStmt)
-                       stmts(&n.Body)
-               case ir.OSELECT:
-                       n := n.(*ir.SelectStmt)
-                       for _, cas := range n.Cases {
-                               stmts(&cas.Body)
-                       }
-               case ir.OSWITCH:
-                       n := n.(*ir.SwitchStmt)
-                       for _, cas := range n.Cases {
-                               stmts(&cas.Body)
-                       }
-               }
-
-               if cut {
-                       ir.VisitList((*nn)[i+1:len(*nn)], markHiddenClosureDead)
-                       *nn = (*nn)[:i+1]
-                       break
-               }
-       }
-}
-
-func expr(n ir.Node) ir.Node {
-       // Perform dead-code elimination on short-circuited boolean
-       // expressions involving constants with the intent of
-       // producing a constant 'if' condition.
-       switch n.Op() {
-       case ir.OANDAND:
-               n := n.(*ir.LogicalExpr)
-               n.X = expr(n.X)
-               n.Y = expr(n.Y)
-               if ir.IsConst(n.X, constant.Bool) {
-                       if ir.BoolVal(n.X) {
-                               return n.Y // true && x => x
-                       } else {
-                               return n.X // false && x => false
-                       }
-               }
-       case ir.OOROR:
-               n := n.(*ir.LogicalExpr)
-               n.X = expr(n.X)
-               n.Y = expr(n.Y)
-               if ir.IsConst(n.X, constant.Bool) {
-                       if ir.BoolVal(n.X) {
-                               return n.X // true || x => true
-                       } else {
-                               return n.Y // false || x => x
-                       }
-               }
-       }
-       return n
-}
-
-func markHiddenClosureDead(n ir.Node) {
-       if n.Op() != ir.OCLOSURE {
-               return
-       }
-       clo := n.(*ir.ClosureExpr)
-       if clo.Func.IsHiddenClosure() {
-               clo.Func.SetIsDeadcodeClosure(true)
-       }
-       ir.VisitList(clo.Func.Body, markHiddenClosureDead)
-}
index 937d1c4751f36576909696bc1902359092d07a85..e9af8aa325234482359ff7ce96e23f3343a70eb9 100644 (file)
@@ -9,7 +9,6 @@ import (
        "bytes"
        "cmd/compile/internal/base"
        "cmd/compile/internal/coverage"
-       "cmd/compile/internal/deadcode"
        "cmd/compile/internal/devirtualize"
        "cmd/compile/internal/dwarfgen"
        "cmd/compile/internal/escape"
@@ -220,11 +219,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
 
        // Create "init" function for package-scope variable initialization
        // statements, if any.
-       //
-       // Note: This needs to happen early, before any optimizations. The
-       // Go spec defines a precise order than initialization should be
-       // carried out in, and even mundane optimizations like dead code
-       // removal can skew the results (e.g., #43444).
        pkginit.MakeInit()
 
        // Second part of code coverage fixup (init func modification),
@@ -233,14 +227,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
                coverage.FixupInit(cnames)
        }
 
-       // Eliminate some obviously dead code.
-       // Must happen after typechecking.
-       for _, n := range typecheck.Target.Decls {
-               if n.Op() == ir.ODCLFUNC {
-                       deadcode.Func(n.(*ir.Func))
-               }
-       }
-
        // Compute Addrtaken for names.
        // We need to wait until typechecking is done so that when we see &x[i]
        // we know that x has its address taken if x is an array, but not if x is a slice.
index f63040ae13de37e148ee3026a333d0b4bfe50b39..a92a8904376c264287df2ff1d990a9070ad3f58f 100644 (file)
@@ -13,7 +13,6 @@ import (
        "strings"
 
        "cmd/compile/internal/base"
-       "cmd/compile/internal/deadcode"
        "cmd/compile/internal/dwarfgen"
        "cmd/compile/internal/inline"
        "cmd/compile/internal/ir"
@@ -1900,6 +1899,10 @@ func (r *reader) forStmt(label *types.Sym) ir.Node {
        perLoopVars := r.Bool()
        r.closeAnotherScope()
 
+       if ir.IsConst(cond, constant.Bool) && !ir.BoolVal(cond) {
+               return init // simplify "for init; false; post { ... }" into "init"
+       }
+
        stmt := ir.NewForStmt(pos, init, cond, post, body, perLoopVars)
        stmt.Label = label
        return stmt
@@ -1913,9 +1916,14 @@ func (r *reader) ifStmt() ir.Node {
        cond := r.expr()
        then := r.blockStmt()
        els := r.stmts()
+       r.closeAnotherScope()
+
+       if ir.IsConst(cond, constant.Bool) && len(init)+len(then)+len(els) == 0 {
+               return nil // drop empty if statement
+       }
+
        n := ir.NewIfStmt(pos, cond, then, els)
        n.SetInit(init)
-       r.closeAnotherScope()
        return n
 }
 
@@ -3531,8 +3539,6 @@ func unifiedInlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.Inlined
        // Note issue 28603.
        init.Append(ir.NewInlineMarkStmt(call.Pos().WithIsStmt(), int64(r.inlTreeIndex)))
 
-       nparams := len(r.curfn.Dcl)
-
        ir.WithFunc(r.curfn, func() {
                if !r.syntheticBody(call.Pos()) {
                        assert(r.Bool()) // have body
@@ -3548,8 +3554,6 @@ func unifiedInlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.Inlined
                // themselves. But currently it's an easy fix to #50552.
                readBodies(typecheck.Target, true)
 
-               deadcode.Func(r.curfn)
-
                // Replace any "return" statements within the function body.
                var edit func(ir.Node) ir.Node
                edit = func(n ir.Node) ir.Node {
@@ -3564,22 +3568,14 @@ func unifiedInlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.Inlined
 
        body := ir.Nodes(r.curfn.Body)
 
-       // Quirkish: We need to eagerly prune variables added during
-       // inlining, but removed by deadcode.FuncBody above. Unused
-       // variables will get removed during stack frame layout anyway, but
-       // len(fn.Dcl) ends up influencing things like autotmp naming.
-
-       used := usedLocals(body)
-
-       for i, name := range r.curfn.Dcl {
-               if i < nparams || used.Has(name) {
-                       name.Curfn = callerfn
-                       callerfn.Dcl = append(callerfn.Dcl, name)
+       // Reparent any declarations into the caller function.
+       for _, name := range r.curfn.Dcl {
+               name.Curfn = callerfn
+               callerfn.Dcl = append(callerfn.Dcl, name)
 
-                       if name.AutoTemp() {
-                               name.SetEsc(ir.EscUnknown)
-                               name.SetInlLocal(true)
-                       }
+               if name.AutoTemp() {
+                       name.SetEsc(ir.EscUnknown)
+                       name.SetInlLocal(true)
                }
        }
 
@@ -3647,10 +3643,6 @@ func expandInline(fn *ir.Func, pri pkgReaderIndex) {
                r.funarghack = true
 
                r.funcBody(tmpfn)
-
-               ir.WithFunc(tmpfn, func() {
-                       deadcode.Func(tmpfn)
-               })
        }
 
        used := usedLocals(tmpfn.Body)
index 77708245ae90318b8778a4c02dceb350f56bd1e9..f5b83c64020798b1dd8657b76be9f5b8c41524d9 100644 (file)
@@ -6,6 +6,8 @@ package noder
 
 import (
        "fmt"
+       "go/constant"
+       "go/token"
        "internal/buildcfg"
        "internal/pkgbits"
 
@@ -1206,9 +1208,18 @@ func (w *writer) stmt(stmt syntax.Stmt) {
 }
 
 func (w *writer) stmts(stmts []syntax.Stmt) {
+       dead := false
        w.Sync(pkgbits.SyncStmts)
        for _, stmt := range stmts {
+               if dead {
+                       // Any statements after a terminating statement are safe to
+                       // omit, at least until the next labeled statement.
+                       if _, ok := stmt.(*syntax.LabeledStmt); !ok {
+                               continue
+                       }
+               }
                w.stmt1(stmt)
+               dead = w.p.terminates(stmt)
        }
        w.Code(stmtEnd)
        w.Sync(pkgbits.SyncStmtsEnd)
@@ -1449,6 +1460,11 @@ func (w *writer) forStmt(stmt *syntax.ForStmt) {
                }
 
        } else {
+               if stmt.Cond != nil && w.p.staticBool(&stmt.Cond) < 0 { // always false
+                       stmt.Post = nil
+                       stmt.Body.List = nil
+               }
+
                w.pos(stmt)
                w.stmt(stmt.Init)
                w.optExpr(stmt.Cond)
@@ -1504,6 +1520,13 @@ func (pw *pkgWriter) rangeTypes(expr syntax.Expr) (key, value types2.Type) {
 }
 
 func (w *writer) ifStmt(stmt *syntax.IfStmt) {
+       switch cond := w.p.staticBool(&stmt.Cond); {
+       case cond > 0: // always true
+               stmt.Else = nil
+       case cond < 0: // always false
+               stmt.Then.List = nil
+       }
+
        w.Sync(pkgbits.SyncIfStmt)
        w.openScope(stmt.Pos())
        w.pos(stmt)
@@ -1558,10 +1581,56 @@ func (w *writer) switchStmt(stmt *syntax.SwitchStmt) {
        } else {
                tag := stmt.Tag
 
+               var tagValue constant.Value
                if tag != nil {
-                       tagType = w.p.typeOf(tag)
+                       tv := w.p.typeAndValue(tag)
+                       tagType = tv.Type
+                       tagValue = tv.Value
                } else {
                        tagType = types2.Typ[types2.Bool]
+                       tagValue = constant.MakeBool(true)
+               }
+
+               if tagValue != nil {
+                       // If the switch tag has a constant value, look for a case
+                       // clause that we always branch to.
+                       func() {
+                               var target *syntax.CaseClause
+                       Outer:
+                               for _, clause := range stmt.Body {
+                                       if clause.Cases == nil {
+                                               target = clause
+                                       }
+                                       for _, cas := range unpackListExpr(clause.Cases) {
+                                               tv := w.p.typeAndValue(cas)
+                                               if tv.Value == nil {
+                                                       return // non-constant case; give up
+                                               }
+                                               if constant.Compare(tagValue, token.EQL, tv.Value) {
+                                                       target = clause
+                                                       break Outer
+                                               }
+                                       }
+                               }
+                               // We've found the target clause, if any.
+
+                               if target != nil {
+                                       if hasFallthrough(target.Body) {
+                                               return // fallthrough is tricky; give up
+                                       }
+
+                                       // Rewrite as single "default" case.
+                                       target.Cases = nil
+                                       stmt.Body = []*syntax.CaseClause{target}
+                               } else {
+                                       stmt.Body = nil
+                               }
+
+                               // Clear switch tag (i.e., replace with implicit "true").
+                               tag = nil
+                               stmt.Tag = nil
+                               tagType = types2.Typ[types2.Bool]
+                       }()
                }
 
                // Walk is going to emit comparisons between the tag value and
@@ -2629,6 +2698,61 @@ func (w *writer) pkgObjs(names ...*syntax.Name) {
 
 // @@@ Helpers
 
+// staticBool analyzes a boolean expression and reports whether it's
+// always true (positive result), always false (negative result), or
+// unknown (zero).
+//
+// It also simplifies the expression while preserving semantics, if
+// possible.
+func (pw *pkgWriter) staticBool(ep *syntax.Expr) int {
+       if val := pw.typeAndValue(*ep).Value; val != nil {
+               if constant.BoolVal(val) {
+                       return +1
+               } else {
+                       return -1
+               }
+       }
+
+       if e, ok := (*ep).(*syntax.Operation); ok {
+               switch e.Op {
+               case syntax.Not:
+                       return pw.staticBool(&e.X)
+
+               case syntax.AndAnd:
+                       x := pw.staticBool(&e.X)
+                       if x < 0 {
+                               *ep = e.X
+                               return x
+                       }
+
+                       y := pw.staticBool(&e.Y)
+                       if x > 0 || y < 0 {
+                               if pw.typeAndValue(e.X).Value != nil {
+                                       *ep = e.Y
+                               }
+                               return y
+                       }
+
+               case syntax.OrOr:
+                       x := pw.staticBool(&e.X)
+                       if x > 0 {
+                               *ep = e.X
+                               return x
+                       }
+
+                       y := pw.staticBool(&e.Y)
+                       if x < 0 || y > 0 {
+                               if pw.typeAndValue(e.X).Value != nil {
+                                       *ep = e.Y
+                               }
+                               return y
+                       }
+               }
+       }
+
+       return 0
+}
+
 // hasImplicitTypeParams reports whether obj is a defined type with
 // implicit type parameters (e.g., declared within a generic function
 // or method).
@@ -2706,6 +2830,15 @@ func isNil(p *pkgWriter, expr syntax.Expr) bool {
        return tv.IsNil()
 }
 
+// isBuiltin reports whether expr is a (possibly parenthesized)
+// referenced to the specified built-in function.
+func (p *pkgWriter) isBuiltin(expr syntax.Expr, builtin string) bool {
+       if name, ok := unparen(expr).(*syntax.Name); ok && name.Value == builtin {
+               return p.typeAndValue(name).IsBuiltin()
+       }
+       return false
+}
+
 // recvBase returns the base type for the given receiver parameter.
 func recvBase(recv *types2.Var) *types2.Named {
        typ := recv.Type()
@@ -2789,3 +2922,56 @@ func isPtrTo(from, to types2.Type) bool {
        ptr, ok := from.(*types2.Pointer)
        return ok && types2.Identical(ptr.Elem(), to)
 }
+
+// hasFallthrough reports whether stmts ends in a fallthrough
+// statement.
+func hasFallthrough(stmts []syntax.Stmt) bool {
+       last, ok := lastNonEmptyStmt(stmts).(*syntax.BranchStmt)
+       return ok && last.Tok == syntax.Fallthrough
+}
+
+// lastNonEmptyStmt returns the last non-empty statement in list, if
+// any.
+func lastNonEmptyStmt(stmts []syntax.Stmt) syntax.Stmt {
+       for i := len(stmts) - 1; i >= 0; i-- {
+               stmt := stmts[i]
+               if _, ok := stmt.(*syntax.EmptyStmt); !ok {
+                       return stmt
+               }
+       }
+       return nil
+}
+
+// terminates reports whether stmt terminates normal control flow
+// (i.e., does not merely advance to the following statement).
+func (p *pkgWriter) terminates(stmt syntax.Stmt) bool {
+       switch stmt := stmt.(type) {
+       case *syntax.BranchStmt:
+               if stmt.Tok == syntax.Goto {
+                       return true
+               }
+       case *syntax.ReturnStmt:
+               return true
+       case *syntax.ExprStmt:
+               if call, ok := unparen(stmt.X).(*syntax.CallExpr); ok {
+                       if p.isBuiltin(call.Fun, "panic") {
+                               return true
+                       }
+               }
+
+               // The handling of BlockStmt here is approximate, but it serves to
+               // allow dead-code elimination for:
+               //
+               //      if true {
+               //              return x
+               //      }
+               //      unreachable
+       case *syntax.IfStmt:
+               cond := p.staticBool(&stmt.Cond)
+               return (cond < 0 || p.terminates(stmt.Then)) && (cond > 0 || p.terminates(stmt.Else))
+       case *syntax.BlockStmt:
+               return p.terminates(lastNonEmptyStmt(stmt.List))
+       }
+
+       return false
+}