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}