feo3boy_opcodes/compiler/state_executor_generation.rs
1//! Provides utilities for codegenerating the state-machine executor.
2//!
3//! Although the types used here are very similar to those used in the direct executor, we
4//! don't share types because we need slightly different state types.
5//!
6//! We also don't share the variable assignment logic because we have slightly different
7//! constraints -- every time we yield, any stack nesting gets reset and we resume the
8//! next state with all variables in the local stack.
9
10use std::collections::BTreeMap;
11use std::iter::FusedIterator;
12use std::mem;
13
14use crate::compiler::args::Arg;
15use crate::compiler::instr::flow::{Element, ElementPath, ElementPathBuf, ElementSelector};
16use crate::compiler::instr::InstrDef;
17use crate::compiler::variables::{Conversion, StackTracker, VarAssigner, VarAssignment, VarId};
18use crate::microcode::{Microcode, ValType};
19
20/// Elements of a state.
21#[derive(Debug)]
22pub enum StateElement {
23 Microcode(StateMicrocode),
24 Block(StateBlock),
25 Branch(StateBranch),
26 /// Change to the state in InstrStates with the given index.
27 ChangeState(StateChange),
28}
29
30impl StateElement {
31 /// Run `other` after `self`. Transforms `self` into a `Block` if necessary to append
32 /// `other` and adds its elements after the current value.
33 pub fn extend_block(&mut self, other: StateElement) {
34 match (self, other) {
35 // self is a block and other is a block, so just append the elements from
36 // other onto self.
37 (Self::Block(ref mut self_block), Self::Block(mut other_block)) => {
38 self_block.elements.append(&mut other_block.elements)
39 }
40 // self is a block but other is not, so just push other on to this block
41 // directly.
42 (Self::Block(ref mut self_block), other) => self_block.elements.push(other),
43 // other is a block but self is not. Swap self and other, and then insert self
44 // at the front of other.
45 (this, mut other @ Self::Block(_)) => {
46 mem::swap(this, &mut other);
47 match this {
48 Self::Block(ref mut old_other_block) => {
49 old_other_block
50 .elements
51 .insert(0, /* used to be self */ other)
52 }
53 _ => unreachable!(),
54 }
55 }
56 // Neither self nor other are blocks. Create a new block and append both.
57 (this, other) => {
58 let old_self = mem::replace(
59 this,
60 StateElement::Block(StateBlock {
61 elements: Vec::with_capacity(2),
62 }),
63 );
64 match this {
65 Self::Block(ref mut self_block) => {
66 self_block.elements.push(old_self);
67 self_block.elements.push(other);
68 }
69 _ => unreachable!(),
70 }
71 }
72 }
73 }
74
75 /// Returns true if every path out of this element ends in a state-change or a
76 /// terminal operation.
77 ///
78 /// Only checks the end of a block, so if a state transition or terminal is
79 /// accidentally placed in the middle of a block, this won't find it.
80 pub fn ends_in_state_change_or_terminal(&self) -> bool {
81 match self {
82 StateElement::Microcode(code) => code.microcode.is_terminal(),
83 StateElement::Block(StateBlock { elements }) => match elements.last() {
84 Some(last) => last.ends_in_state_change_or_terminal(),
85 None => false,
86 },
87 StateElement::Branch(StateBranch {
88 code_if_true,
89 code_if_false,
90 ..
91 }) => {
92 // If either branch doesn't result in a state-change, we say we can still
93 // continue.
94 code_if_true.ends_in_state_change_or_terminal()
95 && code_if_false.ends_in_state_change_or_terminal()
96 }
97 StateElement::ChangeState(_) => true,
98 }
99 }
100}
101
102impl Default for StateElement {
103 fn default() -> Self {
104 StateElement::Block(Default::default())
105 }
106}
107
108/// Microcode with required type conversions and variable assignments provided.
109#[derive(Debug)]
110pub struct StateMicrocode {
111 /// The microcode to execute.
112 pub microcode: Microcode,
113 /// Type conversions to apply before running the microcode for this operation.
114 pub conversions: Vec<Conversion>,
115 /// Arguments to the microcode function, with the variables they will be pulled from.
116 pub args: Vec<Arg<VarAssignment>>,
117 /// Variables that will be assigned by the result.
118 pub returns: Vec<VarAssignment>,
119}
120
121impl StateMicrocode {
122 /// Assign variables for this microcode instruction. Result variable assignments will
123 /// be applied directly to the parent stack since Microcode does not produce a new
124 /// scope.
125 fn build_assignment(microcode: Microcode, parent_stack: &mut StackTracker) -> Self {
126 let desc = microcode.descriptor();
127 let mut conversions = vec![];
128 let args = desc
129 .args
130 .into_iter()
131 .map(|arg| match arg {
132 Arg::Literal(lit) => Arg::Literal(lit),
133 Arg::StackValue(val) => {
134 let (assignment, c) = parent_stack.pop(val);
135 conversions.extend_from_slice(&c);
136 Arg::StackValue(assignment)
137 }
138 })
139 .collect();
140 let returns = desc
141 .returns
142 .into_iter()
143 .map(|ret| parent_stack.push(ret))
144 .collect();
145
146 Self {
147 microcode,
148 conversions,
149 args,
150 returns,
151 }
152 }
153}
154
155/// A block of state elements with variable assignments computed. Note that this block
156/// does not create a new scope, unlike a Rust `{` block `}`.
157#[derive(Debug, Default)]
158pub struct StateBlock {
159 /// Assigned elements.
160 pub elements: Vec<StateElement>,
161}
162
163/// A branch between two state elements with variable assignments computed.
164#[derive(Debug)]
165pub struct StateBranch {
166 /// Conversions applied to acquire the branch condition.
167 pub cond_conversion: Vec<Conversion>,
168 /// Variable which will contain the bool branch condition.
169 pub cond: VarId,
170
171 /// Code to execute if the condition is true.
172 pub code_if_true: Box<StateElement>,
173
174 /// Assignments of the pushed values from the true branch. These will be collected
175 /// into a tuple and assigned to the `pushes` of the branch in the outer block.
176 pub true_returns: Vec<VarAssignment>,
177
178 /// Conversions to apply at the end of the true branch to align its types to the false
179 /// branch.
180 pub true_end_conv: Vec<Conversion>,
181
182 /// Code to execute if the condition is false.
183 pub code_if_false: Box<StateElement>,
184
185 /// Conversions to apply at the end of the false branch to align its types to the true
186 /// branch.
187 pub false_end_conv: Vec<Conversion>,
188
189 /// Assignments of the pushed values from the false branch. These will be collected
190 /// into a tuple and assigned to the `pushes` of the branch in the outer block.
191 pub false_returns: Vec<VarAssignment>,
192
193 /// Values pushed onto the parent's stack in the scope outside of the branch.
194 pub pushes: Vec<VarAssignment>,
195}
196
197/// Describes the target of a state change.
198#[derive(Debug, Clone)]
199pub struct StateChange {
200 /// Path that the next state begins at.
201 pub next_state: ElementPathBuf,
202 /// Stack values saved by this state.
203 pub saved_vars: Vec<VarAssignment>,
204}
205
206/// Describes a single state in an instruction.
207#[derive(Debug)]
208pub struct State {
209 /// Sequential state number of this state.
210 pub num: usize,
211 /// Variables which are stored coming into this state.
212 pub stored_stack: Vec<VarAssignment>,
213 /// Element which defines the code to run in this state.
214 pub element: StateElement,
215}
216
217/// The full set of states for an instruction.
218pub struct InstrStates {
219 /// Collection of states used in a single instruction.
220 states: BTreeMap<ElementPathBuf, State>,
221}
222
223impl InstrStates {
224 /// Build InstrStates for the given [`InstrDef`].
225 pub fn new(instr: &InstrDef) -> Self {
226 Self::from_element(instr.flow())
227 }
228
229 /// Break the given root element down into a set of states.
230 fn from_element(root: &Element) -> Self {
231 let mut states: BTreeMap<ElementPathBuf, State> = Default::default();
232
233 let mut traversal = vec![StateChange {
234 next_state: ElementPathBuf::new(),
235 saved_vars: Vec::new(),
236 }];
237
238 let assigner = VarAssigner::default();
239
240 while let Some(prev_transition) = traversal.pop() {
241 if states.contains_key(&prev_transition.next_state) {
242 continue;
243 }
244
245 let mut stack =
246 StackTracker::root_with_stack(&assigner, prev_transition.saved_vars.clone());
247 let element = build_state_from(
248 prev_transition.next_state.clone(),
249 root,
250 &mut stack,
251 &mut traversal,
252 );
253 let state = State {
254 num: states.len(),
255 stored_stack: prev_transition.saved_vars,
256 element,
257 };
258
259 states.insert(prev_transition.next_state, state);
260 }
261
262 Self { states }
263 }
264
265 /// Get a state based on the path where that state starts.
266 #[inline]
267 pub fn get_state(&self, path: &ElementPath) -> Option<&State> {
268 self.states.get(path)
269 }
270
271 /// Get an iterator over the paths where the various states start.
272 pub fn state_starts(
273 &self,
274 ) -> impl Iterator<Item = &ElementPath> + DoubleEndedIterator + ExactSizeIterator + FusedIterator
275 {
276 self.states.keys().map(|path| path.as_path())
277 }
278
279 /// Get an iterator over all of the states along with their starting paths.
280 pub fn states(
281 &self,
282 ) -> impl Iterator<Item = (&ElementPath, &State)>
283 + DoubleEndedIterator
284 + ExactSizeIterator
285 + FusedIterator {
286 self.states.iter().map(|(k, v)| (k.as_path(), v))
287 }
288
289 /// Consume this InstrStates producing an iterator over the paths and states.
290 #[inline]
291 pub fn into_states(self) -> <BTreeMap<ElementPathBuf, State> as IntoIterator>::IntoIter {
292 self.states.into_iter()
293 }
294}
295
296impl IntoIterator for InstrStates {
297 type IntoIter = <BTreeMap<ElementPathBuf, State> as IntoIterator>::IntoIter;
298 type Item = <BTreeMap<ElementPathBuf, State> as IntoIterator>::Item;
299
300 #[inline]
301 fn into_iter(self) -> Self::IntoIter {
302 self.into_states()
303 }
304}
305
306/// Construct a new state starting from the given path. Any time a state transition is
307/// encountered, push the transition onto the `traversal` stack.
308///
309/// This operation can start in the middle of a Branch and will linearize the branch into
310/// its parent block.
311fn build_state_from(
312 mut path: ElementPathBuf,
313 root: &Element,
314 parent_stack: &mut StackTracker,
315 traversal: &mut Vec<StateChange>,
316) -> StateElement {
317 let mut state = StateElement::default();
318 loop {
319 // We don't need to worry about traversing down into children, as build_state_only
320 // will handle that. All this method needs to do is handle traversing back into
321 // parents whenever we operate from an index which is the middle of a branch.
322 state.extend_block(build_state_only(&path, root, parent_stack, traversal));
323 // If we've reached a point where all paths lead to terminals, stop.
324 if state.ends_in_state_change_or_terminal() {
325 break;
326 }
327 path = next_in_block_or_parent(path, root)
328 .expect("Hit end of root element without a terminal");
329 }
330
331 state
332}
333
334/// Build out a state consisting only of the element at `path` and its children. Never
335/// jump back to a parent. If new states are encountered, they are pushed to the
336/// `traversal`.
337fn build_state_only(
338 path: &ElementPath,
339 root: &Element,
340 parent_stack: &mut StackTracker,
341 traversal: &mut Vec<StateChange>,
342) -> StateElement {
343 match &root[path] {
344 Element::Microcode(Microcode::Yield) => {
345 let next_state = next_in_block_or_parent(path.to_buf(), root)
346 .expect("Yield found at end of instruction");
347 let state_change = StateChange {
348 next_state,
349 saved_vars: parent_stack.snapshot(),
350 };
351 traversal.push(state_change.clone());
352 StateElement::ChangeState(state_change)
353 }
354 // Normal microcode: compute assignments for this instruction.
355 Element::Microcode(code) => {
356 StateElement::Microcode(StateMicrocode::build_assignment(*code, parent_stack))
357 }
358 Element::Block(block) => {
359 let mut child_path = path.to_buf();
360 child_path.push(0);
361
362 // For blocks, process as long as the block doesn't hit a state change or
363 // terminal.
364 let mut elements = Vec::with_capacity(block.elements.len());
365 for idx in 0..block.elements.len() {
366 *child_path.last_mut().unwrap() = ElementSelector::Block(idx);
367 let element = build_state_only(&child_path, root, parent_stack, traversal);
368 let stop = element.ends_in_state_change_or_terminal();
369 elements.push(element);
370 if stop {
371 break;
372 }
373 }
374 StateElement::Block(StateBlock { elements })
375 }
376 Element::Branch(_) => {
377 let (cond, cond_conversion) = parent_stack.pop(ValType::Bool);
378
379 let mut child_path = path.to_buf();
380 child_path.push(true);
381
382 let mut true_stack = parent_stack.make_child();
383 let code_if_true = Box::new(build_state_only(
384 &child_path,
385 root,
386 &mut true_stack,
387 traversal,
388 ));
389
390 *child_path.last_mut().unwrap() = ElementSelector::Branch(false);
391 let mut false_stack = parent_stack.make_child();
392 let code_if_false = Box::new(build_state_only(
393 &child_path,
394 root,
395 &mut false_stack,
396 traversal,
397 ));
398
399 let true_terminal_or_yield = code_if_true.ends_in_state_change_or_terminal();
400 let false_terminal_or_yield = code_if_false.ends_in_state_change_or_terminal();
401
402 let mut true_end_conv = vec![];
403 let mut false_end_conv = vec![];
404
405 let pushes = if true_terminal_or_yield && false_terminal_or_yield {
406 // Both branches are terminal or yield, so don't push anything to the
407 // parent stack. Also clear the local stacks so they don't try to return
408 // anything. (Unlike in the direct executor, there may still be things on
409 // the stack, but those things get stored in the new state on a yield).
410 true_stack.local_stack.clear();
411 false_stack.local_stack.clear();
412 vec![]
413 } else if true_terminal_or_yield {
414 // True branch is a terminal operation or yield, but false is not. Take
415 // our pushes from the false stack, and update the parent stack (the
416 // result of our operation) from the end state of the parent of the false
417 // branch.
418 *parent_stack = *false_stack.parent.unwrap();
419 // Also clear the local stack of true so it doesn't try to return
420 // anything. (Unlike in the direct executor, there may still be things on
421 // the stack, but those things get stored in the new state on a yield).
422 true_stack.local_stack.clear();
423 false_stack
424 .local_stack
425 .iter()
426 .map(|asgn| parent_stack.push(asgn.val))
427 .collect()
428 } else if false_terminal_or_yield {
429 // False branch is a terminal operation or yield, but true is not. Take
430 // our pushes from the true stack, and update the parent stack (the result
431 // of our operation) from the end state of the parent of the true branch.
432 *parent_stack = *true_stack.parent.unwrap();
433 // Also clear the local stack of false so it doesn't try to return
434 // anything. (Unlike in the direct executor, there may still be things on
435 // the stack, but those things get stored in the new state on a yield).
436 false_stack.local_stack.clear();
437
438 true_stack
439 .local_stack
440 .iter()
441 .map(|asgn| parent_stack.push(asgn.val))
442 .collect()
443 } else {
444 // Both true and false branches continue to further code.
445
446 // Make the true and false branches have the same return length and types.
447 if true_stack.local_bytes() < false_stack.local_bytes() {
448 // If the true_stack is smaller, pop values off of true_stack matching
449 // false_stack's types to do the type conversion and pull more values from the
450 // parent.
451 let mut true_pushes_rev = Vec::with_capacity(false_stack.local_stack.len());
452 for VarAssignment { val, .. } in false_stack.local_stack.iter().rev().copied() {
453 let (var, conv) = true_stack.pop(val);
454 true_end_conv.extend_from_slice(&conv);
455 true_pushes_rev.push(var);
456 }
457 true_pushes_rev.reverse();
458 assert!(true_stack.local_stack.is_empty());
459 true_stack.local_stack = true_pushes_rev;
460 } else if true_stack.local_bytes() > false_stack.local_bytes() {
461 // If the false_stack's local stack is smaller, pop values off the false_stack
462 // matching true_stacks' types to do type conversion and pull more values from
463 // the parent if needed.
464 let mut false_pushes_rev = Vec::with_capacity(true_stack.local_stack.len());
465 for VarAssignment { val, .. } in true_stack.local_stack.iter().rev().copied() {
466 let (var, conv) = false_stack.pop(val);
467 false_end_conv.extend_from_slice(&conv);
468 false_pushes_rev.push(var);
469 }
470 false_pushes_rev.reverse();
471 assert!(false_stack.local_stack.is_empty());
472 false_stack.local_stack = false_pushes_rev;
473 }
474
475 // Check that we've put both parent stacks in the same state.
476 assert!(true_stack.parent == false_stack.parent);
477 assert!(
478 true_stack
479 .local_stack
480 .iter()
481 .map(|asgn| asgn.val)
482 .collect::<Vec<_>>()
483 == false_stack
484 .local_stack
485 .iter()
486 .map(|asgn| asgn.val)
487 .collect::<Vec<_>>()
488 );
489
490 *parent_stack = *true_stack.parent.unwrap();
491
492 true_stack
493 .local_stack
494 .iter()
495 .map(|asgn| parent_stack.push(asgn.val))
496 .collect()
497 };
498
499 StateElement::Branch(StateBranch {
500 cond_conversion,
501 cond: cond.var,
502 code_if_true,
503 true_end_conv,
504 true_returns: true_stack.local_stack,
505 code_if_false,
506 false_end_conv,
507 false_returns: false_stack.local_stack,
508 pushes,
509 })
510 }
511 }
512}
513
514/// Find the path where the next state begins after the specified path.
515fn next_in_block_or_parent(mut path: ElementPathBuf, root: &Element) -> Option<ElementPathBuf> {
516 // `path` points to a particular element, but what we are actually incrementing
517 // relative to is the parent. Basically we are saying "when `path` is done, what comes
518 // next?" This means that if path points to an entire block, we should go to the next
519 // whole block in the parent.
520 //
521 // We need to know what the parent is because the parent will tell us if we are going
522 // out of bounds or not.
523 //
524 // If this path is already the root path, we are done, since nothing comes after the
525 // root.
526 match &root[path.parent()?] {
527 Element::Microcode(_) => panic!(
528 "Microcode instructions don't have children. Previous path must have been invalid."
529 ),
530 // If the parent is a block, try to increment the last element of the path.
531 // path must have a `last` because it has a parent.
532 Element::Block(block) => {
533 if path.last().unwrap().unwrap_block() + 1 < block.elements.len() {
534 *path.last_mut().unwrap().unwrap_block_mut() += 1;
535 Some(path)
536 } else {
537 path.pop().unwrap();
538 next_in_block_or_parent(path, root)
539 }
540 }
541 // For a branch, we want the code after the branch, so just check the next level
542 // of parent.
543 Element::Branch(_) => {
544 path.pop().unwrap();
545 next_in_block_or_parent(path, root)
546 }
547 }
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553 use crate::opcode::{CBOpcode, InternalFetch, Opcode};
554
555 #[test]
556 fn assign_all_instrs() {
557 InstrStates::new(&InternalFetch.into());
558 for opcode in 0..=0xffu8 {
559 InstrStates::new(&Opcode::decode(opcode).into());
560 }
561 for cbopcode in 0..=0xffu8 {
562 InstrStates::new(&CBOpcode::decode(cbopcode).into());
563 }
564 }
565}