+++ /dev/null
-// 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)
-}
"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"
// 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),
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.
"strings"
"cmd/compile/internal/base"
- "cmd/compile/internal/deadcode"
"cmd/compile/internal/dwarfgen"
"cmd/compile/internal/inline"
"cmd/compile/internal/ir"
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
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
}
// 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
// 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 {
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)
}
}
r.funarghack = true
r.funcBody(tmpfn)
-
- ir.WithFunc(tmpfn, func() {
- deadcode.Func(tmpfn)
- })
}
used := usedLocals(tmpfn.Body)
import (
"fmt"
+ "go/constant"
+ "go/token"
"internal/buildcfg"
"internal/pkgbits"
}
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)
}
} 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)
}
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)
} 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
// @@@ 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).
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()
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
+}