1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
7 // This file implements discovery of switch and type-switch constructs
8 // from low-level control flow.
10 // Many techniques exist for compiling a high-level switch with
11 // constant cases to efficient machine code. The optimal choice will
12 // depend on the data type, the specific case values, the code in the
13 // body of each case, and the hardware.
15 // - a lookup table (for a switch that maps constants to constants)
19 // - a two-level switch (to partition constant strings by their first byte).
27 "honnef.co/go/tools/ir"
30 // A ConstCase represents a single constant comparison.
31 // It is part of a Switch.
32 type ConstCase struct {
33 Block *ir.BasicBlock // block performing the comparison
34 Body *ir.BasicBlock // body of the case
35 Value *ir.Const // case comparand
38 // A TypeCase represents a single type assertion.
39 // It is part of a Switch.
40 type TypeCase struct {
41 Block *ir.BasicBlock // block performing the type assert
42 Body *ir.BasicBlock // body of the case
43 Type types.Type // case type
44 Binding ir.Value // value bound by this case
47 // A Switch is a logical high-level control flow operation
48 // (a multiway branch) discovered by analysis of a CFG containing
49 // only if/else chains. It is not part of the ir.Instruction set.
51 // One of ConstCases and TypeCases has length >= 2;
54 // In a value switch, the list of cases may contain duplicate constants.
55 // A type switch may contain duplicate types, or types assignable
56 // to an interface type also in the list.
57 // TODO(adonovan): eliminate such duplicates.
60 Start *ir.BasicBlock // block containing start of if/else chain
61 X ir.Value // the switch operand
62 ConstCases []ConstCase // ordered list of constant comparisons
63 TypeCases []TypeCase // ordered list of type assertions
64 Default *ir.BasicBlock // successor if all comparisons fail
67 func (sw *Switch) String() string {
68 // We represent each block by the String() of its
69 // first Instruction, e.g. "print(42:int)".
71 if sw.ConstCases != nil {
72 fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
73 for _, c := range sw.ConstCases {
74 fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0])
77 fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
78 for _, c := range sw.TypeCases {
79 fmt.Fprintf(&buf, "case %s %s: %s\n",
80 c.Binding.Name(), c.Type, c.Body.Instrs[0])
83 if sw.Default != nil {
84 fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
86 fmt.Fprintf(&buf, "}")
90 // Switches examines the control-flow graph of fn and returns the
91 // set of inferred value and type switches. A value switch tests an
92 // ir.Value for equality against two or more compile-time constant
93 // values. Switches involving link-time constants (addresses) are
94 // ignored. A type switch type-asserts an ir.Value against two or
97 // The switches are returned in dominance order.
99 // The resulting switches do not necessarily correspond to uses of the
100 // 'switch' keyword in the source: for example, a single source-level
101 // switch statement with non-constant cases may result in zero, one or
102 // many Switches, one per plural sequence of constant cases.
103 // Switches may even be inferred from if/else- or goto-based control flow.
104 // (In general, the control flow constructs of the source program
105 // cannot be faithfully reproduced from the IR.)
107 func Switches(fn *ir.Function) []Switch {
108 // Traverse the CFG in dominance order, so we don't
109 // enter an if/else-chain in the middle.
110 var switches []Switch
111 seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet
112 for _, b := range fn.DomPreorder() {
113 if x, k := isComparisonBlock(b); x != nil {
114 // Block b starts a switch.
115 sw := Switch{Start: b, X: x}
116 valueSwitch(&sw, k, seen)
117 if len(sw.ConstCases) > 1 {
118 switches = append(switches, sw)
122 if y, x, T := isTypeAssertBlock(b); y != nil {
123 // Block b starts a type switch.
124 sw := Switch{Start: b, X: x}
125 typeSwitch(&sw, y, T, seen)
126 if len(sw.TypeCases) > 1 {
127 switches = append(switches, sw)
134 func isSameX(x1 ir.Value, x2 ir.Value) bool {
138 if x2, ok := x2.(*ir.Sigma); ok {
139 return isSameX(x1, x2.X)
144 func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) {
147 for isSameX(sw.X, x) {
153 sw.ConstCases = append(sw.ConstCases, ConstCase{
160 for _, instr := range b.Instrs {
161 switch instr.(type) {
162 case *ir.If, *ir.BinOp:
164 case *ir.Sigma, *ir.Phi, *ir.DebugRef:
170 // Block b contains not just 'if x == k' and σ/ϕ nodes,
171 // so it may have side effects that
172 // make it unsafe to elide.
175 if len(b.Preds) != 1 {
176 // Block b has multiple predecessors,
177 // so it cannot be treated as a case.
180 x, k = isComparisonBlock(b)
185 func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) {
188 for isSameX(sw.X, x) {
194 sw.TypeCases = append(sw.TypeCases, TypeCase{
202 for _, instr := range b.Instrs {
203 switch instr.(type) {
204 case *ir.TypeAssert, *ir.Extract, *ir.If:
206 case *ir.Sigma, *ir.Phi:
212 // Block b contains not just
213 // {TypeAssert; Extract #0; Extract #1; If}
214 // so it may have side effects that
215 // make it unsafe to elide.
218 if len(b.Preds) != 1 {
219 // Block b has multiple predecessors,
220 // so it cannot be treated as a case.
223 y, x, T = isTypeAssertBlock(b)
228 // isComparisonBlock returns the operands (v, k) if a block ends with
229 // a comparison v==k, where k is a compile-time constant.
231 func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) {
232 if n := len(b.Instrs); n >= 2 {
233 if i, ok := b.Instrs[n-1].(*ir.If); ok {
234 if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
235 if k, ok := binop.Y.(*ir.Const); ok {
238 if k, ok := binop.X.(*ir.Const); ok {
247 // isTypeAssertBlock returns the operands (y, x, T) if a block ends with
248 // a type assertion "if y, ok := x.(T); ok {".
250 func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) {
251 if n := len(b.Instrs); n >= 4 {
252 if i, ok := b.Instrs[n-1].(*ir.If); ok {
253 if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
254 if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b {
255 // hack: relies upon instruction ordering.
256 if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok {
257 return ext0, ta.X, ta.AssertedType