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