Skip to main content

feo3boy_opcodes/compiler/
variables.rs

1//! Types for managing variables in generated executor implementations.
2
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5use quote::{format_ident, ToTokens};
6
7use crate::microcode::ValType;
8
9/// Type conversion operation. Enumerates the allowed type conversions.
10#[derive(Debug, Copy, Clone)]
11pub enum Conversion {
12    /// Conversion between types which are the same size.
13    SameSize {
14        from: VarAssignment,
15        to: VarAssignment,
16    },
17    /// Conversion from a U16 to two U8.
18    Split {
19        from: VarId,
20        low: VarId,
21        high: VarId,
22    },
23    /// Conversion from two one-byte values to a single two-byte value.
24    Merge { low: VarId, high: VarId, to: VarId },
25}
26
27#[derive(Default)]
28pub struct VarAssigner {
29    next_var: AtomicUsize,
30}
31
32impl VarAssigner {
33    /// Get the next variable assignment.
34    fn take_next(&self) -> VarId {
35        VarId(self.next_var.fetch_add(1, Ordering::Relaxed))
36    }
37
38    /// Create an assignment with the specified type and the next available variable ID.
39    fn assign(&self, val: ValType) -> VarAssignment {
40        VarAssignment {
41            var: self.take_next(),
42            val,
43        }
44    }
45}
46
47#[derive(Debug, Copy, Clone, Eq, PartialEq)]
48/// ID of a variable.
49pub struct VarId(usize);
50
51/// Generate a variable name from this id.
52impl ToTokens for VarId {
53    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
54        format_ident!("val{}", self.0).to_tokens(tokens)
55    }
56}
57
58/// Tracks the stack of assignments.
59#[derive(Clone)]
60pub struct StackTracker<'a> {
61    /// Stack from the parent.
62    pub parent: Option<Box<StackTracker<'a>>>,
63    /// Variable assigner used for this stack.
64    assigner: &'a VarAssigner,
65    /// Variables assigned in the local space.
66    pub local_stack: Vec<VarAssignment>,
67}
68
69impl<'a> PartialEq for StackTracker<'a> {
70    fn eq(&self, other: &Self) -> bool {
71        self.parent == other.parent
72            && self.assigner as *const _ == other.assigner as *const _
73            && self.local_stack == other.local_stack
74    }
75}
76
77impl<'a> StackTracker<'a> {
78    /// Create a new root StackTracker.
79    pub fn new_root(assigner: &'a VarAssigner) -> Self {
80        Self {
81            parent: None,
82            assigner,
83            local_stack: Vec::new(),
84        }
85    }
86
87    /// Create a stack tracker with a clone of this one as its parent.
88    pub fn make_child(&self) -> Self {
89        Self {
90            parent: Some(Box::new(self.clone())),
91            assigner: self.assigner,
92            local_stack: vec![],
93        }
94    }
95
96    /// Create a new root variable assigner with the specified initial stack.
97    pub fn root_with_stack(assigner: &'a VarAssigner, initial_stack: Vec<VarAssignment>) -> Self {
98        Self {
99            parent: None,
100            assigner,
101            local_stack: initial_stack,
102        }
103    }
104
105    /// Remove a value of the specified type from this stack, returning both the variable
106    /// assignment corresponding to the value as well as any conversions required to get
107    /// the value, in the order they must be performed. Any excess bytes that had to be
108    /// popped from the parent's stack to get down to the correct type but aren't used in
109    /// this value are pushed to the local stack.
110    pub fn pop(&mut self, val: ValType) -> (VarAssignment, Vec<Conversion>) {
111        let (assignment, conversions, extra) = self.pop_with_extra(val);
112        if let Some(extra) = extra {
113            self.local_stack.push(extra);
114        }
115        (assignment, conversions)
116    }
117
118    /// Push a value onto the local stack, returning the variable that it will be assigned
119    /// to.
120    pub fn push(&mut self, val: ValType) -> VarAssignment {
121        let assignment = self.assigner.assign(val);
122        self.local_stack.push(assignment);
123        assignment
124    }
125
126    /// Pop a value from the local stack and parent stacks, returning the assignment for
127    /// the popped value, conversions needed to reach the correct type, any excess byte
128    /// that had to be popped for type conversion, which sould be pushed to the local
129    /// stack at the top level that requested the pop.
130    fn pop_with_extra(
131        &mut self,
132        val: ValType,
133    ) -> (VarAssignment, Vec<Conversion>, Option<VarAssignment>) {
134        // No need to reach into the parent's stack for this one.
135        if val.bytes() <= self.local_bytes() {
136            let top = self.local_stack.pop().unwrap();
137            if top.val.bytes() == val.bytes() {
138                if top.val == val {
139                    (top, vec![], None)
140                } else {
141                    let new_assignment = self.assigner.assign(val);
142                    (
143                        new_assignment,
144                        vec![Conversion::SameSize {
145                            from: top,
146                            to: new_assignment,
147                        }],
148                        None,
149                    )
150                }
151            } else if top.val.bytes() < val.bytes() {
152                assert!(val == ValType::U16);
153                assert!(top.val.bytes() == 1);
154                let second = self.local_stack.pop().unwrap();
155                let mut conversions = vec![];
156                let high = if top.val == ValType::U8 {
157                    top
158                } else {
159                    let converted_top = self.assigner.assign(ValType::U8);
160                    conversions.push(Conversion::SameSize {
161                        from: top,
162                        to: converted_top,
163                    });
164                    converted_top
165                };
166
167                let (low, extra) = match second.val {
168                    ValType::Bool | ValType::Flags => {
169                        let converted_second = self.assigner.assign(ValType::U8);
170                        conversions.push(Conversion::SameSize {
171                            from: top,
172                            to: converted_second,
173                        });
174                        (converted_second, None)
175                    }
176                    ValType::U8 => (second, None),
177                    ValType::U16 => {
178                        let second_low = self.assigner.assign(ValType::U8);
179                        let second_high = self.assigner.assign(ValType::U8);
180                        conversions.push(Conversion::Split {
181                            from: second.var,
182                            low: second_low.var,
183                            high: second_high.var,
184                        });
185                        (second_high, Some(second_low))
186                    }
187                };
188
189                let final_assignment = self.assigner.assign(ValType::U16);
190                conversions.push(Conversion::Merge {
191                    low: low.var,
192                    high: high.var,
193                    to: final_assignment.var,
194                });
195
196                (final_assignment, conversions, extra)
197            } else {
198                assert!(top.val == ValType::U16);
199                assert!(val.bytes() == 1);
200                let mut conversions = Vec::with_capacity(2);
201                let low = self.assigner.assign(ValType::U8);
202                let high = self.assigner.assign(ValType::U8);
203                conversions.push(Conversion::Split {
204                    from: top.var,
205                    low: low.var,
206                    high: high.var,
207                });
208                // We need another step to go from U8 to the correct type.
209                let final_assignment = if val == ValType::U8 {
210                    high
211                } else {
212                    let assignment = self.assigner.assign(val);
213                    conversions.push(Conversion::SameSize {
214                        from: high,
215                        to: assignment,
216                    });
217                    assignment
218                };
219
220                (final_assignment, conversions, Some(low))
221            }
222        } else if let Some(parent) = self.parent.as_deref_mut() {
223            // We need to retrieve all of the bytes for the operation from the parent.
224            if self.local_stack.is_empty() {
225                parent.pop_with_extra(val)
226            } else {
227                let top = self.local_stack.pop().unwrap();
228                assert!(top.val.bytes() == 1);
229                assert!(val == ValType::U16);
230                let mut conversions = vec![];
231                let high = if top.val == ValType::U8 {
232                    top
233                } else {
234                    let high = self.assigner.assign(ValType::U8);
235                    conversions.push(Conversion::SameSize {
236                        from: top,
237                        to: high,
238                    });
239                    high
240                };
241
242                let (low, parent_conversions, extra) = parent.pop_with_extra(ValType::U8);
243                conversions.extend_from_slice(&parent_conversions);
244
245                let final_assignment = self.assigner.assign(ValType::U16);
246                conversions.push(Conversion::Merge {
247                    low: low.var,
248                    high: high.var,
249                    to: final_assignment.var,
250                });
251
252                (final_assignment, conversions, extra)
253            }
254        } else {
255            panic!("Stack underflow -- not enough bytes at this level, and no parent available");
256        }
257    }
258
259    /// Get the number of bytes in the local portion of the stack.
260    pub fn local_bytes(&self) -> usize {
261        self.local_stack
262            .iter()
263            .map(|assignment| assignment.val.bytes())
264            .sum()
265    }
266
267    /// Get the total number of bytes in the stack including the local stack and the
268    /// parent's stack.
269    pub fn total_bytes(&self) -> usize {
270        self.local_bytes()
271            + self
272                .parent
273                .as_deref()
274                .map(|p| p.total_bytes())
275                .unwrap_or_default()
276    }
277
278    /// Get a snapshot of the cumulative stack at this layer + all parents.
279    pub fn snapshot(&self) -> Vec<VarAssignment> {
280        let mut parent_snapshot = self
281            .parent
282            .as_deref()
283            .map(Self::snapshot)
284            .unwrap_or_default();
285        parent_snapshot.extend_from_slice(&self.local_stack);
286        parent_snapshot
287    }
288}
289
290/// A variable assignment number and the type of the microcode value held there.
291#[derive(Debug, Copy, Clone, Eq, PartialEq)]
292pub struct VarAssignment {
293    /// ID of the variable that is assigned.
294    pub var: VarId,
295    /// Type of the value assigned to that variable ID.
296    pub val: ValType,
297}