rucc_regalloc/legalize.rs
1//! What an instruction needs around it for the machine to accept the places its operands were
2//! given.
3//!
4//! Design: `spec/optimizer/39-register-allocation.md` section 39.7, the legalization phase, and
5//! tamnd/rucc#1177.
6//!
7//! The assignment says where every value lives, and that is not yet something the machine will
8//! run. A value on the stack has to be read into a register before an instruction that wants it in
9//! one, and an answer written into a register has to be stored to the slot it lives in. An operand
10//! the instruction insists on having in one register has to be moved there and back when its
11//! value lives in another. A two address instruction writes one of the registers it reads, so the
12//! value it reads has to be copied into the register the answer lives in first when the two are
13//! not already the same. This works all of that out for one instruction at a time and hands back
14//! the operands as the machine will read them and the moves either side, in the order they have to
15//! be made in. [`crate::rewrite`] files them, and writes the operands into the function.
16//!
17//! Keeping it apart from the rewrite is what lets it be asked about one instruction and checked
18//! against what comes back, rather than only through the function the whole rewrite produces.
19//!
20//! # How many scratch registers one instruction wants
21//!
22//! Two of a class, and a target holds two of each back for exactly this. The instruction that asks
23//! for most reads two values and writes a third with nothing of the three in a register, and the
24//! arithmetic works out because the two reads are what use the two scratch registers and the answer
25//! is written back into one of them. Writing over it destroys nothing, since it holds a copy of a
26//! value whose home is a stack slot and the instruction has already read it, and the answer is
27//! stored away from it afterwards. Giving the answer a scratch register of its own would want a
28//! third, which a program with enough live values around a call reaches, and that was issue #350.
29//!
30//! Which register the answer goes back into depends on what wrote it. A two address instruction
31//! writes the register the operand it reuses was read into, because that is what two address means.
32//! A three address one, which is `lea` and the compare and set pairs, writes a register that is
33//! none of its operands, and there the answer takes the first scratch register of the class again:
34//! the reads are done by the time the write happens, so the two uses of that register do not meet.
35//! Counting the two jobs in one running number is what made a three address instruction with every
36//! end on the stack ask for a third register and abort, which was issue #726.
37//!
38//! It is only a scratch register the answer may have either way. Where the operand a two address
39//! instruction reuses is in a register the assignment gave out, the value in it may be wanted after
40//! the instruction, and the assignment only lets one be written over when it is not, which it says
41//! by giving the answer that register in the first place. So the answer takes a scratch register
42//! there and the two address copy fills it. That one is filled in front of the instruction rather
43//! than by it, so it cannot share with a read, and the count still comes to two, because an operand
44//! that is in a register is not holding a scratch register.
45//!
46//! Deciding either way needs to know where the operand it reuses went, so an operand that reuses
47//! another and has no register of its own is placed in a second pass over the operands.
48//!
49//! The count is per class. An instruction reading a spilled value out of each of two files wants
50//! the first register of each, since a class holds its own back and nothing on the instruction is
51//! in the other's.
52//!
53//! # What happens when two is not enough after all
54//!
55//! Two runs out on an instruction that reads three registers and writes none, because then there is
56//! no answer to fold back into a register an operand arrived in and the arithmetic above has nothing
57//! to work on. The instruction that does this on x86-64 is the indexed store, whose base, index and
58//! value are three registers it only reads, and at `-O0` all three of them can be stack slots. That
59//! is tamnd/rucc#913, and it stopped brotli and cmocka on the first file that held one.
60//!
61//! What answers it is borrowing: a register of the class the instruction has not named is
62//! taken, whatever is in it is put in a slot of the frame in front of the instruction, and it is
63//! brought back behind it. That asks nothing at all of the register, so it does not matter whether
64//! the value in it is wanted afterwards, whether the callee owes it back, or whether an argument
65//! travels in it, which are the three things that make a register held back hard to find. A third
66//! register held back would cost every function in the program one, and on x86-64 the only one
67//! available is `rax`, which is the return value, so the bill would be a move at every return. This
68//! costs two memory accesses at the one instruction that wanted it and a slot most functions never
69//! take.
70//!
71//! # What a fixed register turns into
72//!
73//! A move each way. The assignment deliberately gave the value some other register, so a division
74//! whose dividend has to be in `rax` gets a move into `rax` in front of it and a move out of `rax`
75//! behind it. That is the cost of the rule the assignment follows, and it is the rule that keeps
76//! the `-O0` allocator one pass.
77//!
78
79use rucc_mir::{Constraint, Func, Inst, Operand, Reg, Role};
80use rucc_target::{PhysReg, RegClass};
81
82use crate::assign::{Assignment, Env, Place};
83use crate::moves::Move;
84
85/// What one instruction becomes.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Legal {
88 /// The operands, each naming the physical register the instruction reads or writes it in.
89 pub operands: Vec<Operand>,
90 /// The moves in front of the instruction, with the class of each, in the order they are made.
91 pub before: Vec<(Move<Place>, RegClass)>,
92 /// The moves behind it, the same way.
93 pub after: Vec<(Move<Place>, RegClass)>,
94}
95
96/// Works out what one instruction's operands are rewritten to and what has to happen either side of
97/// it for the machine to accept the places the assignment gave them.
98///
99/// The assignment is taken by reference and may gain a slot, which is where a register borrowed at
100/// an instruction with more spilled operands than the class holds registers back for waits.
101/// `spare` is the function's list of those slots, shared by every instruction in it.
102///
103/// # Panics
104///
105/// Panics on an instruction naming every register of a class at once, which leaves nothing to
106/// borrow, and on an operand whose register the assignment says nothing about and which is not a
107/// physical register either.
108#[must_use]
109pub fn instruction(
110 func: &Func,
111 assignment: &mut Assignment,
112 env: &Env,
113 spare: &mut Spare,
114 inst: Inst,
115) -> Legal {
116 let list = func[inst].operands;
117 let mut operands: Vec<Operand> = func[list].to_vec();
118 let mut before = Moves::new();
119 let mut after = Moves::new();
120 let mut taken = Taken::new();
121
122 // Where the assignment put each operand's value, taken before anything is rewritten, since
123 // rewriting an operand is what loses that. The second pass below reads it.
124 let places: Vec<Place> =
125 operands.iter().map(|operand| place(assignment, operand.reg)).collect();
126
127 // A spilled operand that reuses another is left for the second pass, because where it goes
128 // depends on where the operand it reuses went and that is not known until every operand ahead
129 // of it has been placed.
130 let mut reusing: Vec<usize> = Vec::new();
131
132 // Every register the instruction has named for itself, which is one an operand's value is
133 // already in and one a fixed constraint asked for. Taken before anything is rewritten, for the
134 // same reason the places above are: rewriting is what turns an operand's register into a
135 // physical one and loses which of the two it was.
136 let mut claimed = Claimed::default();
137 for (operand, place) in operands.iter().zip(&places) {
138 if let Place::Reg(at) = *place {
139 claimed.named(operand, at);
140 }
141 if let Constraint::Fixed(at) = operand.constraint {
142 claimed.named(operand, at);
143 }
144 }
145 let mut scratch = Scratch::new(env, assignment, spare, claimed);
146
147 for (index, operand) in operands.iter_mut().enumerate() {
148 let fixed = match operand.constraint {
149 Constraint::Fixed(at) => Some(at),
150 _ => None,
151 };
152 let at = match (place(scratch.assignment, operand.reg), fixed) {
153 (Place::Reg(at), None) => at,
154 (Place::Reg(at), Some(fixed)) => {
155 if at != fixed {
156 let (there, here) = (Place::Reg(fixed), Place::Reg(at));
157 push(&mut before, &mut after, operand, Move::new(there, here));
158 }
159 fixed
160 }
161 (Place::Slot(_), None) if matches!(operand.constraint, Constraint::Reuse(_)) => {
162 reusing.push(index);
163 continue;
164 }
165 (Place::Slot(slot), fixed) => {
166 // Which of the two jobs this register is for. An operand the instruction only
167 // writes wants one from the instruction onwards, and an operand it reads wants one
168 // from before the instruction until it reads it, so the same register does both
169 // and the two are counted apart.
170 let at = match fixed {
171 Some(fixed) => fixed,
172 None if operand.role.is_def() => {
173 taken.written_into(operand.class, &mut scratch)
174 }
175 None => taken.read_into(operand.class, &mut scratch),
176 };
177 push(
178 &mut before,
179 &mut after,
180 operand,
181 Move::new(Place::Reg(at), Place::Slot(slot)),
182 );
183 at
184 }
185 };
186 operand.reg = Reg::physical(at);
187 }
188
189 for index in reusing {
190 let Constraint::Reuse(other) = operands[index].constraint else {
191 unreachable!("only an operand that reuses another was left for this pass")
192 };
193 let Place::Slot(slot) = places[index] else {
194 unreachable!("only a spilled operand was left for this pass")
195 };
196 // Where the operand it reuses was read into, if it was read into anywhere. A scratch
197 // register holds a copy of a value that lives on the stack, so writing over it destroys
198 // nothing and the instruction can have it. A register the assignment gave out is a
199 // different matter: the value in it may be wanted after the instruction, and the
200 // assignment only lets one be written over when it is not, which it says by giving the
201 // answer that register. So a fresh scratch register there, and the copy below fills it.
202 //
203 // Either way this shape wants two of the class and no more. If the operand it reuses is on
204 // the stack then it is holding one of them already, and if it is not then it is not
205 // holding one at all.
206 //
207 // This one is asked for as a read even though the instruction writes it, because the copy
208 // that fills it goes in front of the instruction. It is live from there, which is the same
209 // span a value read in off the stack is live for, so it cannot share with one.
210 let other = usize::from(other);
211 let at = match places[other] {
212 Place::Slot(_) => phys(operands[other].reg),
213 Place::Reg(_) => taken.read_into(operands[index].class, &mut scratch),
214 };
215 push(
216 &mut before,
217 &mut after,
218 &operands[index],
219 Move::new(Place::Reg(at), Place::Slot(slot)),
220 );
221 operands[index].reg = Reg::physical(at);
222 }
223
224 // A two address instruction writes one of the registers it reads, and the copy that makes that
225 // true goes after everything else in front of the instruction, since what it reads may be a
226 // value that was itself only just read in from the stack.
227 for index in 0..operands.len() {
228 let Constraint::Reuse(other) = operands[index].constraint else { continue };
229 let (to, from) = (operands[index], operands[usize::from(other)]);
230 if to.reg != from.reg {
231 let mov = Move::new(Place::Reg(phys(to.reg)), Place::Reg(phys(from.reg)));
232 before.push((mov, to.class));
233 }
234 }
235
236 // A borrowed register is put away in front of everything else and brought back behind
237 // everything else, since what happens in between is the instruction using it and the moves
238 // that carry its operands in and out. Nothing borrowed at one instruction is still borrowed at
239 // the next, which is what lets the slot be shared.
240 let (saves, restores) = scratch.finish();
241
242 let mut first = saves;
243 first.extend(before);
244 let mut last = after;
245 last.extend(restores);
246 Legal { operands, before: first, after: last }
247}
248
249/// How many scratch registers of each class one instruction has been handed, in each of the two
250/// jobs they do.
251///
252/// Counted per class rather than in one running number, because the classes hold their own back
253/// and an instruction reading a spilled value out of each of two files would otherwise skip the
254/// first register of the second file for no reason.
255///
256/// Counted per job as well, and that is the part that keeps the count down. A register a spilled
257/// value is read into is live from in front of the instruction until the instruction reads it. A
258/// register the instruction writes its answer into is live from the instruction until the store
259/// behind it. Those two spans do not meet, so one register does both jobs and the counting starts
260/// again rather than carrying on. What that rests on is the machine reading its operands before it
261/// writes its answer, which is true of every instruction the backends here emit and is the same
262/// thing that makes `addq %rax, %rax` mean what it looks like.
263///
264/// Where the count runs out is an instruction that reads three registers and writes none, because
265/// then there is no answer to fold back into a register an operand arrived in and the trick above
266/// has nothing to work on. On x86-64 that instruction is the indexed store, whose base, index and
267/// value are three registers it only reads, and at `-O0` all three of them can be stack slots. That
268/// is tamnd/rucc#913, and what answers it is [`Scratch::borrow`] rather than a third register held
269/// back, since holding a third back costs every function a register and this costs only the
270/// instruction that wanted one.
271#[derive(Debug, Default)]
272struct Taken {
273 /// How many of each class hold a value read in ahead of the instruction.
274 read: Vec<usize>,
275 /// How many of each class hold an answer the instruction writes.
276 written: Vec<usize>,
277}
278
279impl Taken {
280 /// Nothing handed out yet.
281 fn new() -> Self {
282 Self::default()
283 }
284
285 /// A register of a class for a value read in ahead of the instruction.
286 fn read_into(&mut self, class: RegClass, scratch: &mut Scratch<'_>) -> PhysReg {
287 Self::take(&mut self.read, class, scratch, Role::Use)
288 }
289
290 /// A register of a class for an answer the instruction writes.
291 fn written_into(&mut self, class: RegClass, scratch: &mut Scratch<'_>) -> PhysReg {
292 Self::take(&mut self.written, class, scratch, Role::Def)
293 }
294
295 /// The next register of a class out of one of the two counts, passing over any the instruction
296 /// has already named itself for a value travelling the same way and borrowing one when the held
297 /// back ones run out.
298 ///
299 /// An operand with a fixed constraint names a register the instruction has to have its value
300 /// in, and the move that puts it there is in the same list as the move that would fill a
301 /// scratch register. So handing the same register out for both would lose one of the two
302 /// values, quietly and at run time. It is passed over instead.
303 ///
304 /// Which way the value travels is what decides whether there is a clash at all, and [`Claimed`]
305 /// says why. A register the instruction only writes is free to carry a value in, which is what a
306 /// call wants: a call names every caller saved register as one it writes, and those are the very
307 /// registers held back for scratch.
308 ///
309 /// A clash comes up on a machine where a register held back is one an instruction can also
310 /// insist on, and on x86-64 the way in is inline assembly naming `r10` or `r11`.
311 fn take(
312 counts: &mut Vec<usize>,
313 class: RegClass,
314 scratch: &mut Scratch<'_>,
315 role: Role,
316 ) -> PhysReg {
317 let index = usize::from(class.number());
318 if counts.len() <= index {
319 counts.resize(index + 1, 0);
320 }
321 let held: &[PhysReg] = scratch.env.scratch(class);
322 while held.get(counts[index]).is_some_and(|®| scratch.claimed.clashes(role, class, reg))
323 {
324 counts[index] += 1;
325 }
326 if let Some(&at) = held.get(counts[index]) {
327 counts[index] += 1;
328 return at;
329 }
330 scratch.borrow(class)
331 }
332}
333
334/// The registers the instruction has named for itself, which scratch has to work around.
335///
336/// A register is kept with the class it was named in, because a register number is only a number
337/// into one file and the same one means a different register in another: a call names sixteen vector
338/// registers numbered nought to fifteen and sixteen general purpose ones numbered the same, and
339/// reading the two lists as one leaves the general purpose file looking entirely spoken for.
340///
341/// Reading and writing are kept apart because they clash with different things. A register a value
342/// arrives in is one no move in front of the instruction may write, and a register an answer leaves
343/// in is one no move behind it may write. A call is the case that makes the difference matter: it
344/// names every caller saved register as one it writes, `r10` and `r11` among them, and an indirect
345/// call through a pointer on the stack has to read that pointer into one of exactly those two.
346#[derive(Debug, Default)]
347struct Claimed {
348 /// The registers a value arrives in, with the class each was named in.
349 reads: Vec<(RegClass, PhysReg)>,
350 /// The registers an answer leaves in, with the class each was named in.
351 writes: Vec<(RegClass, PhysReg)>,
352}
353
354impl Claimed {
355 /// Records a register an operand named, on the side its value travels.
356 fn named(&mut self, operand: &Operand, at: PhysReg) {
357 self.side_mut(operand.role).push((operand.class, at));
358 }
359
360 /// Records a register nothing may be handed for the rest of the instruction, which is one
361 /// [`Scratch::borrow`] has just taken.
362 fn taken(&mut self, class: RegClass, at: PhysReg) {
363 self.reads.push((class, at));
364 self.writes.push((class, at));
365 }
366
367 /// Whether handing that register out for a value travelling that way would lose a value.
368 fn clashes(&self, role: Role, class: RegClass, at: PhysReg) -> bool {
369 self.side(role).contains(&(class, at))
370 }
371
372 /// Whether the instruction names that register at all, which is what borrowing has to keep off:
373 /// what is borrowed is put back behind the instruction, over anything left there.
374 fn names(&self, class: RegClass, at: PhysReg) -> bool {
375 self.reads.contains(&(class, at)) || self.writes.contains(&(class, at))
376 }
377
378 /// The list for values travelling that way. The lists are one instruction's long, so a scan
379 /// beats a set.
380 fn side(&self, role: Role) -> &Vec<(RegClass, PhysReg)> {
381 if role.is_def() { &self.writes } else { &self.reads }
382 }
383
384 /// The same, to write to.
385 fn side_mut(&mut self, role: Role) -> &mut Vec<(RegClass, PhysReg)> {
386 if role.is_def() { &mut self.writes } else { &mut self.reads }
387 }
388}
389
390/// Moves waiting to be filed, each with the class of the value it moves.
391///
392/// The class travels with the move because an [`Edit`] carries one and the consumer needs it to pick
393/// the instruction that does the move, and by the time a move is filed the operand it came from is
394/// out of reach.
395type Moves = Vec<(Move<Place>, RegClass)>;
396
397/// The frame slots a borrowed register's value waits in, one list per class.
398///
399/// They belong to the function rather than to an instruction, because a borrowed register is given
400/// back before the next instruction starts and the slot is dead in between, so one slot serves
401/// every instruction in the function that borrows. Most functions never take one at all.
402pub type Spare = Vec<Vec<u32>>;
403
404/// What it takes to hand a register to one instruction.
405///
406/// It is a struct rather than four arguments because [`Scratch::borrow`] writes to all of them at
407/// once: it reads the environment, takes a slot off the assignment, remembers the register so a
408/// second borrow at the same instruction does not land on it, and files the two moves that make it
409/// safe.
410struct Scratch<'a> {
411 env: &'a Env,
412 /// Where every value went, and where a slot for a borrowed register comes from.
413 assignment: &'a mut Assignment,
414 /// The function's slots for borrowed registers, reused at every instruction.
415 spare: &'a mut Spare,
416 /// Every register the instruction has named, and then every one borrowed here as it is borrowed.
417 claimed: Claimed,
418 /// How many of each class have been borrowed at this instruction, which says which slot the
419 /// next one uses.
420 borrowed: Vec<usize>,
421 /// The moves that put a borrowed register's value away, which go in front of everything else.
422 saves: Moves,
423 /// The moves that bring it back, which go behind everything else.
424 restores: Moves,
425}
426
427impl<'a> Scratch<'a> {
428 /// Nothing borrowed yet at an instruction claiming those registers.
429 fn new(
430 env: &'a Env,
431 assignment: &'a mut Assignment,
432 spare: &'a mut Spare,
433 claimed: Claimed,
434 ) -> Self {
435 Self {
436 env,
437 assignment,
438 spare,
439 claimed,
440 borrowed: Vec::new(),
441 saves: Vec::new(),
442 restores: Vec::new(),
443 }
444 }
445
446 /// A register of the class the instruction is not using, with whatever is in it put away in
447 /// front of the instruction and brought back behind it.
448 ///
449 /// This is what a class runs out to, and it works on any machine because it asks nothing at all
450 /// of the register it takes. Whatever was in it is somewhere else for the length of one
451 /// instruction, so it does not matter whether that value is wanted afterwards, whether the
452 /// callee owes the register back, or whether an argument travels in it, which are the three
453 /// things that make a register held back hard to find. What it costs is two memory accesses at
454 /// the one instruction that wanted it and one slot of the frame, against a register taken off
455 /// every function in the program, and `rucc_codegen::pipeline` says why that trade goes this
456 /// way round on x86-64.
457 ///
458 /// The register is any of the class the instruction has not claimed for itself. A register the
459 /// allocator gave a value that is live right across the instruction is as good as an idle one,
460 /// which is the whole point of putting the contents away first.
461 ///
462 /// # Panics
463 ///
464 /// Panics if the class has no register the instruction has not already claimed, which is an
465 /// instruction naming every register of a file at once.
466 fn borrow(&mut self, class: RegClass) -> PhysReg {
467 let index = usize::from(class.number());
468 let at = *self
469 .env
470 .order(class)
471 .iter()
472 .find(|&®| !self.claimed.names(class, reg))
473 .expect("an instruction naming every register of its class at once");
474
475 if self.borrowed.len() <= index {
476 self.borrowed.resize(index + 1, 0);
477 }
478 if self.spare.len() <= index {
479 self.spare.resize(index + 1, Vec::new());
480 }
481 let nth = self.borrowed[index];
482 if self.spare[index].len() <= nth {
483 let slot = self.assignment.take_slot(class);
484 self.spare[index].push(slot);
485 }
486 let slot = self.spare[index][nth];
487
488 self.borrowed[index] = nth + 1;
489 self.claimed.taken(class, at);
490 self.saves.push((Move::new(Place::Slot(slot), Place::Reg(at)), class));
491 self.restores.push((Move::new(Place::Reg(at), Place::Slot(slot)), class));
492 at
493 }
494
495 /// The moves either side of the instruction, once every register has been handed out.
496 fn finish(self) -> (Moves, Moves) {
497 (self.saves, self.restores)
498 }
499}
500
501/// Files a move in front of the instruction or behind it, and turns it round for a value the
502/// instruction writes, since that one travels the other way.
503fn push(before: &mut Moves, after: &mut Moves, operand: &Operand, mov: Move<Place>) {
504 if operand.role.is_def() {
505 after.push((Move::new(mov.from, mov.to), operand.class));
506 } else {
507 before.push((mov, operand.class));
508 }
509}
510
511/// Where a register is, whether the allocator put it there or it was already somewhere.
512pub(crate) fn place(assignment: &Assignment, reg: Reg) -> Place {
513 assignment.place(reg).unwrap_or_else(|| Place::Reg(phys(reg)))
514}
515
516/// The physical register a register is, once it has to be one.
517pub(crate) fn phys(reg: Reg) -> PhysReg {
518 reg.phys().expect("a register the assignment says nothing about and that is not a register")
519}
520
521#[cfg(test)]
522mod tests {
523 use rucc_base::Interner;
524 use rucc_mir::Opcode;
525 use rucc_target::x86_64::{GPR, RAX, RCX, SYSV};
526
527 use super::*;
528
529 /// Two registers to hand out and two held back after them.
530 fn env() -> Env {
531 Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..4])
532 }
533
534 fn func() -> (Func, Opcode, rucc_mir::Block) {
535 let mut names = Interner::new();
536 let mut func = Func::new(names.intern("f"));
537 let opcode = Opcode::new(names.intern("x64.nop"));
538 let block = func.create_block();
539 (func, opcode, block)
540 }
541
542 fn legal(func: &Func, assignment: &mut Assignment, inst: Inst) -> Legal {
543 instruction(func, assignment, &env(), &mut Spare::new(), inst)
544 }
545
546 fn regs(legal: &Legal) -> Vec<PhysReg> {
547 legal.operands.iter().map(|operand| phys(operand.reg)).collect()
548 }
549
550 #[test]
551 fn an_instruction_whose_values_are_where_it_wants_them_needs_nothing() {
552 let (mut func, opcode, block) = func();
553 let value = func.new_vreg(GPR);
554 let inst = func.build(block, opcode).uses(value, GPR).finish();
555 let mut assignment = Assignment::empty(func.vregs());
556 assignment.put(value, Place::Reg(RCX));
557
558 let legal = legal(&func, &mut assignment, inst);
559 assert_eq!(regs(&legal), [RCX]);
560 assert!(legal.before.is_empty() && legal.after.is_empty());
561 }
562
563 #[test]
564 fn a_register_the_instruction_insists_on_is_filled_in_front_of_it() {
565 let (mut func, opcode, block) = func();
566 let value = func.new_vreg(GPR);
567 let inst = func
568 .build(block, opcode)
569 .operand(Operand::read(value, GPR).with(Constraint::Fixed(RAX)))
570 .finish();
571 let mut assignment = Assignment::empty(func.vregs());
572 assignment.put(value, Place::Reg(RCX));
573
574 let legal = legal(&func, &mut assignment, inst);
575 assert_eq!(regs(&legal), [RAX]);
576 assert_eq!(legal.before, [(Move::new(Place::Reg(RAX), Place::Reg(RCX)), GPR)]);
577 assert!(legal.after.is_empty());
578 }
579
580 #[test]
581 fn an_answer_written_where_it_does_not_live_is_taken_away_behind_it() {
582 let (mut func, opcode, block) = func();
583 let value = func.new_vreg(GPR);
584 let inst = func
585 .build(block, opcode)
586 .operand(Operand::write(value, GPR).with(Constraint::Fixed(RAX)))
587 .finish();
588 let mut assignment = Assignment::empty(func.vregs());
589 assignment.put(value, Place::Reg(RCX));
590
591 let legal = legal(&func, &mut assignment, inst);
592 assert_eq!(regs(&legal), [RAX]);
593 assert!(legal.before.is_empty());
594 assert_eq!(legal.after, [(Move::new(Place::Reg(RCX), Place::Reg(RAX)), GPR)]);
595 }
596
597 #[test]
598 fn a_value_on_the_stack_is_read_into_a_register_held_back() {
599 let (mut func, opcode, block) = func();
600 let value = func.new_vreg(GPR);
601 let inst = func.build(block, opcode).uses(value, GPR).finish();
602 let mut assignment = Assignment::empty(func.vregs());
603 let slot = assignment.take_slot(GPR);
604 assignment.put(value, Place::Slot(slot));
605
606 let legal = legal(&func, &mut assignment, inst);
607 let scratch = env().scratch(GPR)[0];
608 assert_eq!(regs(&legal), [scratch]);
609 assert_eq!(legal.before, [(Move::new(Place::Reg(scratch), Place::Slot(slot)), GPR)]);
610 }
611
612 #[test]
613 fn a_two_address_answer_apart_from_its_source_is_copied_into_first() {
614 let (mut func, opcode, block) = func();
615 let source = func.new_vreg(GPR);
616 let answer = func.new_vreg(GPR);
617 let inst = func
618 .build(block, opcode)
619 .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
620 .uses(source, GPR)
621 .finish();
622 let mut assignment = Assignment::empty(func.vregs());
623 assignment.put(source, Place::Reg(RCX));
624 assignment.put(answer, Place::Reg(RAX));
625
626 let legal = legal(&func, &mut assignment, inst);
627 assert_eq!(regs(&legal), [RAX, RCX]);
628 assert_eq!(legal.before, [(Move::new(Place::Reg(RAX), Place::Reg(RCX)), GPR)]);
629 }
630
631 #[test]
632 fn a_third_value_on_the_stack_borrows_a_register_and_gives_it_back() {
633 let (mut func, opcode, block) = func();
634 let values: Vec<Reg> = (0..3).map(|_| func.new_vreg(GPR)).collect();
635 let build = func.build(block, opcode);
636 let inst = values.iter().fold(build, |build, &value| build.uses(value, GPR)).finish();
637 let mut assignment = Assignment::empty(func.vregs());
638 for &value in &values {
639 let slot = assignment.take_slot(GPR);
640 assignment.put(value, Place::Slot(slot));
641 }
642
643 let legal = legal(&func, &mut assignment, inst);
644 let borrowed = regs(&legal)[2];
645 assert!(!env().scratch(GPR).contains(&borrowed));
646 // The one borrowed is put away first and brought back last, around everything else.
647 let spare = Place::Slot(3);
648 assert_eq!(legal.before[0], (Move::new(spare, Place::Reg(borrowed)), GPR));
649 assert_eq!(legal.after.last(), Some(&(Move::new(Place::Reg(borrowed), spare), GPR)));
650 assert_eq!(assignment.spilled(), 4);
651 }
652}