-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathterminator.go
More file actions
183 lines (168 loc) · 5.82 KB
/
terminator.go
File metadata and controls
183 lines (168 loc) · 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package ossa
import (
"fmt"
)
// Terminator represents the edge between one basic block and zero or more
// other successor basic blocks in a control flow graph.
type Terminator struct {
op Op
// args is zero or more argument values whose meaning depends on the
// indicated operation. Some elements of this slice may not use both
// fields of struct BasicBlockValue, depending on the needs of the op.
args []BasicBlockValue
// For ops that use two or fewer args, this can be used as the backing
// array for args, avoiding another allocation. The size 3 is chosen
// to make just enough room for call instructions that are representing
// either unary or binary operators (where the first element is a
// representation of the operator itself.)
argsBuf [2]BasicBlockValue
}
// Jump constructs an unconditional jump terminator leading to the given
// other basic block.
func Jump(target *BasicBlock) *Terminator {
t := &Terminator{
op: OpJump,
}
t.argsBuf[0].Block = target
t.args = t.argsBuf[:1]
return t
}
// Branch constructs a conditional branch terminator with the given condition
// value and pair of target basic blocks.
func Branch(cond *Value, trueTarget, falseTarget *BasicBlock) *Terminator {
t := &Terminator{
op: OpBranch,
}
t.argsBuf[0].Value = cond
t.argsBuf[0].Block = trueTarget
t.argsBuf[1].Block = falseTarget // argsBuf[1].Value is unused
t.args = t.argsBuf[:2]
return t
}
// Switch constructs a conditional switch terminator with the given input
// value, default target basic block, and zero or more conditional branch
// pairs.
func Switch(inp *Value, defTarget *BasicBlock, cases ...BasicBlockValue) *Terminator {
t := &Terminator{
op: OpSwitch,
}
aa := t.bufForArgs(len(cases) + 1)
aa = append(aa, BasicBlockValue{
Value: inp,
Block: defTarget,
})
aa = append(aa, cases...)
t.args = aa
return t
}
// Return constructs a terminator that exits the current function with the
// given return value. This terminator produces no successors.
func Return(ret *Value) *Terminator {
t := &Terminator{
op: OpReturn,
}
t.argsBuf[0].Value = ret
t.args = t.argsBuf[:1]
return t
}
// Yield constructs a terminator that acts as a yield point for coroutines.
// Yield indicates that the routine wishes to yield control to another routine.
// The exact behavior of a yield is ultimately decided by the language runtime;
// for languages that don't use coroutines, do not generate Yield terminators.
//
// The given basic block is the point where execution will continue after the
// coroutine is resumed.
func Yield(resume *BasicBlock) *Terminator {
t := &Terminator{
op: OpYield,
}
t.argsBuf[0].Block = resume
t.args = t.argsBuf[:1]
return t
}
// Await constructs a terminator that acts as an async blocking point for
// coroutines. This is similar to Await except also takes an argument for
// some language-defined event value (promise, etc) that must occur or complete
// before the routine can resume.
//
// The given basic block is the point where execution will continue after the
// coroutine is resumed.
func Await(event *Value, resume *BasicBlock) *Terminator {
t := &Terminator{
op: OpAwait,
}
t.argsBuf[0].Value = event
t.argsBuf[0].Block = resume
t.args = t.argsBuf[:1]
return t
}
// Unreachable is a special terminator that has no behavior and no successors.
// This should be used only in situations where the language frontend can
// guarantee control can never reach a certain point (or it would be undefined
// behavior to do so).
//
// For example, this might be emitted immediately after a call instruction which
// the frontend knows cannot actually return in practice, e.g. if it exits the
// program, or just blocks/loops forever.
//
// Although this is a variable, callers are forbidden from assigning to it.
var Unreachable *Terminator
// AppendSuccessors appends to the given slice any successors for the recieving
// terminator. Pass a nil slice to force this function to allocate a new backing
// array and return it, or pre-allocate a buffer in the caller.
//
// Most terminators have no more than two successors, so passing a slice with
// at least capacity two can avoid allocation in many cases. On the other hand,
// some terminators have no successors at all, so passing nil can mean avoiding
// allocation altogether in those cases.
func (t *Terminator) AppendSuccessors(to []*BasicBlock) []*BasicBlock {
b := basicBlockSliceBuilder{&to}
t.AddSuccessors(&b)
return to
}
// AddSuccessors adds to the given set any successors for the receiving
// terminator, in-place.
func (t *Terminator) AddSuccessors(to BasicBlockAdder) {
// This switch must cover all of the ops that are considered to be
// terminator operations by op.Terminator.
switch t.op {
case OpJump:
to.Add(t.args[0].Block)
case OpBranch:
to.Add(t.args[0].Block)
to.Add(t.args[1].Block)
case OpSwitch:
for _, arg := range t.args {
to.Add(arg.Block)
}
case OpReturn, OpUnreachable:
return // no successors
case OpYield, OpAwait:
to.Add(t.args[0].Block)
default:
if t.op.Terminator() {
// Indicates we're missing a case above
panic(fmt.Sprintf("AppendSuccessors is missing a case for %s", t.op))
} else {
// Indicates an incorrectly-constructed terminator
panic("AppendSuccessors with non-terminator operation")
}
}
}
// bufForArgs returns a zero-length arg slice with at least the given capacity
// that can be used as the arguments for the receiving terminator.
//
// bufForArgs either allocates a slice with the given capacity ready to have
// arguments appended to it, or returns a slice backed by t.argsBuf if
// its length is enough to contain the args with no further allocation.
func (t *Terminator) bufForArgs(capacity int) []BasicBlockValue {
if len(t.argsBuf) >= capacity {
return t.argsBuf[:0]
}
return make([]BasicBlockValue, 0, capacity)
}
func init() {
Unreachable = &Terminator{
op: OpUnreachable,
}
}