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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
//! Provides tools for generating the Direct Executor.
use crate::compiler::args::Arg;
use crate::compiler::instr::flow::{Block, Branch, Element};
use crate::compiler::variables::{Conversion, StackTracker, VarAssigner, VarAssignment, VarId};
use crate::microcode::{Microcode, ValType};
use super::instr::InstrDef;
/// [`Element`] with variable assignments and type conversions computed.
#[derive(Debug)]
pub enum FuncElement {
/// A microcode instruction with variable assignments.
Microcode(AssignedMicrocode),
/// A code block with variable assignments computed.
Block(AssignedBlock),
/// A branch with variable assignments.
Branch(AssignedBranch),
}
impl FuncElement {
/// Construct a root Funcelement from an InstrDef.
pub fn new(instr: InstrDef) -> Self {
let assigner = VarAssigner::default();
let mut stack = StackTracker::new_root(&assigner);
let assigned = FuncElement::build_assignment(instr.flow(), &mut stack);
assert!(
stack.total_bytes() == 0,
"Stack was not empty at the end of instruction execution."
);
assigned
}
/// Assign variables for this FuncElement.
pub fn build_assignment(elem: &Element, parent_stack: &mut StackTracker) -> Self {
match elem {
Element::Microcode(microcode) => FuncElement::Microcode(
AssignedMicrocode::build_assignment(*microcode, parent_stack),
),
Element::Block(block) => {
FuncElement::Block(AssignedBlock::build_assignment(block, parent_stack))
}
Element::Branch(branch) => {
FuncElement::Branch(AssignedBranch::build_assignment(branch, parent_stack))
}
}
}
}
/// Microcode with required type conversions and variable assignments provided.
#[derive(Debug)]
pub struct AssignedMicrocode {
/// The microcode to execute.
pub microcode: Microcode,
/// Type conversions to apply before running the microcode for this operation.
pub conversions: Vec<Conversion>,
/// Arguments to the microcode function, with the variables they will be pulled from.
pub args: Vec<Arg<VarAssignment>>,
/// Variables that will be assigned by the result.
pub returns: Vec<VarAssignment>,
}
impl AssignedMicrocode {
/// Assign variables for this microcode instruction. Result variable assignments will
/// be applied directly to the parent stack since Microcode does not produce a new
/// scope.
fn build_assignment(microcode: Microcode, parent_stack: &mut StackTracker) -> Self {
let desc = microcode.descriptor();
let mut conversions = vec![];
let args = desc
.args
.into_iter()
.map(|arg| match arg {
Arg::Literal(lit) => Arg::Literal(lit),
Arg::StackValue(val) => {
let (assignment, c) = parent_stack.pop(val);
conversions.extend_from_slice(&c);
Arg::StackValue(assignment)
}
})
.collect();
let returns = desc
.returns
.into_iter()
.map(|ret| parent_stack.push(ret))
.collect();
Self {
microcode,
conversions,
args,
returns,
}
}
}
/// A "block" of func elements with variable assignments computed. Note that this block
/// does not create a new scope, unlike a Rust `{` block `}`.
#[derive(Debug)]
pub struct AssignedBlock {
/// Assigned elements.
pub elements: Vec<FuncElement>,
}
impl AssignedBlock {
fn build_assignment(block: &Block, parent_stack: &mut StackTracker) -> Self {
let elements = block
.elements
.iter()
.map(|elem| FuncElement::build_assignment(elem, parent_stack))
.collect();
Self { elements }
}
}
#[derive(Debug)]
pub struct AssignedBranch {
/// Conversions applied to acquire the branch condition.
pub cond_conversion: Vec<Conversion>,
/// Variable which will contain the bool branch condition.
pub cond: VarId,
/// Code to execute if the condition is true.
pub code_if_true: Box<FuncElement>,
/// Assignments of the pushed values from the true branch. These will be collected
/// into a tuple and assigned to the `pushes` of the branch in the outer block.
pub true_returns: Vec<VarAssignment>,
/// Conversions to apply at the end of the true branch to align its types to the false
/// branch.
pub true_end_conv: Vec<Conversion>,
/// Code to execute if the condition is false.
pub code_if_false: Box<FuncElement>,
/// Conversions to apply at the end of the false branch to align its types to the true
/// branch.
pub false_end_conv: Vec<Conversion>,
/// Assignments of the pushed values from the false branch. These will be collected
/// into a tuple and assigned to the `pushes` of the branch in the outer block.
pub false_returns: Vec<VarAssignment>,
/// Values pushed onto the parent's stack in the scope outside of the branch.
pub pushes: Vec<VarAssignment>,
}
impl AssignedBranch {
fn build_assignment(branch: &Branch, parent_stack: &mut StackTracker) -> Self {
let (cond, cond_conversion) = parent_stack.pop(ValType::Bool);
let mut true_stack = parent_stack.make_child();
let code_if_true = Box::new(FuncElement::build_assignment(
&branch.code_if_true,
&mut true_stack,
));
let mut false_stack = parent_stack.make_child();
let code_if_false = Box::new(FuncElement::build_assignment(
&branch.code_if_false,
&mut false_stack,
));
assert!(
true_stack.total_bytes() == false_stack.total_bytes(),
"True and false branches have different byte counts"
);
let true_terminal = branch.code_if_true.ends_with_terminal();
let false_terminal = branch.code_if_false.ends_with_terminal();
let mut true_end_conv = vec![];
let mut false_end_conv = vec![];
let pushes = if true_terminal && false_terminal {
// Both branches are terminals, so there's no result from the `if` in either
// case. No need to compute pushes, just check that both stacks are empty.
assert!(
true_stack.total_bytes() == 0,
"Terminal branch has non-empty stack"
);
assert!(
false_stack.total_bytes() == 0,
"Terminal branch has non-empty stack"
);
vec![]
} else if true_terminal {
// True branch is a terminal operation, but false is not. Take our pushes from
// the false stack, and update the parent stack (the result of our operation)
// from the end state of the parent of the false branch.
assert!(
true_stack.total_bytes() == 0,
"Terminal branch has non-empty stack"
);
*parent_stack = *false_stack.parent.unwrap();
false_stack
.local_stack
.iter()
.map(|asgn| parent_stack.push(asgn.val))
.collect()
} else if false_terminal {
// False branch is a terminal operation, but true is not. Take our pushes from
// the true stack, and update the parent stack (the result of our operation)
// from the end state of the parent of the true branch.
assert!(
false_stack.total_bytes() == 0,
"Terminal branch has non-empty stack"
);
*parent_stack = *true_stack.parent.unwrap();
true_stack
.local_stack
.iter()
.map(|asgn| parent_stack.push(asgn.val))
.collect()
} else {
// Both true and false branches continue to further code.
// Make the true and false branches have the same return length and types.
if true_stack.local_bytes() < false_stack.local_bytes() {
// If the true_stack is smaller, pop values off of true_stack matching
// false_stack's types to do the type conversion and pull more values from the
// parent.
let mut true_pushes_rev = Vec::with_capacity(false_stack.local_stack.len());
for VarAssignment { val, .. } in false_stack.local_stack.iter().rev().copied() {
let (var, conv) = true_stack.pop(val);
true_end_conv.extend_from_slice(&conv);
true_pushes_rev.push(var);
}
true_pushes_rev.reverse();
assert!(true_stack.local_stack.is_empty());
true_stack.local_stack = true_pushes_rev;
} else if true_stack.local_bytes() > false_stack.local_bytes() {
// If the false_stack's local stack is smaller, pop values off the false_stack
// matching true_stacks' types to do type conversion and pull more values from
// the parent if needed.
let mut false_pushes_rev = Vec::with_capacity(true_stack.local_stack.len());
for VarAssignment { val, .. } in true_stack.local_stack.iter().rev().copied() {
let (var, conv) = false_stack.pop(val);
false_end_conv.extend_from_slice(&conv);
false_pushes_rev.push(var);
}
false_pushes_rev.reverse();
assert!(false_stack.local_stack.is_empty());
false_stack.local_stack = false_pushes_rev;
}
// Check that we've put both parent stacks in the same state.
assert!(true_stack.parent == false_stack.parent);
assert!(
true_stack
.local_stack
.iter()
.map(|asgn| asgn.val)
.collect::<Vec<_>>()
== false_stack
.local_stack
.iter()
.map(|asgn| asgn.val)
.collect::<Vec<_>>()
);
*parent_stack = *true_stack.parent.unwrap();
true_stack
.local_stack
.iter()
.map(|asgn| parent_stack.push(asgn.val))
.collect()
};
Self {
cond_conversion,
cond: cond.var,
code_if_true,
true_end_conv,
true_returns: true_stack.local_stack,
code_if_false,
false_end_conv,
false_returns: false_stack.local_stack,
pushes,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::opcode::{CBOpcode, InternalFetch, Opcode};
#[test]
fn assign_all_instrs() {
FuncElement::new(InternalFetch.into());
for opcode in 0..=0xffu8 {
FuncElement::new(Opcode::decode(opcode).into());
}
for cbopcode in 0..=0xffu8 {
FuncElement::new(CBOpcode::decode(cbopcode).into());
}
}
}