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}