| // Copyright 2013 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 ssautil |
| |
| // This file implements discovery of switch and type-switch constructs |
| // from low-level control flow. |
| // |
| // Many techniques exist for compiling a high-level switch with |
| // constant cases to efficient machine code. The optimal choice will |
| // depend on the data type, the specific case values, the code in the |
| // body of each case, and the hardware. |
| // Some examples: |
| // - a lookup table (for a switch that maps constants to constants) |
| // - a computed goto |
| // - a binary tree |
| // - a perfect hash |
| // - a two-level switch (to partition constant strings by their first byte). |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/token" |
| "go/types" |
| |
| "golang.org/x/tools/go/ssa" |
| ) |
| |
| // A ConstCase represents a single constant comparison. |
| // It is part of a Switch. |
| type ConstCase struct { |
| Block *ssa.BasicBlock // block performing the comparison |
| Body *ssa.BasicBlock // body of the case |
| Value *ssa.Const // case comparand |
| } |
| |
| // A TypeCase represents a single type assertion. |
| // It is part of a Switch. |
| type TypeCase struct { |
| Block *ssa.BasicBlock // block performing the type assert |
| Body *ssa.BasicBlock // body of the case |
| Type types.Type // case type |
| Binding ssa.Value // value bound by this case |
| } |
| |
| // A Switch is a logical high-level control flow operation |
| // (a multiway branch) discovered by analysis of a CFG containing |
| // only if/else chains. It is not part of the ssa.Instruction set. |
| // |
| // One of ConstCases and TypeCases has length >= 2; |
| // the other is nil. |
| // |
| // In a value switch, the list of cases may contain duplicate constants. |
| // A type switch may contain duplicate types, or types assignable |
| // to an interface type also in the list. |
| // TODO(adonovan): eliminate such duplicates. |
| type Switch struct { |
| Start *ssa.BasicBlock // block containing start of if/else chain |
| X ssa.Value // the switch operand |
| ConstCases []ConstCase // ordered list of constant comparisons |
| TypeCases []TypeCase // ordered list of type assertions |
| Default *ssa.BasicBlock // successor if all comparisons fail |
| } |
| |
| func (sw *Switch) String() string { |
| // We represent each block by the String() of its |
| // first Instruction, e.g. "print(42:int)". |
| var buf bytes.Buffer |
| if sw.ConstCases != nil { |
| fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) |
| for _, c := range sw.ConstCases { |
| fmt.Fprintf(&buf, "case %s: %s\n", c.Value, c.Body.Instrs[0]) |
| } |
| } else { |
| fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) |
| for _, c := range sw.TypeCases { |
| fmt.Fprintf(&buf, "case %s %s: %s\n", |
| c.Binding.Name(), c.Type, c.Body.Instrs[0]) |
| } |
| } |
| if sw.Default != nil { |
| fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) |
| } |
| fmt.Fprintf(&buf, "}") |
| return buf.String() |
| } |
| |
| // Switches examines the control-flow graph of fn and returns the |
| // set of inferred value and type switches. A value switch tests an |
| // ssa.Value for equality against two or more compile-time constant |
| // values. Switches involving link-time constants (addresses) are |
| // ignored. A type switch type-asserts an ssa.Value against two or |
| // more types. |
| // |
| // The switches are returned in dominance order. |
| // |
| // The resulting switches do not necessarily correspond to uses of the |
| // 'switch' keyword in the source: for example, a single source-level |
| // switch statement with non-constant cases may result in zero, one or |
| // many Switches, one per plural sequence of constant cases. |
| // Switches may even be inferred from if/else- or goto-based control flow. |
| // (In general, the control flow constructs of the source program |
| // cannot be faithfully reproduced from the SSA representation.) |
| func Switches(fn *ssa.Function) []Switch { |
| // Traverse the CFG in dominance order, so we don't |
| // enter an if/else-chain in the middle. |
| var switches []Switch |
| seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet |
| for _, b := range fn.DomPreorder() { |
| if x, k := isComparisonBlock(b); x != nil { |
| // Block b starts a switch. |
| sw := Switch{Start: b, X: x} |
| valueSwitch(&sw, k, seen) |
| if len(sw.ConstCases) > 1 { |
| switches = append(switches, sw) |
| } |
| } |
| |
| if y, x, T := isTypeAssertBlock(b); y != nil { |
| // Block b starts a type switch. |
| sw := Switch{Start: b, X: x} |
| typeSwitch(&sw, y, T, seen) |
| if len(sw.TypeCases) > 1 { |
| switches = append(switches, sw) |
| } |
| } |
| } |
| return switches |
| } |
| |
| func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) { |
| b := sw.Start |
| x := sw.X |
| for x == sw.X { |
| if seen[b] { |
| break |
| } |
| seen[b] = true |
| |
| sw.ConstCases = append(sw.ConstCases, ConstCase{ |
| Block: b, |
| Body: b.Succs[0], |
| Value: k, |
| }) |
| b = b.Succs[1] |
| if len(b.Instrs) > 2 { |
| // Block b contains not just 'if x == k', |
| // so it may have side effects that |
| // make it unsafe to elide. |
| break |
| } |
| if len(b.Preds) != 1 { |
| // Block b has multiple predecessors, |
| // so it cannot be treated as a case. |
| break |
| } |
| x, k = isComparisonBlock(b) |
| } |
| sw.Default = b |
| } |
| |
| func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) { |
| b := sw.Start |
| x := sw.X |
| for x == sw.X { |
| if seen[b] { |
| break |
| } |
| seen[b] = true |
| |
| sw.TypeCases = append(sw.TypeCases, TypeCase{ |
| Block: b, |
| Body: b.Succs[0], |
| Type: T, |
| Binding: y, |
| }) |
| b = b.Succs[1] |
| if len(b.Instrs) > 4 { |
| // Block b contains not just |
| // {TypeAssert; Extract #0; Extract #1; If} |
| // so it may have side effects that |
| // make it unsafe to elide. |
| break |
| } |
| if len(b.Preds) != 1 { |
| // Block b has multiple predecessors, |
| // so it cannot be treated as a case. |
| break |
| } |
| y, x, T = isTypeAssertBlock(b) |
| } |
| sw.Default = b |
| } |
| |
| // isComparisonBlock returns the operands (v, k) if a block ends with |
| // a comparison v==k, where k is a compile-time constant. |
| func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) { |
| if n := len(b.Instrs); n >= 2 { |
| if i, ok := b.Instrs[n-1].(*ssa.If); ok { |
| if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { |
| if k, ok := binop.Y.(*ssa.Const); ok { |
| return binop.X, k |
| } |
| if k, ok := binop.X.(*ssa.Const); ok { |
| return binop.Y, k |
| } |
| } |
| } |
| } |
| return |
| } |
| |
| // isTypeAssertBlock returns the operands (y, x, T) if a block ends with |
| // a type assertion "if y, ok := x.(T); ok {". |
| func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) { |
| if n := len(b.Instrs); n >= 4 { |
| if i, ok := b.Instrs[n-1].(*ssa.If); ok { |
| if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 { |
| if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b { |
| // hack: relies upon instruction ordering. |
| if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok { |
| return ext0, ta.X, ta.AssertedType |
| } |
| } |
| } |
| } |
| } |
| return |
| } |