GoPLS Viewer

Home|gopls/go/ssa/ssautil/switch.go
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
5package 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
21import (
22    "bytes"
23    "fmt"
24    "go/token"
25    "go/types"
26
27    "golang.org/x/tools/go/ssa"
28)
29
30// A ConstCase represents a single constant comparison.
31// It is part of a Switch.
32type 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.
40type 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.
58type Switch struct {
59    Start      *ssa.BasicBlock // block containing start of if/else chain
60    X          ssa.Value       // the switch operand
61    ConstCases []ConstCase     // ordered list of constant comparisons
62    TypeCases  []TypeCase      // ordered list of type assertions
63    Default    *ssa.BasicBlock // successor if all comparisons fail
64}
65
66func (sw *SwitchString() 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.Valuec.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.Typec.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// ssa.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 ssa.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 SSA representation.)
105func Switches(fn *ssa.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[*ssa.BasicBlock]bool// TODO(adonovan): opt: use ssa.blockSet
110    for _b := range fn.DomPreorder() {
111        if xk := isComparisonBlock(b); x != nil {
112            // Block b starts a switch.
113            sw := Switch{StartbXx}
114            valueSwitch(&swkseen)
115            if len(sw.ConstCases) > 1 {
116                switches = append(switchessw)
117            }
118        }
119
120        if yxT := isTypeAssertBlock(b); y != nil {
121            // Block b starts a type switch.
122            sw := Switch{StartbXx}
123            typeSwitch(&swyTseen)
124            if len(sw.TypeCases) > 1 {
125                switches = append(switchessw)
126            }
127        }
128    }
129    return switches
130}
131
132func valueSwitch(sw *Switchk *ssa.Constseen map[*ssa.BasicBlock]bool) {
133    b := sw.Start
134    x := sw.X
135    for x == sw.X {
136        if seen[b] {
137            break
138        }
139        seen[b] = true
140
141        sw.ConstCases = append(sw.ConstCasesConstCase{
142            Blockb,
143            Body:  b.Succs[0],
144            Valuek,
145        })
146        b = b.Succs[1]
147        if len(b.Instrs) > 2 {
148            // Block b contains not just 'if x == k',
149            // so it may have side effects that
150            // make it unsafe to elide.
151            break
152        }
153        if len(b.Preds) != 1 {
154            // Block b has multiple predecessors,
155            // so it cannot be treated as a case.
156            break
157        }
158        xk = isComparisonBlock(b)
159    }
160    sw.Default = b
161}
162
163func typeSwitch(sw *Switchy ssa.ValueT types.Typeseen map[*ssa.BasicBlock]bool) {
164    b := sw.Start
165    x := sw.X
166    for x == sw.X {
167        if seen[b] {
168            break
169        }
170        seen[b] = true
171
172        sw.TypeCases = append(sw.TypeCasesTypeCase{
173            Block:   b,
174            Body:    b.Succs[0],
175            Type:    T,
176            Bindingy,
177        })
178        b = b.Succs[1]
179        if len(b.Instrs) > 4 {
180            // Block b contains not just
181            //  {TypeAssert; Extract #0; Extract #1; If}
182            // so it may have side effects that
183            // make it unsafe to elide.
184            break
185        }
186        if len(b.Preds) != 1 {
187            // Block b has multiple predecessors,
188            // so it cannot be treated as a case.
189            break
190        }
191        yxT = isTypeAssertBlock(b)
192    }
193    sw.Default = b
194}
195
196// isComparisonBlock returns the operands (v, k) if a block ends with
197// a comparison v==k, where k is a compile-time constant.
198func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Valuek *ssa.Const) {
199    if n := len(b.Instrs); n >= 2 {
200        if iok := b.Instrs[n-1].(*ssa.If); ok {
201            if binopok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
202                if kok := binop.Y.(*ssa.Const); ok {
203                    return binop.Xk
204                }
205                if kok := binop.X.(*ssa.Const); ok {
206                    return binop.Yk
207                }
208            }
209        }
210    }
211    return
212}
213
214// isTypeAssertBlock returns the operands (y, x, T) if a block ends with
215// a type assertion "if y, ok := x.(T); ok {".
216func isTypeAssertBlock(b *ssa.BasicBlock) (yx ssa.ValueT types.Type) {
217    if n := len(b.Instrs); n >= 4 {
218        if iok := b.Instrs[n-1].(*ssa.If); ok {
219            if ext1ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
220                if taok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b {
221                    // hack: relies upon instruction ordering.
222                    if ext0ok := b.Instrs[n-3].(*ssa.Extract); ok {
223                        return ext0ta.Xta.AssertedType
224                    }
225                }
226            }
227        }
228    }
229    return
230}
231
MembersX
valueSwitch
valueSwitch.x
isComparisonBlock.n
isTypeAssertBlock.T
Switches.RangeStmt_4024.b
Switches.RangeStmt_4024.BlockStmt.T
typeSwitch.seen
isTypeAssertBlock.x
ConstCase.Block
Switch.String.BlockStmt.RangeStmt_2459.c
typeSwitch.y
typeSwitch.T
typeSwitch.b
TypeCase.Type
Switches.RangeStmt_4024.BlockStmt.k
typeSwitch
isTypeAssertBlock.y
TypeCase.Body
Switches.switches
Switches
Switches.seen
Switches.RangeStmt_4024.BlockStmt.y
isComparisonBlock
fmt
ConstCase
TypeCase.Block
Switch.String.BlockStmt.RangeStmt_2631.c
Switch.String
Switch
Switches.RangeStmt_4024.BlockStmt.BlockStmt.sw
Switch.X
isTypeAssertBlock.n
ConstCase.Body
ConstCase.Value
valueSwitch.b
typeSwitch.sw
isComparisonBlock.v
Switch.TypeCases
typeSwitch.x
isComparisonBlock.b
isTypeAssertBlock
Switch.ConstCases
valueSwitch.seen
Switch.Default
Switch.String.sw
valueSwitch.sw
isTypeAssertBlock.b
TypeCase
Switch.Start
Switches.RangeStmt_4024.BlockStmt.x
valueSwitch.k
Switch.String.buf
Switches.fn
isComparisonBlock.k
TypeCase.Binding
Members
X