Skip to main content

fidget_core/compiler/
alloc.rs

1use crate::compiler::{Lru, RegOp, RegTape, SsaOp};
2
3#[derive(Copy, Clone, Debug)]
4enum Allocation {
5    Register(u8),
6    Memory(u32),
7    Unassigned,
8}
9
10const UNASSIGNED: u32 = u32::MAX;
11
12/// Cheap and cheerful single-pass register allocation
13pub struct RegisterAllocator<const N: usize> {
14    /// Map from the index in the original (globally allocated) tape to a
15    /// specific register or memory slot.
16    ///
17    /// Unallocated slots are marked with `UNASSIGNED` (`u32::MAX`); allocated
18    /// slots have the value of their register or memory slot (which are both
19    /// integers; the dividing point is based on register count).
20    allocations: Vec<u32>,
21
22    /// Map from a particular register to the index in the original tape that's
23    /// using that register, or `UNASSIGNED` (`u32::MAX`) if the register is
24    /// currently unused.
25    ///
26    /// The inner `u32` here is an index into the original (SSA) tape
27    registers: [u32; N],
28
29    /// Stores a least-recently-used list of registers
30    ///
31    /// Registers are indexed by their value in the output tape
32    register_lru: Lru<N>,
33
34    /// Available short registers (index < N)
35    ///
36    /// The most recently available is at the back
37    spare_registers: Vec<u8>,
38
39    /// Available extended registers (index >= N)
40    ///
41    /// The most recently available is at the back of the `Vec`
42    spare_memory: Vec<u32>,
43
44    /// Output slots, assembled in reverse order
45    out: RegTape,
46}
47
48impl<const N: usize> RegisterAllocator<N> {
49    /// Builds a new `RegisterAllocator`.
50    pub fn new(size: usize) -> Self {
51        assert!(N <= u8::MAX as usize);
52        Self {
53            allocations: vec![UNASSIGNED; size],
54
55            registers: [UNASSIGNED; N],
56            register_lru: Lru::new(),
57
58            spare_registers: (0..N as u8).rev().collect(),
59            spare_memory: Vec::with_capacity(1024),
60
61            out: RegTape::empty(),
62        }
63    }
64
65    /// Build a new empty register allocator
66    pub fn empty() -> Self {
67        Self {
68            allocations: vec![],
69
70            registers: [UNASSIGNED; N],
71            register_lru: Lru::new(),
72
73            spare_registers: (0..N as u8).rev().collect(),
74            spare_memory: vec![],
75
76            out: RegTape::empty(),
77        }
78    }
79
80    /// Resets internal state, reusing allocations and the provided tape
81    ///
82    /// This must be called after the allocator is finalized (removing the
83    /// internal [`RegTape`]).
84    ///
85    /// # Panics
86    /// If the internal tape is not empty
87    pub fn reset(&mut self, size: usize, tape: RegTape) {
88        assert!(self.out.is_empty());
89        self.allocations.fill(UNASSIGNED);
90        self.allocations.resize(size, UNASSIGNED);
91        self.registers.fill(UNASSIGNED);
92        self.register_lru = Lru::new();
93        self.spare_registers.clear();
94        self.spare_registers.extend((0..N as u8).rev());
95        self.spare_memory.clear();
96        self.out = tape;
97        self.out.reset();
98    }
99
100    /// Claims the internal [`RegTape`], leaving the allocator empty
101    #[inline]
102    pub fn finalize(&mut self) -> RegTape {
103        std::mem::take(&mut self.out)
104    }
105
106    /// Returns an available memory slot.
107    ///
108    /// Memory is treated as unlimited; if we don't have any spare slots, then
109    /// we'll assign a new one (incrementing `self.total_slots`).
110    ///
111    /// > If there's one thing I love  
112    /// > It's an infinite resource  
113    /// > If there's one thing worth loving  
114    /// > It's a surplus of supplies
115    #[inline]
116    fn get_memory(&mut self) -> u32 {
117        if let Some(p) = self.spare_memory.pop() {
118            p
119        } else {
120            let out = self.out.slot_count;
121            self.out.slot_count += 1;
122            assert!(out as usize >= N);
123            out
124        }
125    }
126
127    /// Finds the oldest register
128    ///
129    /// This is useful when deciding which register to evict to make room
130    #[inline]
131    fn oldest_reg(&mut self) -> u8 {
132        self.register_lru.pop()
133    }
134
135    /// Returns the slot allocated to the given node
136    ///
137    /// The input is an SSA assignment (i.e. an assignment in the input
138    /// `SsaTape`)
139    ///
140    /// If the output is a register, then it's poked to update recency
141    #[inline]
142    fn get_allocation(&mut self, n: u32) -> Allocation {
143        match self.allocations[n as usize] {
144            i if i < N as u32 => {
145                self.register_lru.poke(i as u8);
146                Allocation::Register(i as u8)
147            }
148            UNASSIGNED => Allocation::Unassigned,
149            i => Allocation::Memory(i),
150        }
151    }
152
153    /// Return an unoccupied register, if available
154    #[inline]
155    fn get_spare_register(&mut self) -> Option<u8> {
156        let r = self.spare_registers.pop()?;
157        self.out.slot_count = self.out.slot_count.max(r as u32 + 1);
158        Some(r)
159    }
160
161    #[inline]
162    fn get_register(&mut self) -> u8 {
163        if let Some(reg) = self.get_spare_register() {
164            assert_eq!(self.registers[reg as usize], UNASSIGNED);
165            self.register_lru.poke(reg);
166            reg
167        } else {
168            // Slot is in memory, and no spare register is available
169            let reg = self.oldest_reg();
170
171            // Here's where it will go:
172            let mem = self.get_memory();
173
174            // Whoever was previously using you is in for a surprise
175            let prev_node = self.registers[reg as usize];
176            self.allocations[prev_node as usize] = mem;
177
178            // This register is now unassigned
179            self.registers[reg as usize] = UNASSIGNED;
180
181            self.out.push(RegOp::Load(reg, mem));
182            reg
183        }
184    }
185
186    #[inline]
187    fn rebind_register(&mut self, n: u32, reg: u8) {
188        assert!(self.allocations[n as usize] >= N as u32);
189        assert!(self.registers[reg as usize] != UNASSIGNED);
190
191        let prev_node = self.registers[reg as usize];
192        self.allocations[prev_node as usize] = UNASSIGNED;
193
194        // Bind the register, but don't bother poking; whoever got the register
195        // for us is responsible for that step.
196        self.registers[reg as usize] = n;
197        self.allocations[n as usize] = reg as u32;
198    }
199
200    #[inline]
201    fn bind_register(&mut self, n: u32, reg: u8) {
202        assert!(self.allocations[n as usize] >= N as u32);
203        assert!(self.registers[reg as usize] == UNASSIGNED);
204
205        // Bind the register, but don't bother poking; whoever got the register
206        // for us is responsible for that step.
207        self.registers[reg as usize] = n;
208        self.allocations[n as usize] = reg as u32;
209    }
210
211    /// Release a register back to the pool of spares
212    #[inline]
213    fn release_reg(&mut self, reg: u8) {
214        // Release the output register, so it could be used for inputs
215        assert!((reg as usize) < N);
216
217        let node = self.registers[reg as usize];
218        assert!(node != UNASSIGNED);
219
220        self.registers[reg as usize] = UNASSIGNED;
221        self.spare_registers.push(reg);
222        // Modifying self.allocations isn't strictly necessary, but could help
223        // us detect logical errors (since it should never be used after this)
224        self.allocations[node as usize] = UNASSIGNED;
225    }
226
227    #[inline]
228    fn release_mem(&mut self, mem: u32) {
229        assert!(mem >= N as u32);
230        self.spare_memory.push(mem);
231        // This leaves self.allocations[...] still pointing to the memory slot,
232        // but that's okay, because it should never be used
233    }
234
235    /// Lowers an operation that uses a single register into an
236    /// [`RegOp`], pushing it to the internal tape.
237    ///
238    /// This may also push `Load` or `Store` instructions to the internal tape,
239    /// if there aren't enough spare registers.
240    #[inline(always)]
241    fn op_reg(&mut self, op: SsaOp) {
242        let (out, arg, op): (u32, u32, fn(u8, u8) -> RegOp) = match op {
243            SsaOp::NegReg(out, arg) => (out, arg, RegOp::NegReg),
244            SsaOp::AbsReg(out, arg) => (out, arg, RegOp::AbsReg),
245            SsaOp::RecipReg(out, arg) => (out, arg, RegOp::RecipReg),
246            SsaOp::SqrtReg(out, arg) => (out, arg, RegOp::SqrtReg),
247            SsaOp::SquareReg(out, arg) => (out, arg, RegOp::SquareReg),
248            SsaOp::FloorReg(out, arg) => (out, arg, RegOp::FloorReg),
249            SsaOp::CeilReg(out, arg) => (out, arg, RegOp::CeilReg),
250            SsaOp::RoundReg(out, arg) => (out, arg, RegOp::RoundReg),
251            SsaOp::SinReg(out, arg) => (out, arg, RegOp::SinReg),
252            SsaOp::CosReg(out, arg) => (out, arg, RegOp::CosReg),
253            SsaOp::TanReg(out, arg) => (out, arg, RegOp::TanReg),
254            SsaOp::AsinReg(out, arg) => (out, arg, RegOp::AsinReg),
255            SsaOp::AcosReg(out, arg) => (out, arg, RegOp::AcosReg),
256            SsaOp::AtanReg(out, arg) => (out, arg, RegOp::AtanReg),
257            SsaOp::ExpReg(out, arg) => (out, arg, RegOp::ExpReg),
258            SsaOp::LnReg(out, arg) => (out, arg, RegOp::LnReg),
259            SsaOp::NotReg(out, arg) => (out, arg, RegOp::NotReg),
260            SsaOp::CopyReg(out, arg) => (out, arg, RegOp::CopyReg),
261            SsaOp::RandReg(out, arg) => (out, arg, RegOp::RandReg),
262            _ => panic!("Bad opcode: {op:?}"),
263        };
264        self.op_reg_fn(out, arg, op);
265    }
266
267    /// Allocates the next operation in the tape
268    #[inline(always)]
269    pub fn op(&mut self, op: SsaOp) {
270        match op {
271            SsaOp::Output(reg, i) => self.op_output(reg, i),
272            SsaOp::Input(out, i) => self.op_input(out, i),
273            SsaOp::CopyImm(out, imm) => self.op_copy_imm(out, imm),
274
275            SsaOp::NegReg(..)
276            | SsaOp::AbsReg(..)
277            | SsaOp::RecipReg(..)
278            | SsaOp::SqrtReg(..)
279            | SsaOp::SquareReg(..)
280            | SsaOp::FloorReg(..)
281            | SsaOp::CeilReg(..)
282            | SsaOp::RoundReg(..)
283            | SsaOp::CopyReg(..)
284            | SsaOp::SinReg(..)
285            | SsaOp::CosReg(..)
286            | SsaOp::TanReg(..)
287            | SsaOp::AsinReg(..)
288            | SsaOp::AcosReg(..)
289            | SsaOp::AtanReg(..)
290            | SsaOp::ExpReg(..)
291            | SsaOp::LnReg(..)
292            | SsaOp::NotReg(..)
293            | SsaOp::RandReg(..) => self.op_reg(op),
294
295            SsaOp::AddRegImm(..)
296            | SsaOp::SubRegImm(..)
297            | SsaOp::SubImmReg(..)
298            | SsaOp::MulRegImm(..)
299            | SsaOp::DivRegImm(..)
300            | SsaOp::DivImmReg(..)
301            | SsaOp::AtanImmReg(..)
302            | SsaOp::AtanRegImm(..)
303            | SsaOp::MinRegImm(..)
304            | SsaOp::MaxRegImm(..)
305            | SsaOp::CompareRegImm(..)
306            | SsaOp::CompareImmReg(..)
307            | SsaOp::MixRegImm(..)
308            | SsaOp::MixImmReg(..)
309            | SsaOp::ModRegImm(..)
310            | SsaOp::ModImmReg(..)
311            | SsaOp::AndRegImm(..)
312            | SsaOp::OrRegImm(..) => self.op_reg_imm(op),
313
314            SsaOp::AddRegReg(..)
315            | SsaOp::SubRegReg(..)
316            | SsaOp::MulRegReg(..)
317            | SsaOp::DivRegReg(..)
318            | SsaOp::AtanRegReg(..)
319            | SsaOp::MinRegReg(..)
320            | SsaOp::MaxRegReg(..)
321            | SsaOp::CompareRegReg(..)
322            | SsaOp::MixRegReg(..)
323            | SsaOp::ModRegReg(..)
324            | SsaOp::AndRegReg(..)
325            | SsaOp::OrRegReg(..) => self.op_reg_reg(op),
326        }
327    }
328
329    fn push_store(&mut self, reg: u8, mem: u32) {
330        self.out.push(RegOp::Store(reg, mem));
331        self.release_mem(mem);
332    }
333
334    /// Returns a register that is bound to the given SSA input
335    ///
336    /// If the given SSA input is not already bound to a register, then we
337    /// evict the oldest register using `Self::get_register`, with the
338    /// appropriate set of LOAD/STORE operations.
339    #[inline]
340    fn get_out_reg(&mut self, out: u32) -> u8 {
341        match self.get_allocation(out) {
342            Allocation::Register(r_x) => r_x,
343            Allocation::Memory(m_x) => {
344                // TODO: this could be more efficient with a Swap instruction,
345                // since we know that we're about to free a memory slot.
346                let r_a = self.get_register();
347
348                self.push_store(r_a, m_x);
349                self.bind_register(out, r_a);
350                r_a
351            }
352            Allocation::Unassigned => panic!("Cannot have unassigned output"),
353        }
354    }
355
356    #[inline(always)]
357    fn op_reg_fn(&mut self, out: u32, arg: u32, op: impl Fn(u8, u8) -> RegOp) {
358        // When we enter this function, the output can be assigned to either a
359        // register or memory, and the input can be a register, memory, or
360        // unassigned.  This gives us six unique situations.
361        //
362        //   out | arg | what do?
363        //  ================================================================
364        //   r_x | r_y | r_x = op r_y
365        //       |     |
366        //       |     | Afterwards, r_x is free
367        //  -----|-----|----------------------------------------------------
368        //   r_x | m_y | r_x = op r_a
369        //       |     | store r_a -> m_y
370        //       |     | [load r_a <- m_a]
371        //       |     |
372        //       |     | Afterward, r_x is free and r_a points to the former m_y
373        //  -----|-----|----------------------------------------------------
374        //   r_x |  U  | r_x = op r_x
375        //       |     |
376        //       |     | Afterward, r_x points to the arg
377        //  -----|-----|----------------------------------------------------
378        //
379        //  Cases with the output in memory (m_x) are identical except that they
380        //  include a trailing
381        //
382        //      store r_a -> m_x
383        //      [load r_a <- m_a]
384        //
385        // i.e. storing the value in the assigned memory slot, and then
386        // restoring the previous register value if present (when read forward).
387        let r_x = self.get_out_reg(out);
388        match self.get_allocation(arg) {
389            Allocation::Register(r_y) => {
390                assert!(r_x != r_y);
391                self.out.push(op(r_x, r_y));
392                self.release_reg(r_x);
393            }
394            Allocation::Memory(m_y) => {
395                let r_a = self.get_register();
396                self.push_store(r_a, m_y);
397                self.out.push(op(r_x, r_a));
398                self.release_reg(r_x);
399                self.bind_register(arg, r_a);
400            }
401            Allocation::Unassigned => {
402                self.out.push(op(r_x, r_x));
403                self.rebind_register(arg, r_x);
404            }
405        }
406    }
407
408    /// Lowers a two-register operation into an [`RegOp`], pushing it to the
409    /// internal tape.
410    ///
411    /// Inputs are SSA registers from a [`SsaTape`](crate::compiler::SsaTape),
412    /// i.e. globally addressed.
413    ///
414    /// If there aren't enough spare registers, this may also push `Load` or
415    /// `Store` instructions to the internal tape.  It's trickier than it
416    /// sounds; look at the source code for a table showing all 18 (!) possible
417    /// configurations.
418    #[inline(always)]
419    fn op_reg_reg(&mut self, op: SsaOp) {
420        // Looking at this horrific table, you may be tempted to think "surely
421        // there's a clean abstraction that wraps this up in a few functions".
422        // You may be right, but I spent a few days chasing down terrible memory
423        // load/store ordering bugs, and decided that the brute-force approach
424        // was the right one.
425        //
426        //   out | lhs  | rhs  | what do?
427        //  ================================================================
428        //  r_x  | r_y  | r_z  | r_x = op r_y r_z
429        //       |      |      |
430        //       |      |      | Afterwards, r_x is free
431        //  -----|------|------|----------------------------------------------
432        //  r_x  | m_y  | r_z  | r_x = op r_a r_z
433        //       |      |      | store r_a -> m_y
434        //       |      |      | [load r_a <- m_a]
435        //       |      |      |
436        //       |      |      | Afterwards, r_x is free, r_a points to the
437        //       |      |      | former m_y, and m_y is free
438        //  -----|------|------|----------------------------------------------
439        //  r_x  | r_y  | m_z  | ibid
440        //  -----|------|------|----------------------------------------------
441        //  r_x  | m_y  | m_z  | r_x = op r_a r_b
442        //       |      |      | store r_b -> m_z
443        //       |      |      | [load r_b <- m_b]
444        //       |      |      | store r_a -> m_y
445        //       |      |      | [load r_a <- m_a]
446        //       |      |      |
447        //       |      |      | Afterwards, r_x points to the former m_y, r_a
448        //       |      |      | points to the former m_z, m_y and m_z are free,
449        //       |      |      | [and m_a points to the former r_a]
450        //  -----|------|------|----------------------------------------------
451        //  r_x  | U    | r_z  | r_x = op r_x r_z
452        //       |      |      |
453        //       |      |      | Afterward, r_x points to the lhs
454        //  -----|------|------|----------------------------------------------
455        //  r_x  | r_y  | U    | ibid
456        //  -----|------|------|----------------------------------------------
457        //  r_x  | U    | U    | rx = op r_x r_a
458        //       |      |      | [load r_a <- m_a]
459        //       |      |      |
460        //       |      |      | Afterward, r_x points to the lhs, r_a points to
461        //       |      |      | rhs, [and m_a points to the former r_a]
462        //  -----|------|------|----------------------------------------------
463        //  r_x  | U    | m_z  | r_x = op r_x r_a
464        //       |      |      | store r_a -> m_z
465        //       |      |      | [load r_a <- m_a]
466        //       |      |      |
467        //       |      |      | Afterward, r_x points to the lhs, r_a points to
468        //       |      |      | rhs, m_z is free, [and m_a points to the former
469        //       |      |      | r_a]
470        //  -----|------|------|----------------------------------------------
471        //  r_x  | m_y  | U    | ibid
472        //  =====|======|======|==============================================
473        //
474        //  The operations with the output in the memory slot are identical,
475        //  except that they end with
476        //      store r_o -> m_o
477        //      [load r_o <- m_o]
478        //  (i.e. moving the register to memory immediately, and optionally
479        //  restoring the previous value.  Here's an example:
480        //
481        //  -----|------|------|----------------------------------------------
482        //   m_x | r_y  | r_z  | r_a = op r_y r_z
483        //       |      |      | store r_a -> m_x
484        //       |      |      | [load r_a <- m_a]
485        //       |      |      |
486        //       |      |      | Afterwards, r_a and m_x are free, [m_a points
487        //       |      |      | to the former r_a}
488        //  -----|------|------|----------------------------------------------
489        let (out, lhs, rhs, op): (_, _, _, fn(u8, u8, u8) -> RegOp) = match op {
490            SsaOp::AddRegReg(out, lhs, rhs) => {
491                (out, lhs, rhs, RegOp::AddRegReg)
492            }
493            SsaOp::SubRegReg(out, lhs, rhs) => {
494                (out, lhs, rhs, RegOp::SubRegReg)
495            }
496            SsaOp::MulRegReg(out, lhs, rhs) => {
497                (out, lhs, rhs, RegOp::MulRegReg)
498            }
499            SsaOp::DivRegReg(out, lhs, rhs) => {
500                (out, lhs, rhs, RegOp::DivRegReg)
501            }
502            SsaOp::AtanRegReg(out, lhs, rhs) => {
503                (out, lhs, rhs, RegOp::AtanRegReg)
504            }
505            SsaOp::MinRegReg(out, lhs, rhs) => {
506                (out, lhs, rhs, RegOp::MinRegReg)
507            }
508            SsaOp::MaxRegReg(out, lhs, rhs) => {
509                (out, lhs, rhs, RegOp::MaxRegReg)
510            }
511            SsaOp::CompareRegReg(out, lhs, rhs) => {
512                (out, lhs, rhs, RegOp::CompareRegReg)
513            }
514            SsaOp::ModRegReg(out, lhs, rhs) => {
515                (out, lhs, rhs, RegOp::ModRegReg)
516            }
517            SsaOp::AndRegReg(out, lhs, rhs) => {
518                (out, lhs, rhs, RegOp::AndRegReg)
519            }
520            SsaOp::OrRegReg(out, lhs, rhs) => (out, lhs, rhs, RegOp::OrRegReg),
521            SsaOp::MixRegReg(out, lhs, rhs) => {
522                (out, lhs, rhs, RegOp::MixRegReg)
523            }
524            _ => panic!("Bad opcode: {op:?}"),
525        };
526        let r_x = self.get_out_reg(out);
527        match (self.get_allocation(lhs), self.get_allocation(rhs)) {
528            (Allocation::Register(r_y), Allocation::Register(r_z)) => {
529                self.out.push(op(r_x, r_y, r_z));
530                self.release_reg(r_x);
531            }
532            (Allocation::Memory(m_y), Allocation::Register(r_z)) => {
533                let r_a = self.get_register();
534                self.push_store(r_a, m_y);
535                self.out.push(op(r_x, r_a, r_z));
536                self.release_reg(r_x);
537                self.bind_register(lhs, r_a);
538            }
539            (Allocation::Register(r_y), Allocation::Memory(m_z)) => {
540                let r_a = self.get_register();
541                self.push_store(r_a, m_z);
542                self.out.push(op(r_x, r_y, r_a));
543                self.release_reg(r_x);
544                self.bind_register(rhs, r_a);
545            }
546            (Allocation::Memory(m_y), Allocation::Memory(..)) if lhs == rhs => {
547                let r_a = self.get_register();
548                self.push_store(r_a, m_y);
549                self.out.push(op(r_x, r_a, r_a));
550                self.release_reg(r_x);
551                self.bind_register(lhs, r_a);
552            }
553            (Allocation::Memory(m_y), Allocation::Memory(m_z)) => {
554                let r_a = self.get_register();
555                let r_b = self.get_register();
556
557                self.push_store(r_a, m_y);
558                self.push_store(r_b, m_z);
559                self.out.push(op(r_x, r_a, r_b));
560                self.release_reg(r_x);
561                self.bind_register(lhs, r_a);
562                self.bind_register(rhs, r_b);
563            }
564            (Allocation::Unassigned, Allocation::Register(r_z)) => {
565                self.out.push(op(r_x, r_x, r_z));
566                self.rebind_register(lhs, r_x);
567            }
568            (Allocation::Register(r_y), Allocation::Unassigned) => {
569                self.out.push(op(r_x, r_y, r_x));
570                self.rebind_register(rhs, r_x);
571            }
572            (Allocation::Unassigned, Allocation::Unassigned) if lhs == rhs => {
573                self.out.push(op(r_x, r_x, r_x));
574                self.rebind_register(lhs, r_x);
575            }
576            (Allocation::Unassigned, Allocation::Unassigned) => {
577                let r_a = self.get_register();
578
579                self.out.push(op(r_x, r_x, r_a));
580                self.rebind_register(lhs, r_x);
581                self.bind_register(rhs, r_a);
582            }
583            (Allocation::Unassigned, Allocation::Memory(m_z)) => {
584                let r_a = self.get_register();
585                assert!(r_a != r_x);
586                assert!(lhs != rhs);
587
588                self.push_store(r_a, m_z);
589                self.out.push(op(r_x, r_x, r_a));
590                self.rebind_register(lhs, r_x);
591                self.bind_register(rhs, r_a);
592            }
593            (Allocation::Memory(m_y), Allocation::Unassigned) => {
594                let r_a = self.get_register();
595                assert!(r_a != r_x);
596                assert!(lhs != rhs);
597
598                self.push_store(r_a, m_y);
599                self.out.push(op(r_x, r_a, r_x));
600                self.bind_register(lhs, r_a);
601                self.rebind_register(rhs, r_x);
602            }
603        }
604    }
605
606    /// Lowers a function taking one register and one immediate into an
607    /// [`RegOp`], pushing it to the internal tape.
608    #[inline(always)]
609    fn op_reg_imm(&mut self, op: SsaOp) {
610        let (out, arg, imm, op): (_, _, _, fn(u8, u8, f32) -> RegOp) = match op
611        {
612            SsaOp::AddRegImm(out, arg, imm) => {
613                (out, arg, imm, RegOp::AddRegImm)
614            }
615            SsaOp::SubRegImm(out, arg, imm) => {
616                (out, arg, imm, RegOp::SubRegImm)
617            }
618            SsaOp::SubImmReg(out, arg, imm) => {
619                (out, arg, imm, RegOp::SubImmReg)
620            }
621            SsaOp::MulRegImm(out, arg, imm) => {
622                (out, arg, imm, RegOp::MulRegImm)
623            }
624            SsaOp::DivRegImm(out, arg, imm) => {
625                (out, arg, imm, RegOp::DivRegImm)
626            }
627            SsaOp::DivImmReg(out, arg, imm) => {
628                (out, arg, imm, RegOp::DivImmReg)
629            }
630            SsaOp::AtanRegImm(out, arg, imm) => {
631                (out, arg, imm, RegOp::AtanRegImm)
632            }
633            SsaOp::AtanImmReg(out, arg, imm) => {
634                (out, arg, imm, RegOp::AtanImmReg)
635            }
636            SsaOp::MinRegImm(out, arg, imm) => {
637                (out, arg, imm, RegOp::MinRegImm)
638            }
639            SsaOp::MaxRegImm(out, arg, imm) => {
640                (out, arg, imm, RegOp::MaxRegImm)
641            }
642            SsaOp::CompareRegImm(out, arg, imm) => {
643                (out, arg, imm, RegOp::CompareRegImm)
644            }
645            SsaOp::CompareImmReg(out, arg, imm) => {
646                (out, arg, imm, RegOp::CompareImmReg)
647            }
648            SsaOp::ModRegImm(out, arg, imm) => {
649                (out, arg, imm, RegOp::ModRegImm)
650            }
651            SsaOp::ModImmReg(out, arg, imm) => {
652                (out, arg, imm, RegOp::ModImmReg)
653            }
654            SsaOp::MixRegImm(out, arg, imm) => {
655                (out, arg, imm, RegOp::MixRegImm)
656            }
657            SsaOp::MixImmReg(out, arg, imm) => {
658                (out, arg, imm, RegOp::MixImmReg)
659            }
660            SsaOp::AndRegImm(out, arg, imm) => {
661                (out, arg, imm, RegOp::AndRegImm)
662            }
663            SsaOp::OrRegImm(out, arg, imm) => (out, arg, imm, RegOp::OrRegImm),
664            _ => panic!("Bad opcode: {op:?}"),
665        };
666        self.op_reg_fn(out, arg, |out, arg| op(out, arg, imm));
667    }
668
669    #[inline(always)]
670    fn op_out_only(&mut self, out: u32, op: impl Fn(u8) -> RegOp) {
671        let r_x = self.get_out_reg(out);
672        self.out.push(op(r_x));
673        self.release_reg(r_x);
674    }
675
676    /// Pushes a [`CopyImm`](crate::compiler::RegOp::CopyImm) operation to the
677    /// tape
678    #[inline(always)]
679    fn op_copy_imm(&mut self, out: u32, imm: f32) {
680        self.op_out_only(out, |out| RegOp::CopyImm(out, imm));
681    }
682
683    /// Pushes an [`Input`](crate::compiler::RegOp::Input) operation to the tape
684    #[inline(always)]
685    fn op_input(&mut self, out: u32, i: u32) {
686        self.op_out_only(out, |out| RegOp::Input(out, i));
687    }
688
689    /// Pushes an [`Output`](crate::compiler::RegOp::Output) operation to the
690    /// tape
691    #[inline(always)]
692    fn op_output(&mut self, arg: u32, i: u32) {
693        match self.get_allocation(arg) {
694            Allocation::Register(r_y) => self.out.push(RegOp::Output(r_y, i)),
695            Allocation::Memory(m_y) => {
696                let r_a = self.get_register();
697                self.push_store(r_a, m_y);
698                self.out.push(RegOp::Output(r_a, i));
699                self.bind_register(arg, r_a);
700            }
701            Allocation::Unassigned => {
702                let r_a = self.get_register();
703                self.out.push(RegOp::Output(r_a, i));
704                self.bind_register(arg, r_a);
705            }
706        }
707    }
708}