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            _ => panic!("Bad opcode: {op:?}"),
262        };
263        self.op_reg_fn(out, arg, op);
264    }
265
266    /// Allocates the next operation in the tape
267    #[inline(always)]
268    pub fn op(&mut self, op: SsaOp) {
269        match op {
270            SsaOp::Output(reg, i) => self.op_output(reg, i),
271            SsaOp::Input(out, i) => self.op_input(out, i),
272            SsaOp::CopyImm(out, imm) => self.op_copy_imm(out, imm),
273
274            SsaOp::NegReg(..)
275            | SsaOp::AbsReg(..)
276            | SsaOp::RecipReg(..)
277            | SsaOp::SqrtReg(..)
278            | SsaOp::SquareReg(..)
279            | SsaOp::FloorReg(..)
280            | SsaOp::CeilReg(..)
281            | SsaOp::RoundReg(..)
282            | SsaOp::CopyReg(..)
283            | SsaOp::SinReg(..)
284            | SsaOp::CosReg(..)
285            | SsaOp::TanReg(..)
286            | SsaOp::AsinReg(..)
287            | SsaOp::AcosReg(..)
288            | SsaOp::AtanReg(..)
289            | SsaOp::ExpReg(..)
290            | SsaOp::LnReg(..)
291            | SsaOp::NotReg(..) => self.op_reg(op),
292
293            SsaOp::AddRegImm(..)
294            | SsaOp::SubRegImm(..)
295            | SsaOp::SubImmReg(..)
296            | SsaOp::MulRegImm(..)
297            | SsaOp::DivRegImm(..)
298            | SsaOp::DivImmReg(..)
299            | SsaOp::AtanImmReg(..)
300            | SsaOp::AtanRegImm(..)
301            | SsaOp::MinRegImm(..)
302            | SsaOp::MaxRegImm(..)
303            | SsaOp::CompareRegImm(..)
304            | SsaOp::CompareImmReg(..)
305            | SsaOp::ModRegImm(..)
306            | SsaOp::ModImmReg(..)
307            | SsaOp::AndRegImm(..)
308            | SsaOp::OrRegImm(..) => self.op_reg_imm(op),
309
310            SsaOp::AddRegReg(..)
311            | SsaOp::SubRegReg(..)
312            | SsaOp::MulRegReg(..)
313            | SsaOp::DivRegReg(..)
314            | SsaOp::AtanRegReg(..)
315            | SsaOp::MinRegReg(..)
316            | SsaOp::MaxRegReg(..)
317            | SsaOp::CompareRegReg(..)
318            | SsaOp::ModRegReg(..)
319            | SsaOp::AndRegReg(..)
320            | SsaOp::OrRegReg(..) => self.op_reg_reg(op),
321        }
322    }
323
324    fn push_store(&mut self, reg: u8, mem: u32) {
325        self.out.push(RegOp::Store(reg, mem));
326        self.release_mem(mem);
327    }
328
329    /// Returns a register that is bound to the given SSA input
330    ///
331    /// If the given SSA input is not already bound to a register, then we
332    /// evict the oldest register using `Self::get_register`, with the
333    /// appropriate set of LOAD/STORE operations.
334    #[inline]
335    fn get_out_reg(&mut self, out: u32) -> u8 {
336        match self.get_allocation(out) {
337            Allocation::Register(r_x) => r_x,
338            Allocation::Memory(m_x) => {
339                // TODO: this could be more efficient with a Swap instruction,
340                // since we know that we're about to free a memory slot.
341                let r_a = self.get_register();
342
343                self.push_store(r_a, m_x);
344                self.bind_register(out, r_a);
345                r_a
346            }
347            Allocation::Unassigned => panic!("Cannot have unassigned output"),
348        }
349    }
350
351    #[inline(always)]
352    fn op_reg_fn(&mut self, out: u32, arg: u32, op: impl Fn(u8, u8) -> RegOp) {
353        // When we enter this function, the output can be assigned to either a
354        // register or memory, and the input can be a register, memory, or
355        // unassigned.  This gives us six unique situations.
356        //
357        //   out | arg | what do?
358        //  ================================================================
359        //   r_x | r_y | r_x = op r_y
360        //       |     |
361        //       |     | Afterwards, r_x is free
362        //  -----|-----|----------------------------------------------------
363        //   r_x | m_y | r_x = op r_a
364        //       |     | store r_a -> m_y
365        //       |     | [load r_a <- m_a]
366        //       |     |
367        //       |     | Afterward, r_x is free and r_a points to the former m_y
368        //  -----|-----|----------------------------------------------------
369        //   r_x |  U  | r_x = op r_x
370        //       |     |
371        //       |     | Afterward, r_x points to the arg
372        //  -----|-----|----------------------------------------------------
373        //
374        //  Cases with the output in memory (m_x) are identical except that they
375        //  include a trailing
376        //
377        //      store r_a -> m_x
378        //      [load r_a <- m_a]
379        //
380        // i.e. storing the value in the assigned memory slot, and then
381        // restoring the previous register value if present (when read forward).
382        let r_x = self.get_out_reg(out);
383        match self.get_allocation(arg) {
384            Allocation::Register(r_y) => {
385                assert!(r_x != r_y);
386                self.out.push(op(r_x, r_y));
387                self.release_reg(r_x);
388            }
389            Allocation::Memory(m_y) => {
390                let r_a = self.get_register();
391                self.push_store(r_a, m_y);
392                self.out.push(op(r_x, r_a));
393                self.release_reg(r_x);
394                self.bind_register(arg, r_a);
395            }
396            Allocation::Unassigned => {
397                self.out.push(op(r_x, r_x));
398                self.rebind_register(arg, r_x);
399            }
400        }
401    }
402
403    /// Lowers a two-register operation into an [`RegOp`], pushing it to the
404    /// internal tape.
405    ///
406    /// Inputs are SSA registers from a [`SsaTape`](crate::compiler::SsaTape),
407    /// i.e. globally addressed.
408    ///
409    /// If there aren't enough spare registers, this may also push `Load` or
410    /// `Store` instructions to the internal tape.  It's trickier than it
411    /// sounds; look at the source code for a table showing all 18 (!) possible
412    /// configurations.
413    #[inline(always)]
414    fn op_reg_reg(&mut self, op: SsaOp) {
415        // Looking at this horrific table, you may be tempted to think "surely
416        // there's a clean abstraction that wraps this up in a few functions".
417        // You may be right, but I spent a few days chasing down terrible memory
418        // load/store ordering bugs, and decided that the brute-force approach
419        // was the right one.
420        //
421        //   out | lhs  | rhs  | what do?
422        //  ================================================================
423        //  r_x  | r_y  | r_z  | r_x = op r_y r_z
424        //       |      |      |
425        //       |      |      | Afterwards, r_x is free
426        //  -----|------|------|----------------------------------------------
427        //  r_x  | m_y  | r_z  | r_x = op r_a r_z
428        //       |      |      | store r_a -> m_y
429        //       |      |      | [load r_a <- m_a]
430        //       |      |      |
431        //       |      |      | Afterwards, r_x is free, r_a points to the
432        //       |      |      | former m_y, and m_y is free
433        //  -----|------|------|----------------------------------------------
434        //  r_x  | r_y  | m_z  | ibid
435        //  -----|------|------|----------------------------------------------
436        //  r_x  | m_y  | m_z  | r_x = op r_a r_b
437        //       |      |      | store r_b -> m_z
438        //       |      |      | [load r_b <- m_b]
439        //       |      |      | store r_a -> m_y
440        //       |      |      | [load r_a <- m_a]
441        //       |      |      |
442        //       |      |      | Afterwards, r_x points to the former m_y, r_a
443        //       |      |      | points to the former m_z, m_y and m_z are free,
444        //       |      |      | [and m_a points to the former r_a]
445        //  -----|------|------|----------------------------------------------
446        //  r_x  | U    | r_z  | r_x = op r_x r_z
447        //       |      |      |
448        //       |      |      | Afterward, r_x points to the lhs
449        //  -----|------|------|----------------------------------------------
450        //  r_x  | r_y  | U    | ibid
451        //  -----|------|------|----------------------------------------------
452        //  r_x  | U    | U    | rx = op r_x r_a
453        //       |      |      | [load r_a <- m_a]
454        //       |      |      |
455        //       |      |      | Afterward, r_x points to the lhs, r_a points to
456        //       |      |      | rhs, [and m_a points to the former r_a]
457        //  -----|------|------|----------------------------------------------
458        //  r_x  | U    | m_z  | r_x = op r_x r_a
459        //       |      |      | store r_a -> m_z
460        //       |      |      | [load r_a <- m_a]
461        //       |      |      |
462        //       |      |      | Afterward, r_x points to the lhs, r_a points to
463        //       |      |      | rhs, m_z is free, [and m_a points to the former
464        //       |      |      | r_a]
465        //  -----|------|------|----------------------------------------------
466        //  r_x  | m_y  | U    | ibid
467        //  =====|======|======|==============================================
468        //
469        //  The operations with the output in the memory slot are identical,
470        //  except that they end with
471        //      store r_o -> m_o
472        //      [load r_o <- m_o]
473        //  (i.e. moving the register to memory immediately, and optionally
474        //  restoring the previous value.  Here's an example:
475        //
476        //  -----|------|------|----------------------------------------------
477        //   m_x | r_y  | r_z  | r_a = op r_y r_z
478        //       |      |      | store r_a -> m_x
479        //       |      |      | [load r_a <- m_a]
480        //       |      |      |
481        //       |      |      | Afterwards, r_a and m_x are free, [m_a points
482        //       |      |      | to the former r_a}
483        //  -----|------|------|----------------------------------------------
484        let (out, lhs, rhs, op): (_, _, _, fn(u8, u8, u8) -> RegOp) = match op {
485            SsaOp::AddRegReg(out, lhs, rhs) => {
486                (out, lhs, rhs, RegOp::AddRegReg)
487            }
488            SsaOp::SubRegReg(out, lhs, rhs) => {
489                (out, lhs, rhs, RegOp::SubRegReg)
490            }
491            SsaOp::MulRegReg(out, lhs, rhs) => {
492                (out, lhs, rhs, RegOp::MulRegReg)
493            }
494            SsaOp::DivRegReg(out, lhs, rhs) => {
495                (out, lhs, rhs, RegOp::DivRegReg)
496            }
497            SsaOp::AtanRegReg(out, lhs, rhs) => {
498                (out, lhs, rhs, RegOp::AtanRegReg)
499            }
500            SsaOp::MinRegReg(out, lhs, rhs) => {
501                (out, lhs, rhs, RegOp::MinRegReg)
502            }
503            SsaOp::MaxRegReg(out, lhs, rhs) => {
504                (out, lhs, rhs, RegOp::MaxRegReg)
505            }
506            SsaOp::CompareRegReg(out, lhs, rhs) => {
507                (out, lhs, rhs, RegOp::CompareRegReg)
508            }
509            SsaOp::ModRegReg(out, lhs, rhs) => {
510                (out, lhs, rhs, RegOp::ModRegReg)
511            }
512            SsaOp::AndRegReg(out, lhs, rhs) => {
513                (out, lhs, rhs, RegOp::AndRegReg)
514            }
515            SsaOp::OrRegReg(out, lhs, rhs) => (out, lhs, rhs, RegOp::OrRegReg),
516            _ => panic!("Bad opcode: {op:?}"),
517        };
518        let r_x = self.get_out_reg(out);
519        match (self.get_allocation(lhs), self.get_allocation(rhs)) {
520            (Allocation::Register(r_y), Allocation::Register(r_z)) => {
521                self.out.push(op(r_x, r_y, r_z));
522                self.release_reg(r_x);
523            }
524            (Allocation::Memory(m_y), Allocation::Register(r_z)) => {
525                let r_a = self.get_register();
526                self.push_store(r_a, m_y);
527                self.out.push(op(r_x, r_a, r_z));
528                self.release_reg(r_x);
529                self.bind_register(lhs, r_a);
530            }
531            (Allocation::Register(r_y), Allocation::Memory(m_z)) => {
532                let r_a = self.get_register();
533                self.push_store(r_a, m_z);
534                self.out.push(op(r_x, r_y, r_a));
535                self.release_reg(r_x);
536                self.bind_register(rhs, r_a);
537            }
538            (Allocation::Memory(m_y), Allocation::Memory(..)) if lhs == rhs => {
539                let r_a = self.get_register();
540                self.push_store(r_a, m_y);
541                self.out.push(op(r_x, r_a, r_a));
542                self.release_reg(r_x);
543                self.bind_register(lhs, r_a);
544            }
545            (Allocation::Memory(m_y), Allocation::Memory(m_z)) => {
546                let r_a = self.get_register();
547                let r_b = self.get_register();
548
549                self.push_store(r_a, m_y);
550                self.push_store(r_b, m_z);
551                self.out.push(op(r_x, r_a, r_b));
552                self.release_reg(r_x);
553                self.bind_register(lhs, r_a);
554                self.bind_register(rhs, r_b);
555            }
556            (Allocation::Unassigned, Allocation::Register(r_z)) => {
557                self.out.push(op(r_x, r_x, r_z));
558                self.rebind_register(lhs, r_x);
559            }
560            (Allocation::Register(r_y), Allocation::Unassigned) => {
561                self.out.push(op(r_x, r_y, r_x));
562                self.rebind_register(rhs, r_x);
563            }
564            (Allocation::Unassigned, Allocation::Unassigned) if lhs == rhs => {
565                self.out.push(op(r_x, r_x, r_x));
566                self.rebind_register(lhs, r_x);
567            }
568            (Allocation::Unassigned, Allocation::Unassigned) => {
569                let r_a = self.get_register();
570
571                self.out.push(op(r_x, r_x, r_a));
572                self.rebind_register(lhs, r_x);
573                self.bind_register(rhs, r_a);
574            }
575            (Allocation::Unassigned, Allocation::Memory(m_z)) => {
576                let r_a = self.get_register();
577                assert!(r_a != r_x);
578                assert!(lhs != rhs);
579
580                self.push_store(r_a, m_z);
581                self.out.push(op(r_x, r_x, r_a));
582                self.rebind_register(lhs, r_x);
583                self.bind_register(rhs, r_a);
584            }
585            (Allocation::Memory(m_y), Allocation::Unassigned) => {
586                let r_a = self.get_register();
587                assert!(r_a != r_x);
588                assert!(lhs != rhs);
589
590                self.push_store(r_a, m_y);
591                self.out.push(op(r_x, r_a, r_x));
592                self.bind_register(lhs, r_a);
593                self.rebind_register(rhs, r_x);
594            }
595        }
596    }
597
598    /// Lowers a function taking one register and one immediate into an
599    /// [`RegOp`], pushing it to the internal tape.
600    #[inline(always)]
601    fn op_reg_imm(&mut self, op: SsaOp) {
602        let (out, arg, imm, op): (_, _, _, fn(u8, u8, f32) -> RegOp) = match op
603        {
604            SsaOp::AddRegImm(out, arg, imm) => {
605                (out, arg, imm, RegOp::AddRegImm)
606            }
607            SsaOp::SubRegImm(out, arg, imm) => {
608                (out, arg, imm, RegOp::SubRegImm)
609            }
610            SsaOp::SubImmReg(out, arg, imm) => {
611                (out, arg, imm, RegOp::SubImmReg)
612            }
613            SsaOp::MulRegImm(out, arg, imm) => {
614                (out, arg, imm, RegOp::MulRegImm)
615            }
616            SsaOp::DivRegImm(out, arg, imm) => {
617                (out, arg, imm, RegOp::DivRegImm)
618            }
619            SsaOp::DivImmReg(out, arg, imm) => {
620                (out, arg, imm, RegOp::DivImmReg)
621            }
622            SsaOp::AtanRegImm(out, arg, imm) => {
623                (out, arg, imm, RegOp::AtanRegImm)
624            }
625            SsaOp::AtanImmReg(out, arg, imm) => {
626                (out, arg, imm, RegOp::AtanImmReg)
627            }
628            SsaOp::MinRegImm(out, arg, imm) => {
629                (out, arg, imm, RegOp::MinRegImm)
630            }
631            SsaOp::MaxRegImm(out, arg, imm) => {
632                (out, arg, imm, RegOp::MaxRegImm)
633            }
634            SsaOp::CompareRegImm(out, arg, imm) => {
635                (out, arg, imm, RegOp::CompareRegImm)
636            }
637            SsaOp::CompareImmReg(out, arg, imm) => {
638                (out, arg, imm, RegOp::CompareImmReg)
639            }
640            SsaOp::ModRegImm(out, arg, imm) => {
641                (out, arg, imm, RegOp::ModRegImm)
642            }
643            SsaOp::ModImmReg(out, arg, imm) => {
644                (out, arg, imm, RegOp::ModImmReg)
645            }
646            SsaOp::AndRegImm(out, arg, imm) => {
647                (out, arg, imm, RegOp::AndRegImm)
648            }
649            SsaOp::OrRegImm(out, arg, imm) => (out, arg, imm, RegOp::OrRegImm),
650            _ => panic!("Bad opcode: {op:?}"),
651        };
652        self.op_reg_fn(out, arg, |out, arg| op(out, arg, imm));
653    }
654
655    #[inline(always)]
656    fn op_out_only(&mut self, out: u32, op: impl Fn(u8) -> RegOp) {
657        let r_x = self.get_out_reg(out);
658        self.out.push(op(r_x));
659        self.release_reg(r_x);
660    }
661
662    /// Pushes a [`CopyImm`](crate::compiler::RegOp::CopyImm) operation to the
663    /// tape
664    #[inline(always)]
665    fn op_copy_imm(&mut self, out: u32, imm: f32) {
666        self.op_out_only(out, |out| RegOp::CopyImm(out, imm));
667    }
668
669    /// Pushes an [`Input`](crate::compiler::RegOp::Input) operation to the tape
670    #[inline(always)]
671    fn op_input(&mut self, out: u32, i: u32) {
672        self.op_out_only(out, |out| RegOp::Input(out, i));
673    }
674
675    /// Pushes an [`Output`](crate::compiler::RegOp::Output) operation to the
676    /// tape
677    #[inline(always)]
678    fn op_output(&mut self, arg: u32, i: u32) {
679        match self.get_allocation(arg) {
680            Allocation::Register(r_y) => self.out.push(RegOp::Output(r_y, i)),
681            Allocation::Memory(m_y) => {
682                let r_a = self.get_register();
683                self.push_store(r_a, m_y);
684                self.out.push(RegOp::Output(r_a, i));
685                self.bind_register(arg, r_a);
686            }
687            Allocation::Unassigned => {
688                let r_a = self.get_register();
689                self.out.push(RegOp::Output(r_a, i));
690                self.bind_register(arg, r_a);
691            }
692        }
693    }
694}