github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/ssa/ssautil/switch.go (about) 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. 4 5 package ssautil 6 7 // This file implements discovery of switch and type-switch constructs 8 // from low-level control flow. 9 // 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. 14 // Some examples: 15 // - a lookup table (for a switch that maps constants to constants) 16 // - a computed goto 17 // - a binary tree 18 // - a perfect hash 19 // - a two-level switch (to partition constant strings by their first byte). 20 21 import ( 22 "bytes" 23 "fmt" 24 "go/token" 25 "go/types" 26 27 "github.com/golangci/go-tools/ssa" 28 ) 29 30 // A ConstCase represents a single constant comparison. 31 // It is part of a Switch. 32 type ConstCase struct { 33 Block *ssa.BasicBlock // block performing the comparison 34 Body *ssa.BasicBlock // body of the case 35 Value *ssa.Const // case comparand 36 } 37 38 // A TypeCase represents a single type assertion. 39 // It is part of a Switch. 40 type TypeCase struct { 41 Block *ssa.BasicBlock // block performing the type assert 42 Body *ssa.BasicBlock // body of the case 43 Type types.Type // case type 44 Binding ssa.Value // value bound by this case 45 } 46 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 ssa.Instruction set. 50 // 51 // One of ConstCases and TypeCases has length >= 2; 52 // the other is nil. 53 // 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. 58 // 59 type Switch struct { 60 Start *ssa.BasicBlock // block containing start of if/else chain 61 X ssa.Value // the switch operand 62 ConstCases []ConstCase // ordered list of constant comparisons 63 TypeCases []TypeCase // ordered list of type assertions 64 Default *ssa.BasicBlock // successor if all comparisons fail 65 } 66 67 func (sw *Switch) String() string { 68 // We represent each block by the String() of its 69 // first Instruction, e.g. "print(42:int)". 70 var buf bytes.Buffer 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, c.Body.Instrs[0]) 75 } 76 } else { 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]) 81 } 82 } 83 if sw.Default != nil { 84 fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) 85 } 86 fmt.Fprintf(&buf, "}") 87 return buf.String() 88 } 89 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 // ssa.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 ssa.Value against two or 95 // more types. 96 // 97 // The switches are returned in dominance order. 98 // 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 SSA representation.) 106 // 107 func Switches(fn *ssa.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[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.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) 119 } 120 } 121 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) 128 } 129 } 130 } 131 return switches 132 } 133 134 func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) { 135 b := sw.Start 136 x := sw.X 137 for x == sw.X { 138 if seen[b] { 139 break 140 } 141 seen[b] = true 142 143 sw.ConstCases = append(sw.ConstCases, ConstCase{ 144 Block: b, 145 Body: b.Succs[0], 146 Value: k, 147 }) 148 b = b.Succs[1] 149 if len(b.Instrs) > 2 { 150 // Block b contains not just 'if x == k', 151 // so it may have side effects that 152 // make it unsafe to elide. 153 break 154 } 155 if len(b.Preds) != 1 { 156 // Block b has multiple predecessors, 157 // so it cannot be treated as a case. 158 break 159 } 160 x, k = isComparisonBlock(b) 161 } 162 sw.Default = b 163 } 164 165 func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) { 166 b := sw.Start 167 x := sw.X 168 for x == sw.X { 169 if seen[b] { 170 break 171 } 172 seen[b] = true 173 174 sw.TypeCases = append(sw.TypeCases, TypeCase{ 175 Block: b, 176 Body: b.Succs[0], 177 Type: T, 178 Binding: y, 179 }) 180 b = b.Succs[1] 181 if len(b.Instrs) > 4 { 182 // Block b contains not just 183 // {TypeAssert; Extract #0; Extract #1; If} 184 // so it may have side effects that 185 // make it unsafe to elide. 186 break 187 } 188 if len(b.Preds) != 1 { 189 // Block b has multiple predecessors, 190 // so it cannot be treated as a case. 191 break 192 } 193 y, x, T = isTypeAssertBlock(b) 194 } 195 sw.Default = b 196 } 197 198 // isComparisonBlock returns the operands (v, k) if a block ends with 199 // a comparison v==k, where k is a compile-time constant. 200 // 201 func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) { 202 if n := len(b.Instrs); n >= 2 { 203 if i, ok := b.Instrs[n-1].(*ssa.If); ok { 204 if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { 205 if k, ok := binop.Y.(*ssa.Const); ok { 206 return binop.X, k 207 } 208 if k, ok := binop.X.(*ssa.Const); ok { 209 return binop.Y, k 210 } 211 } 212 } 213 } 214 return 215 } 216 217 // isTypeAssertBlock returns the operands (y, x, T) if a block ends with 218 // a type assertion "if y, ok := x.(T); ok {". 219 // 220 func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) { 221 if n := len(b.Instrs); n >= 4 { 222 if i, ok := b.Instrs[n-1].(*ssa.If); ok { 223 if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 { 224 if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b { 225 // hack: relies upon instruction ordering. 226 if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok { 227 return ext0, ta.X, ta.AssertedType 228 } 229 } 230 } 231 } 232 } 233 return 234 }