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}