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