feo3boy_opcodes/compiler/instr/
builder.rs

1//! Provides the [`InstrBuilder`] for building [`InstrDef`s][InstrDef] as well as helpers
2//! treating values as multiple microcde steps.
3
4use crate::compiler::instr::flow::{Branch, Element};
5use crate::compiler::instr::{InstrDef, InstrId};
6use crate::microcode::Microcode;
7
8/// Builder for microcode.
9#[derive(Default, Debug, Clone)]
10pub struct InstrBuilder {
11    /// Code-flow being built.
12    flow: Element,
13}
14
15impl InstrBuilder {
16    /// Create a new empty builder.
17    pub fn new() -> Self {
18        Default::default()
19    }
20
21    /// Create a new builder with the given initial instructions.
22    pub fn first(code: impl Into<InstrBuilder>) -> Self {
23        code.into()
24    }
25
26    /// Create a builder containing just a yield.
27    pub fn r#yield() -> Self {
28        Self::first(Microcode::Yield)
29    }
30
31    // Create a block of microcode which will pop a single byte boolean off the microcode
32    // stack and then execute one of two branches depending on whether it is nonzero
33    // (true) or zero (false).
34    pub fn cond(
35        code_if_true: impl Into<InstrBuilder>,
36        code_if_false: impl Into<InstrBuilder>,
37    ) -> Self {
38        Self {
39            flow: Element::Branch(Branch {
40                code_if_true: Box::new(code_if_true.into().flow),
41                code_if_false: Box::new(code_if_false.into().flow),
42            }),
43        }
44    }
45
46    /// Pop a single byte boolean off the microcode stack and run this if it is true.
47    pub fn if_true(code_if_true: impl Into<InstrBuilder>) -> Self {
48        InstrBuilder::cond(code_if_true, InstrBuilder::new())
49    }
50
51    /// Pop a single byte boolean off the microcode stack and run this if it is false.
52    pub fn if_false(code_if_false: impl Into<InstrBuilder>) -> Self {
53        InstrBuilder::cond(InstrBuilder::new(), code_if_false)
54    }
55
56    /// Chooses between two branches depending on whether the u8 on top of the stack is
57    /// non-zero.
58    pub fn then_cond(
59        self,
60        code_if_true: impl Into<InstrBuilder>,
61        code_if_false: impl Into<InstrBuilder>,
62    ) -> Self {
63        self.then(InstrBuilder::cond(code_if_true, code_if_false))
64    }
65
66    /// Run the given code if the u8 on top of the stack is non-zero.
67    pub fn then_if_true(self, code_if_true: impl Into<InstrBuilder>) -> Self {
68        self.then_cond(code_if_true, InstrBuilder::new())
69    }
70
71    /// Run the given code if the u8 on top of the stack is zero.
72    pub fn then_if_false(self, code_if_false: impl Into<InstrBuilder>) -> Self {
73        self.then_cond(InstrBuilder::new(), code_if_false)
74    }
75
76    /// Create a single-step microcode that reads from the specified source.
77    pub fn read<R: MicrocodeReadable>(from: R) -> Self {
78        from.to_read()
79    }
80
81    /// Create a single-step microcode that writes to the specified source.
82    pub fn write<W: MicrocodeWritable>(to: W) -> Self {
83        to.to_write()
84    }
85
86    /// Add the given instruction or instructions to the end of this microcode.
87    pub fn then(mut self, code: impl Into<InstrBuilder>) -> Self {
88        let other = code.into();
89        self.flow.extend_block(other.flow);
90        self
91    }
92
93    /// Add a yield to the end of the builder.
94    pub fn then_yield(self) -> Self {
95        self.then(Microcode::Yield)
96    }
97
98    /// Add a read to the end of the builder.
99    pub fn then_read<R: MicrocodeReadable>(self, from: R) -> Self {
100        self.then(from.to_read())
101    }
102
103    /// Add a write to the end of the builder.
104    pub fn then_write<W: MicrocodeWritable>(self, to: W) -> Self {
105        self.then(to.to_write())
106    }
107
108    /// Build an instruction from this microcode, adding the id for the instruction.
109    pub fn build(mut self, id: InstrId) -> InstrDef {
110        add_fetch_next_to_end(&mut self.flow);
111        InstrDef {
112            id,
113            microcode: self.flow.flatten(),
114            flow: self.flow,
115        }
116    }
117}
118
119/// Add a fetch-next to every tail of the instruction.
120fn add_fetch_next_to_end(elem: &mut Element) {
121    match elem {
122        Element::Microcode(microcode) => {
123            match microcode {
124                // If the code is a terminal op, no need to add a fetch.
125                Microcode::FetchNextInstruction
126                | Microcode::ParseOpcode
127                | Microcode::ParseCBOpcode => {}
128                // Add a FetchNextInstruction after all other operations.
129                _ => elem.extend_block(Element::Microcode(Microcode::FetchNextInstruction)),
130            }
131        }
132        Element::Block(block) => match block.elements.last_mut() {
133            Some(last) => {
134                if let Element::Microcode(microcode) = last {
135                    // Avoid creating a nested block.
136                    match microcode {
137                        // If the code is a terminal op, no need to add a fetch.
138                        Microcode::FetchNextInstruction
139                        | Microcode::ParseOpcode
140                        | Microcode::ParseCBOpcode => {}
141                        // Add a FetchNextInstruction after all other operations.
142                        _ => block
143                            .elements
144                            .push(Element::Microcode(Microcode::FetchNextInstruction)),
145                    }
146                } else {
147                    add_fetch_next_to_end(last)
148                }
149            }
150            None => {
151                // If the block is empty, replace it with just the fetch instruction.
152                *elem = Element::Microcode(Microcode::FetchNextInstruction)
153            }
154        },
155        Element::Branch(Branch {
156            code_if_true,
157            code_if_false,
158        }) => {
159            let true_end_terminal = code_if_true.ends_with_terminal();
160            let false_end_terminal = code_if_false.ends_with_terminal();
161            if !true_end_terminal && !false_end_terminal {
162                // neither branch ends in a terminal, so just append the terminal after
163                // this branch.
164                elem.extend_block(Element::Microcode(Microcode::FetchNextInstruction));
165            } else if !true_end_terminal {
166                // Only true branch lacks a terminal, so put the fetch next there.
167                add_fetch_next_to_end(code_if_true);
168            } else if !false_end_terminal {
169                // Only false branch lacks a terminal, so put the fetch next there.
170                add_fetch_next_to_end(code_if_false);
171            }
172            // Both branches already had a terminal, no need to add a fetch.
173        }
174    }
175}
176
177impl<T: Into<InstrBuilder>> From<Option<T>> for InstrBuilder {
178    fn from(value: Option<T>) -> Self {
179        value.map(Into::into).unwrap_or_default()
180    }
181}
182
183impl From<Microcode> for InstrBuilder {
184    fn from(value: Microcode) -> Self {
185        if let Microcode::Skip { .. } | Microcode::SkipIf { .. } = value {
186            panic!("InstrBuilder does not permit explicit skips. Use `cond`.");
187        }
188
189        Self {
190            flow: Element::Microcode(value),
191        }
192    }
193}
194
195/// Allows a type that represents a target that can be read or written to be used with
196/// [`.read`](InstrBuilder::read) or [`.then_read`][InstrBuilder::then_read].
197pub trait MicrocodeReadable {
198    fn to_read(self) -> InstrBuilder;
199}
200
201/// Allows a type that represents a target that can be read or written to be used with
202/// [`.write`](InstrBuilder::write) or [`.then_write`][InstrBuilder::then_write].
203pub trait MicrocodeWritable {
204    fn to_write(self) -> InstrBuilder;
205}