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