feo3boy_opcodes/compiler/
direct_executor_generation.rs

1//! Provides tools for generating the Direct Executor.
2
3use crate::compiler::args::Arg;
4use crate::compiler::instr::flow::{Block, Branch, Element};
5use crate::compiler::variables::{Conversion, StackTracker, VarAssigner, VarAssignment, VarId};
6use crate::microcode::{Microcode, ValType};
7
8use super::instr::InstrDef;
9
10/// [`Element`] with variable assignments and type conversions computed.
11#[derive(Debug)]
12pub enum FuncElement {
13    /// A microcode instruction with variable assignments.
14    Microcode(AssignedMicrocode),
15    /// A code block with variable assignments computed.
16    Block(AssignedBlock),
17    /// A branch with variable assignments.
18    Branch(AssignedBranch),
19}
20
21impl FuncElement {
22    /// Construct a root Funcelement from an InstrDef.
23    pub fn new(instr: InstrDef) -> Self {
24        let assigner = VarAssigner::default();
25        let mut stack = StackTracker::new_root(&assigner);
26        let assigned = FuncElement::build_assignment(instr.flow(), &mut stack);
27        assert!(
28            stack.total_bytes() == 0,
29            "Stack was not empty at the end of instruction execution."
30        );
31        assigned
32    }
33
34    /// Assign variables for this FuncElement.
35    pub fn build_assignment(elem: &Element, parent_stack: &mut StackTracker) -> Self {
36        match elem {
37            Element::Microcode(microcode) => FuncElement::Microcode(
38                AssignedMicrocode::build_assignment(*microcode, parent_stack),
39            ),
40            Element::Block(block) => {
41                FuncElement::Block(AssignedBlock::build_assignment(block, parent_stack))
42            }
43            Element::Branch(branch) => {
44                FuncElement::Branch(AssignedBranch::build_assignment(branch, parent_stack))
45            }
46        }
47    }
48}
49
50/// Microcode with required type conversions and variable assignments provided.
51#[derive(Debug)]
52pub struct AssignedMicrocode {
53    /// The microcode to execute.
54    pub microcode: Microcode,
55    /// Type conversions to apply before running the microcode for this operation.
56    pub conversions: Vec<Conversion>,
57    /// Arguments to the microcode function, with the variables they will be pulled from.
58    pub args: Vec<Arg<VarAssignment>>,
59    /// Variables that will be assigned by the result.
60    pub returns: Vec<VarAssignment>,
61}
62
63impl AssignedMicrocode {
64    /// Assign variables for this microcode instruction. Result variable assignments will
65    /// be applied directly to the parent stack since Microcode does not produce a new
66    /// scope.
67    fn build_assignment(microcode: Microcode, parent_stack: &mut StackTracker) -> Self {
68        let desc = microcode.descriptor();
69        let mut conversions = vec![];
70        let args = desc
71            .args
72            .into_iter()
73            .map(|arg| match arg {
74                Arg::Literal(lit) => Arg::Literal(lit),
75                Arg::StackValue(val) => {
76                    let (assignment, c) = parent_stack.pop(val);
77                    conversions.extend_from_slice(&c);
78                    Arg::StackValue(assignment)
79                }
80            })
81            .collect();
82        let returns = desc
83            .returns
84            .into_iter()
85            .map(|ret| parent_stack.push(ret))
86            .collect();
87
88        Self {
89            microcode,
90            conversions,
91            args,
92            returns,
93        }
94    }
95}
96
97/// A "block" of func elements with variable assignments computed. Note that this block
98/// does not create a new scope, unlike a Rust `{` block `}`.
99#[derive(Debug)]
100pub struct AssignedBlock {
101    /// Assigned elements.
102    pub elements: Vec<FuncElement>,
103}
104
105impl AssignedBlock {
106    fn build_assignment(block: &Block, parent_stack: &mut StackTracker) -> Self {
107        let elements = block
108            .elements
109            .iter()
110            .map(|elem| FuncElement::build_assignment(elem, parent_stack))
111            .collect();
112
113        Self { elements }
114    }
115}
116
117#[derive(Debug)]
118pub struct AssignedBranch {
119    /// Conversions applied to acquire the branch condition.
120    pub cond_conversion: Vec<Conversion>,
121    /// Variable which will contain the bool branch condition.
122    pub cond: VarId,
123
124    /// Code to execute if the condition is true.
125    pub code_if_true: Box<FuncElement>,
126
127    /// Assignments of the pushed values from the true branch. These will be collected
128    /// into a tuple and assigned to the `pushes` of the branch in the outer block.
129    pub true_returns: Vec<VarAssignment>,
130
131    /// Conversions to apply at the end of the true branch to align its types to the false
132    /// branch.
133    pub true_end_conv: Vec<Conversion>,
134
135    /// Code to execute if the condition is false.
136    pub code_if_false: Box<FuncElement>,
137
138    /// Conversions to apply at the end of the false branch to align its types to the true
139    /// branch.
140    pub false_end_conv: Vec<Conversion>,
141
142    /// Assignments of the pushed values from the false branch. These will be collected
143    /// into a tuple and assigned to the `pushes` of the branch in the outer block.
144    pub false_returns: Vec<VarAssignment>,
145
146    /// Values pushed onto the parent's stack in the scope outside of the branch.
147    pub pushes: Vec<VarAssignment>,
148}
149
150impl AssignedBranch {
151    fn build_assignment(branch: &Branch, parent_stack: &mut StackTracker) -> Self {
152        let (cond, cond_conversion) = parent_stack.pop(ValType::Bool);
153
154        let mut true_stack = parent_stack.make_child();
155        let code_if_true = Box::new(FuncElement::build_assignment(
156            &branch.code_if_true,
157            &mut true_stack,
158        ));
159
160        let mut false_stack = parent_stack.make_child();
161        let code_if_false = Box::new(FuncElement::build_assignment(
162            &branch.code_if_false,
163            &mut false_stack,
164        ));
165
166        assert!(
167            true_stack.total_bytes() == false_stack.total_bytes(),
168            "True and false branches have different byte counts"
169        );
170
171        let true_terminal = branch.code_if_true.ends_with_terminal();
172        let false_terminal = branch.code_if_false.ends_with_terminal();
173
174        let mut true_end_conv = vec![];
175        let mut false_end_conv = vec![];
176        let pushes = if true_terminal && false_terminal {
177            // Both branches are terminals, so there's no result from the `if` in either
178            // case. No need to compute pushes, just check that both stacks are empty.
179            assert!(
180                true_stack.total_bytes() == 0,
181                "Terminal branch has non-empty stack"
182            );
183            assert!(
184                false_stack.total_bytes() == 0,
185                "Terminal branch has non-empty stack"
186            );
187            vec![]
188        } else if true_terminal {
189            // True branch is a terminal operation, but false is not. Take our pushes from
190            // the false stack, and update the parent stack (the result of our operation)
191            // from the end state of the parent of the false branch.
192            assert!(
193                true_stack.total_bytes() == 0,
194                "Terminal branch has non-empty stack"
195            );
196            *parent_stack = *false_stack.parent.unwrap();
197            false_stack
198                .local_stack
199                .iter()
200                .map(|asgn| parent_stack.push(asgn.val))
201                .collect()
202        } else if false_terminal {
203            // False branch is a terminal operation, but true is not. Take our pushes from
204            // the true stack, and update the parent stack (the result of our operation)
205            // from the end state of the parent of the true branch.
206            assert!(
207                false_stack.total_bytes() == 0,
208                "Terminal branch has non-empty stack"
209            );
210            *parent_stack = *true_stack.parent.unwrap();
211            true_stack
212                .local_stack
213                .iter()
214                .map(|asgn| parent_stack.push(asgn.val))
215                .collect()
216        } else {
217            // Both true and false branches continue to further code.
218
219            // Make the true and false branches have the same return length and types.
220            if true_stack.local_bytes() < false_stack.local_bytes() {
221                // If the true_stack is smaller, pop values off of true_stack matching
222                // false_stack's types to do the type conversion and pull more values from the
223                // parent.
224                let mut true_pushes_rev = Vec::with_capacity(false_stack.local_stack.len());
225                for VarAssignment { val, .. } in false_stack.local_stack.iter().rev().copied() {
226                    let (var, conv) = true_stack.pop(val);
227                    true_end_conv.extend_from_slice(&conv);
228                    true_pushes_rev.push(var);
229                }
230                true_pushes_rev.reverse();
231                assert!(true_stack.local_stack.is_empty());
232                true_stack.local_stack = true_pushes_rev;
233            } else if true_stack.local_bytes() > false_stack.local_bytes() {
234                // If the false_stack's local stack is smaller, pop values off the false_stack
235                // matching true_stacks' types to do type conversion and pull more values from
236                // the parent if needed.
237                let mut false_pushes_rev = Vec::with_capacity(true_stack.local_stack.len());
238                for VarAssignment { val, .. } in true_stack.local_stack.iter().rev().copied() {
239                    let (var, conv) = false_stack.pop(val);
240                    false_end_conv.extend_from_slice(&conv);
241                    false_pushes_rev.push(var);
242                }
243                false_pushes_rev.reverse();
244                assert!(false_stack.local_stack.is_empty());
245                false_stack.local_stack = false_pushes_rev;
246            }
247
248            // Check that we've put both parent stacks in the same state.
249            assert!(true_stack.parent == false_stack.parent);
250            assert!(
251                true_stack
252                    .local_stack
253                    .iter()
254                    .map(|asgn| asgn.val)
255                    .collect::<Vec<_>>()
256                    == false_stack
257                        .local_stack
258                        .iter()
259                        .map(|asgn| asgn.val)
260                        .collect::<Vec<_>>()
261            );
262
263            *parent_stack = *true_stack.parent.unwrap();
264
265            true_stack
266                .local_stack
267                .iter()
268                .map(|asgn| parent_stack.push(asgn.val))
269                .collect()
270        };
271
272        Self {
273            cond_conversion,
274            cond: cond.var,
275            code_if_true,
276            true_end_conv,
277            true_returns: true_stack.local_stack,
278            code_if_false,
279            false_end_conv,
280            false_returns: false_stack.local_stack,
281            pushes,
282        }
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289    use crate::opcode::{CBOpcode, InternalFetch, Opcode};
290
291    #[test]
292    fn assign_all_instrs() {
293        FuncElement::new(InternalFetch.into());
294        for opcode in 0..=0xffu8 {
295            FuncElement::new(Opcode::decode(opcode).into());
296        }
297        for cbopcode in 0..=0xffu8 {
298            FuncElement::new(CBOpcode::decode(cbopcode).into());
299        }
300    }
301}