blob: decd2611836fb159d38a338e0f123ec553e0101f [file] [log] [blame]
// 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 {
if cas2 != cas || c2 != c {
ir.Visit(c2, markHiddenClosureDead)
}
}
if cas2 != cas {
ir.VisitList(cas2.Body, markHiddenClosureDead)
}
}
cas.List[0] = c
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)
}