github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/go/ir/irutil/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 irutil 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/amarpal/go-tools/go/ir" 28 ) 29 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 36 } 37 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 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 ir.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 type Switch struct { 59 Start *ir.BasicBlock // block containing start of if/else chain 60 X ir.Value // the switch operand 61 ConstCases []ConstCase // ordered list of constant comparisons 62 TypeCases []TypeCase // ordered list of type assertions 63 Default *ir.BasicBlock // successor if all comparisons fail 64 } 65 66 func (sw *Switch) String() string { 67 // We represent each block by the String() of its 68 // first Instruction, e.g. "print(42:int)". 69 var buf bytes.Buffer 70 if sw.ConstCases != nil { 71 fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) 72 for _, c := range sw.ConstCases { 73 fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0]) 74 } 75 } else { 76 fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) 77 for _, c := range sw.TypeCases { 78 fmt.Fprintf(&buf, "case %s %s: %s\n", 79 c.Binding.Name(), c.Type, c.Body.Instrs[0]) 80 } 81 } 82 if sw.Default != nil { 83 fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) 84 } 85 fmt.Fprintf(&buf, "}") 86 return buf.String() 87 } 88 89 // Switches examines the control-flow graph of fn and returns the 90 // set of inferred value and type switches. A value switch tests an 91 // ir.Value for equality against two or more compile-time constant 92 // values. Switches involving link-time constants (addresses) are 93 // ignored. A type switch type-asserts an ir.Value against two or 94 // more types. 95 // 96 // The switches are returned in dominance order. 97 // 98 // The resulting switches do not necessarily correspond to uses of the 99 // 'switch' keyword in the source: for example, a single source-level 100 // switch statement with non-constant cases may result in zero, one or 101 // many Switches, one per plural sequence of constant cases. 102 // Switches may even be inferred from if/else- or goto-based control flow. 103 // (In general, the control flow constructs of the source program 104 // cannot be faithfully reproduced from the IR.) 105 func Switches(fn *ir.Function) []Switch { 106 // Traverse the CFG in dominance order, so we don't 107 // enter an if/else-chain in the middle. 108 var switches []Switch 109 seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet 110 for _, b := range fn.DomPreorder() { 111 if x, k := isComparisonBlock(b); x != nil { 112 // Block b starts a switch. 113 sw := Switch{Start: b, X: x} 114 valueSwitch(&sw, k, seen) 115 if len(sw.ConstCases) > 1 { 116 switches = append(switches, sw) 117 } 118 } 119 120 if y, x, T := isTypeAssertBlock(b); y != nil { 121 // Block b starts a type switch. 122 sw := Switch{Start: b, X: x} 123 typeSwitch(&sw, y, T, seen) 124 if len(sw.TypeCases) > 1 { 125 switches = append(switches, sw) 126 } 127 } 128 } 129 return switches 130 } 131 132 func isSameX(x1 ir.Value, x2 ir.Value) bool { 133 if x1 == x2 { 134 return true 135 } 136 if x2, ok := x2.(*ir.Sigma); ok { 137 return isSameX(x1, x2.X) 138 } 139 return false 140 } 141 142 func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { 143 b := sw.Start 144 x := sw.X 145 for isSameX(sw.X, x) { 146 if seen[b] { 147 break 148 } 149 seen[b] = true 150 151 sw.ConstCases = append(sw.ConstCases, ConstCase{ 152 Block: b, 153 Body: b.Succs[0], 154 Value: k, 155 }) 156 b = b.Succs[1] 157 n := 0 158 for _, instr := range b.Instrs { 159 switch instr.(type) { 160 case *ir.If, *ir.BinOp: 161 n++ 162 case *ir.Sigma, *ir.Phi, *ir.DebugRef: 163 default: 164 n += 1000 165 } 166 } 167 if n != 2 { 168 // Block b contains not just 'if x == k' and σ/ϕ nodes, 169 // so it may have side effects that 170 // make it unsafe to elide. 171 break 172 } 173 if len(b.Preds) != 1 { 174 // Block b has multiple predecessors, 175 // so it cannot be treated as a case. 176 break 177 } 178 x, k = isComparisonBlock(b) 179 } 180 sw.Default = b 181 } 182 183 func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) { 184 b := sw.Start 185 x := sw.X 186 for isSameX(sw.X, x) { 187 if seen[b] { 188 break 189 } 190 seen[b] = true 191 192 sw.TypeCases = append(sw.TypeCases, TypeCase{ 193 Block: b, 194 Body: b.Succs[0], 195 Type: T, 196 Binding: y, 197 }) 198 b = b.Succs[1] 199 n := 0 200 for _, instr := range b.Instrs { 201 switch instr.(type) { 202 case *ir.TypeAssert, *ir.Extract, *ir.If: 203 n++ 204 case *ir.Sigma, *ir.Phi: 205 default: 206 n += 1000 207 } 208 } 209 if n != 4 { 210 // Block b contains not just 211 // {TypeAssert; Extract #0; Extract #1; If} 212 // so it may have side effects that 213 // make it unsafe to elide. 214 break 215 } 216 if len(b.Preds) != 1 { 217 // Block b has multiple predecessors, 218 // so it cannot be treated as a case. 219 break 220 } 221 y, x, T = isTypeAssertBlock(b) 222 } 223 sw.Default = b 224 } 225 226 // isComparisonBlock returns the operands (v, k) if a block ends with 227 // a comparison v==k, where k is a compile-time constant. 228 func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) { 229 if n := len(b.Instrs); n >= 2 { 230 if i, ok := b.Instrs[n-1].(*ir.If); ok { 231 if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { 232 if k, ok := binop.Y.(*ir.Const); ok { 233 return binop.X, k 234 } 235 if k, ok := binop.X.(*ir.Const); ok { 236 return binop.Y, k 237 } 238 } 239 } 240 } 241 return 242 } 243 244 // isTypeAssertBlock returns the operands (y, x, T) if a block ends with 245 // a type assertion "if y, ok := x.(T); ok {". 246 func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) { 247 if n := len(b.Instrs); n >= 4 { 248 if i, ok := b.Instrs[n-1].(*ir.If); ok { 249 if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 { 250 if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b { 251 // hack: relies upon instruction ordering. 252 if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok { 253 return ext0, ta.X, ta.AssertedType 254 } 255 } 256 } 257 } 258 } 259 return 260 }