rucc_codegen/copies.rs
1//! Taking out a move the allocator wrote that puts a value where the machine has it already.
2//!
3//! Design: `spec/optimizer/37-machine-level-optimization.md` sections 37.4 and 37.6, the group of
4//! passes that run after allocation and clean up what the allocator could not.
5//!
6//! The allocator decides one value at a time. It writes a value out when the range it was given a
7//! register for ends, it reads a value back in front of the instruction that wants it, and neither
8//! decision looks at the other. So a value written by one instruction and wanted by the next comes
9//! out as a store and then the load of the same slot on the very next line:
10//!
11//! ```text
12//! movq %r10, 16(%rsp)
13//! movq 16(%rsp), %r10
14//! ```
15//!
16//! The load reads a word the store has just written, into the register the store read it out of,
17//! so the register already holds what the load would put in it. It is a memory access that cannot
18//! change anything, on the two instructions of the pair that are the expensive one.
19//!
20//! The same thing happens with instructions in between. A value spilled once and read back at
21//! three places in a block is three loads of one slot into one scratch register, and the second
22//! and the third are reads of a word that register still holds. A value read back and then written
23//! out again unchanged is a store of a word the slot still holds. A copy into a register that
24//! already holds what it is being given is a copy of nothing. All of them are the same mistake
25//! seen from different sides, which is why they are one pass rather than four: a move is dead when
26//! what it writes to and what it reads from hold the same value already.
27//!
28//! The near miss of the same thing is a load of a slot into a register while a different register
29//! holds that word. Nothing can be removed there, since the word does have to arrive in the
30//! register the load names, but it can come out of the register that has it rather than out of the
31//! frame. That is the second thing this does and the rest of the same knowledge answers it.
32//!
33//! # Why it is not a rule over the instructions
34//!
35//! A store followed by a load of the same address is not on its own a dead load. The same pair of
36//! instructions is what a write to a local variable and a read of it back look like, and when that
37//! variable is `volatile` the read is one the program insisted on and the standard says happens.
38//! Machine IR carries that word now, on the instruction the access was selected from, so the
39//! question could be asked here. It is still the wrong question to build the pass on, because
40//! `volatile` is not the only reason a read of a place the program named has to stay.
41//!
42//! So this does not look for the pattern. [`crate::finish`] records which instruction each of the
43//! allocator's moves became, and this pass only ever takes out one of those. A spill slot belongs
44//! to the allocator, nothing else reads it or writes it, and no part of the program said anything
45//! about it, which is what makes removing a read of one safe when removing a read of a variable is
46//! not.
47//!
48//! A slot can end up sharing its bytes with a local, which [`crate::slots`] arranges, and that does
49//! not change the argument. Two things share a run of the frame only where they are never both
50//! wanted, and a slot between a spill and the read back of it is wanted the whole way, so a store
51//! to the local it shares with cannot fall in that stretch.
52//!
53//! # Why after the whole allocator rather than inside it
54//!
55//! The edits are decided in different places for different reasons, so no one of the decisions is
56//! wrong on its own and there is no place inside the allocator that sees them together. What ends
57//! up between two of them is settled by [`crate::finish`] writing every edit into the function,
58//! which is after the allocator has finished. They are all visible at once here and nowhere
59//! earlier.
60//!
61//! # What a block is walked with
62//!
63//! One map from a place to a number standing for a value, where a place is a register of a class
64//! or a slot of one. Two places with the same number hold the same bits, and that is the whole of
65//! what the pass knows: nothing here has to know what the value is, only that a move of one place
66//! into another with the same number would write what is there.
67//!
68//! The map starts empty at the top of every block, because what a register holds on the way in is
69//! whatever the block before it left, and which block that is depends on the path. A map carried
70//! across edges would be worth something on a straight line of blocks and is a dataflow problem
71//! rather than a walk, which is section 37.4's own answer for why this is the cheap half.
72//!
73//! # What clears it
74//!
75//! A write to a place clears what was there, which is every definition in an operand vector and
76//! the destination of every move.
77//!
78//! A call clears everything. The registers a convention does not preserve are gone across one,
79//! and the ones the arguments travelled in are written down as reads rather than as writes, so
80//! the operand vector of a call does not say what it destroys. That is why the machine
81//! description is asked which instructions are calls rather than the operands being trusted.
82//!
83//! An instruction the description does not name clears everything too, on the same reasoning
84//! backwards: what a pass cannot look up it cannot claim to have read.
85//!
86//! An instruction that writes the stack pointer or the frame pointer clears everything, because a
87//! slot is named by an offset from one of those and a slot at a new address is not the slot the
88//! value was written to. A prologue and an epilogue do this, which costs nothing since neither is
89//! in the middle of anything, and so does the instruction that takes room for an array whose size
90//! is not known until it runs.
91//!
92//! # When a load becomes a copy
93//!
94//! A slot read into a register while no register holds that word is a load and has to stay one. A
95//! slot read into one register while another register holds the same word is a load a copy would
96//! do instead, and a copy is the cheaper of the two on every machine here: it is fewer bytes, it
97//! does not go near the memory unit, and on a machine that renames its registers it often costs
98//! nothing to run at all.
99//!
100//! Which instruction that copy is is the target's answer and not this pass's, and it is the same
101//! answer [`crate::finish`] read to write the load. A class of registers is moved between two
102//! registers by one named instruction and between a register and the frame by two others, and the
103//! three are named together for that class, so asking for the one is asking the description that
104//! produced the other.
105//!
106//! Being named together is also what makes the widths agree. Every entry in the map was put there
107//! by one of the allocator's own moves and every one of those moves a whole register of its class,
108//! so two places holding one value hold it in all of their bytes rather than in a low part that a
109//! wider copy would read past.
110//!
111//! Where several registers hold the word, the lowest numbered of them is the one written. Any of
112//! them would be correct, and the map is a hash map, so writing whichever came out of it first
113//! would make the assembly depend on where the addresses happened to land. A compiler whose output
114//! moves between two runs of one input is one nobody can compare anything against, which is a
115//! worse thing to be than one that picks the second best register.
116//!
117//! # Why it does not go through the change framework
118//!
119//! Because the one question [`crate::changes`] would answer about a removal is one that cannot be
120//! answered here. The framework refuses to take an instruction out while anything still reads a
121//! register it wrote, and it knows that from a count of the reads in the function, which is the
122//! whole answer while every register is written once and is not the answer at all afterwards: the
123//! register a reload writes is physical by the time this runs, the same one is written and read all
124//! over the function about other values, and the count says so. Every removal here would be turned
125//! down.
126//!
127//! What makes these safe is not a count but where the instruction came from. It is one of the
128//! allocator's own moves, [`crate::finish`] wrote it, and the value is in the register already
129//! because an earlier move of the allocator's put it there. That is a reason the framework has no
130//! way to be told, and section 37.2's framework is about the machine's description of itself
131//! rather than about the allocator's, so this keeps its own.
132//!
133//! The framework's own question, whether the target has an instruction of the shape a pass
134//! proposes, is one the rewrite does not have to ask either. The copy it writes is the instruction
135//! the description names for moving a register of that class, so it is an instruction the target
136//! has by where the name came from rather than by a lookup afterwards.
137//!
138//! # What it does not do
139//!
140//! Nothing is propagated. A read of a register that another register is known to equal stays a
141//! read of the register it names. The two things this does are both to one of the allocator's own
142//! moves and to nothing else, for the reason the rest of this is built on: what makes an edit here
143//! safe is that the allocator wrote the instruction and owns the slot, and an instruction a
144//! lowering rule wrote is neither.
145//!
146//! Nothing crosses a block, on either half.
147
148use std::collections::HashMap;
149
150use rucc_base::Interner;
151use rucc_mir::{Block, Func, Inst, Opcode, Reg};
152use rucc_regalloc::assign::Place;
153use rucc_regalloc::rewrite::Edit;
154use rucc_target::{CallRegs, FrameInsts, MachineInsts, PhysReg, RegClass};
155
156use crate::finish::Moves;
157
158/// What one function came to.
159#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
160pub struct Cleaned {
161 /// Moves taken out, because the place each wrote held what it was moving already.
162 pub gone: usize,
163 /// Loads out of the frame written as copies instead, because a register held the word.
164 pub copied: usize,
165}
166
167/// Takes out every move of the allocator's that puts a value where it is already, and reads the
168/// rest out of a register wherever one has the word the frame does.
169///
170/// # Panics
171///
172/// Panics on a move of a class the target did not say how to move, which is the same frame
173/// description [`crate::finish`] wrote the move out of and so is the caller handing this a
174/// function and a target that were not worked out from each other.
175pub fn clean(
176 func: &mut Func,
177 moves: &Moves,
178 machine: &MachineInsts,
179 frame: &FrameInsts,
180 conv: &CallRegs,
181 names: &mut Interner,
182) -> Cleaned {
183 let blocks: Vec<Block> = func.blocks().collect();
184 let mut cleaned = Cleaned::default();
185 for block in blocks {
186 let insts: Vec<Inst> = func.insts(block).collect();
187 let mut holds = Holds::default();
188 let mut gone: Vec<Inst> = Vec::new();
189 for inst in insts {
190 // One of the allocator's own moves, which is the only kind of instruction this edits
191 // and the only kind it learns anything from.
192 if let Some(edit) = moves.at(inst) {
193 if holds.same(edit.class, edit.mov.to, edit.mov.from) {
194 gone.push(inst);
195 cleaned.gone += 1;
196 continue;
197 }
198 // A load of a word a register has. The copy goes where the load was and the load
199 // goes, and what arrives in the register is the same either way, so the map is
200 // told about the move below whichever of the two instructions is left.
201 if let Some((to, from)) = instead(&holds, &edit) {
202 let copy = copy(func, names, frame, edit.class, to, from);
203 func.insert_before(inst, copy);
204 gone.push(inst);
205 cleaned.copied += 1;
206 }
207 holds.moved(edit.class, edit.mov.to, edit.mov.from);
208 continue;
209 }
210 let name = names.resolve(func[inst].opcode.name());
211 if machine.calls(name) || !machine.has(name) {
212 holds.nothing();
213 continue;
214 }
215 let mut addressing = false;
216 for operand in &func[func[inst].operands] {
217 if !operand.role.is_def() {
218 continue;
219 }
220 let Some(reg) = operand.reg.phys() else { continue };
221 addressing |= addresses(conv, operand.class, reg);
222 holds.wrote(operand.class, Place::Reg(reg));
223 }
224 if addressing {
225 holds.nothing();
226 }
227 }
228 for inst in gone {
229 func.remove_inst(inst);
230 }
231 }
232 cleaned
233}
234
235/// Whether that register is one the frame is addressed through, so writing it moves every slot.
236fn addresses(conv: &CallRegs, class: RegClass, reg: PhysReg) -> bool {
237 class == conv.int_class && (reg == conv.stack_pointer || reg == conv.frame_pointer)
238}
239
240/// The two registers a copy would be written between, where this edit is a load out of the frame
241/// of a word some register holds.
242///
243/// `None` for every other edit. A store has nowhere else to go, since the word has to reach the
244/// frame and no machine here writes the frame from anywhere but a register, and a copy between two
245/// registers is already the instruction this would be turning something into.
246fn instead(holds: &Holds, edit: &Edit) -> Option<(PhysReg, PhysReg)> {
247 let (Place::Reg(to), Place::Slot(_)) = (edit.mov.to, edit.mov.from) else { return None };
248 Some((to, holds.register(edit.class, edit.mov.from)?))
249}
250
251/// The instruction that copies a register of that class into another on this target.
252fn copy(
253 func: &mut Func,
254 names: &mut Interner,
255 frame: &FrameInsts,
256 class: RegClass,
257 to: PhysReg,
258 from: PhysReg,
259) -> Inst {
260 let moves = frame.moves(class).expect("a class the target says how to move");
261 let mov = Opcode::new(names.intern(&format!("{}{}", frame.prefix, moves.mov)));
262 func.build_loose(mov).def(Reg::physical(to), class).uses(Reg::physical(from), class).finish()
263}
264
265/// Which places are known to hold the same value as each other, over one block.
266///
267/// A value is a number and nothing more. Where it came from and what it means are questions this
268/// does not ask, because the only thing a move is taken out over is two places holding the same
269/// one.
270#[derive(Debug, Default)]
271struct Holds {
272 /// What is in each place, by the class it is a place of and the place itself. A class is in
273 /// the key because a register is a number inside its class and a slot is a slot of one, so
274 /// number four of one file and number four of another are two places.
275 what: HashMap<(u8, Place), u32>,
276 /// How many values have been named, so the next one is a number no other place holds.
277 named: u32,
278}
279
280impl Holds {
281 /// Whether both places are known to hold one value, which is what makes a move of the one into
282 /// the other write what is there.
283 fn same(&self, class: RegClass, to: Place, from: Place) -> bool {
284 let read = self.what.get(&(class.number(), from));
285 read.is_some() && read == self.what.get(&(class.number(), to))
286 }
287
288 /// A register known to hold what that place holds, and the lowest numbered of them where more
289 /// than one does.
290 ///
291 /// Lowest numbered rather than whichever the map hands back first, because the map is a hash
292 /// map and which entry comes out of one first is a fact about addresses. Reading it would make
293 /// the assembly of one input differ between two runs of the compiler.
294 fn register(&self, class: RegClass, place: Place) -> Option<PhysReg> {
295 let value = *self.what.get(&(class.number(), place))?;
296 self.what
297 .iter()
298 .filter(|&(&(number, _), &held)| number == class.number() && held == value)
299 .filter_map(|(&(_, place), _)| match place {
300 Place::Reg(reg) => Some(reg),
301 Place::Slot(_) => None,
302 })
303 .min_by_key(|reg| reg.number())
304 }
305
306 /// Records a move that ran, so what it wrote holds what it read.
307 ///
308 /// A read of a place nothing is known about is what names a value: the bits are whatever they
309 /// are, the two ends of the move agree about them from here on, and that agreement is the only
310 /// thing this pass ever asks about.
311 fn moved(&mut self, class: RegClass, to: Place, from: Place) {
312 let value = match self.what.get(&(class.number(), from)) {
313 Some(&value) => value,
314 None => {
315 self.named += 1;
316 self.what.insert((class.number(), from), self.named);
317 self.named
318 }
319 };
320 self.what.insert((class.number(), to), value);
321 }
322
323 /// Records that something wrote a place, so whatever it held is no longer what is there.
324 fn wrote(&mut self, class: RegClass, place: Place) {
325 self.what.remove(&(class.number(), place));
326 }
327
328 /// Forgets the block so far, which is the answer to an instruction whose writes cannot all be
329 /// seen.
330 fn nothing(&mut self) {
331 self.what.clear();
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use rucc_mir::{Mem, Opcode, Operand, Reg};
338 use rucc_regalloc::moves::Move;
339 use rucc_regalloc::rewrite::{At, Edit};
340 use rucc_target::x86_64::{FRAME, GPR, MACHINE, RAX, SYSV, XMM};
341
342 use super::*;
343
344 /// A spill register to write with, which is the one x86-64 holds back for exactly this.
345 const R10: PhysReg = PhysReg::new(10);
346
347 /// The second of them, which is what an instruction with both operands on the stack reads the
348 /// other one into.
349 const R11: PhysReg = PhysReg::new(11);
350
351 /// A function with one block in it, and the names it was built with.
352 fn empty() -> (Interner, Func, Block) {
353 let mut names = Interner::new();
354 let mut func = Func::new(names.intern("f"));
355 let block = func.create_block();
356 (names, func, block)
357 }
358
359 /// The pass, with the one target these tests are written against.
360 fn clean(func: &mut Func, moves: &Moves, names: &mut Interner) -> Cleaned {
361 super::clean(func, moves, &MACHINE, &FRAME, &SYSV, names)
362 }
363
364 /// How many moves went, for a test about the half that removes.
365 fn gone(func: &mut Func, moves: &Moves, names: &mut Interner) -> usize {
366 clean(func, moves, names).gone
367 }
368
369 /// The opcode of that name on this target.
370 fn op(names: &mut Interner, name: &str) -> Opcode {
371 Opcode::new(names.intern(&format!("{}{name}", FRAME.prefix)))
372 }
373
374 /// A store of a physical register to a frame address, as [`crate::finish`] writes a spill.
375 fn store(func: &mut Func, names: &mut Interner, block: Block, reg: PhysReg, at: i32) -> Inst {
376 let store = op(names, "mov_mr_64");
377 let base = Operand::read(Reg::physical(RAX), GPR);
378 func.build(block, store).uses(Reg::physical(reg), GPR).mem(Mem::at(base).plus(at)).finish()
379 }
380
381 /// A load of a physical register from a frame address, as it writes a reload.
382 fn load(func: &mut Func, names: &mut Interner, block: Block, reg: PhysReg, at: i32) -> Inst {
383 let load = op(names, "mov_rm_64");
384 let base = Operand::read(Reg::physical(RAX), GPR);
385 func.build(block, load).def(Reg::physical(reg), GPR).mem(Mem::at(base).plus(at)).finish()
386 }
387
388 /// A copy of one physical register into another, as it writes one of the allocator's copies.
389 fn copy(
390 func: &mut Func,
391 names: &mut Interner,
392 block: Block,
393 to: PhysReg,
394 from: PhysReg,
395 ) -> Inst {
396 let copy = op(names, "mov_rr_64");
397 func.build(block, copy).def(Reg::physical(to), GPR).uses(Reg::physical(from), GPR).finish()
398 }
399
400 /// An instruction that reads a register and writes another, which is what a spilled value was
401 /// read back for.
402 fn add(
403 func: &mut Func,
404 names: &mut Interner,
405 block: Block,
406 to: PhysReg,
407 from: PhysReg,
408 ) -> Inst {
409 let add = op(names, "add_rr_64");
410 func.build(block, add).def(Reg::physical(to), GPR).uses(Reg::physical(from), GPR).finish()
411 }
412
413 /// What the allocator asked for, in the two shapes this pass is about.
414 ///
415 /// Where the edit was to go is not read by anything here, since what says two instructions are
416 /// next to each other is the function they were written into rather than what the allocator
417 /// said about where they belong.
418 fn out(block: Block, slot: u32, reg: PhysReg) -> Edit {
419 Edit {
420 at: At::StartOf(block),
421 mov: Move::new(Place::Slot(slot), Place::Reg(reg)),
422 class: GPR,
423 }
424 }
425
426 /// The other direction, and the one a dead reload is.
427 fn back(block: Block, slot: u32, reg: PhysReg) -> Edit {
428 Edit {
429 at: At::StartOf(block),
430 mov: Move::new(Place::Reg(reg), Place::Slot(slot)),
431 class: GPR,
432 }
433 }
434
435 /// A move of one register into another, which the allocator writes where the two ends of a
436 /// value could not be given the same register.
437 fn across(block: Block, to: PhysReg, from: PhysReg) -> Edit {
438 Edit {
439 at: At::StartOf(block),
440 mov: Move::new(Place::Reg(to), Place::Reg(from)),
441 class: GPR,
442 }
443 }
444
445 /// How many instructions a block has left.
446 fn left(func: &Func, block: Block) -> usize {
447 func.insts(block).count()
448 }
449
450 /// What the block says now, one opcode per instruction, which is how a test about a rewrite
451 /// says which instruction came out of it.
452 fn written(func: &Func, block: Block, names: &Interner) -> Vec<String> {
453 func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
454 }
455
456 /// The registers the instruction at that position in the block reads.
457 fn reads(func: &Func, block: Block, at: usize) -> Vec<PhysReg> {
458 let inst = func.insts(block).nth(at).expect("an instruction at that position");
459 func[func[inst].operands]
460 .iter()
461 .filter(|operand| !operand.role.is_def())
462 .filter_map(|operand| operand.reg.phys())
463 .collect()
464 }
465
466 /// The pair the pass started as: a word written out and read straight back into the register it
467 /// was written out of.
468 #[test]
469 fn a_reload_of_the_slot_the_instruction_in_front_of_it_spilled_goes() {
470 let (mut names, mut func, block) = empty();
471 let spill = store(&mut func, &mut names, block, R10, 16);
472 let reload = load(&mut func, &mut names, block, R10, 16);
473 let mut moves = Moves::default();
474 moves.record(spill, out(block, 0, R10));
475 moves.record(reload, back(block, 0, R10));
476
477 assert_eq!(gone(&mut func, &moves, &mut names), 1);
478 assert_eq!(left(&func, block), 1, "the spill went too, or the reload stayed");
479 assert_eq!(func.insts(block).next(), Some(spill));
480 }
481
482 /// Two reads of one word, which is one spill and two reloads, and the second is as dead as the
483 /// first because the register still holds what the first put in it.
484 #[test]
485 fn a_run_of_reloads_of_one_slot_goes_in_a_single_pass() {
486 let (mut names, mut func, block) = empty();
487 let spill = store(&mut func, &mut names, block, R10, 16);
488 let first = load(&mut func, &mut names, block, R10, 16);
489 let second = load(&mut func, &mut names, block, R10, 16);
490 let mut moves = Moves::default();
491 moves.record(spill, out(block, 0, R10));
492 moves.record(first, back(block, 0, R10));
493 moves.record(second, back(block, 0, R10));
494
495 assert_eq!(gone(&mut func, &moves, &mut names), 2);
496 assert_eq!(left(&func, block), 1);
497 }
498
499 /// The case the pass was grown for. What the value was read back for stands between the two
500 /// reloads, and it writes neither the slot nor the register the word is in, so the second read
501 /// is of a word that register still holds.
502 #[test]
503 fn a_reload_with_an_instruction_between_that_writes_neither_end_goes() {
504 let (mut names, mut func, block) = empty();
505 let spill = store(&mut func, &mut names, block, R10, 16);
506 let first = load(&mut func, &mut names, block, R10, 16);
507 add(&mut func, &mut names, block, RAX, R10);
508 let second = load(&mut func, &mut names, block, R10, 16);
509 let mut moves = Moves::default();
510 moves.record(spill, out(block, 0, R10));
511 moves.record(first, back(block, 0, R10));
512 moves.record(second, back(block, 0, R10));
513
514 assert_eq!(gone(&mut func, &moves, &mut names), 2);
515 assert_eq!(left(&func, block), 2, "the spill and the instruction between are the two");
516 }
517
518 /// The instruction between them writes the register this time, which is what a spilled value
519 /// is read back into a scratch register for, so the reload behind it reads a word that
520 /// register no longer holds.
521 #[test]
522 fn a_reload_behind_an_instruction_that_writes_the_register_stays() {
523 let (mut names, mut func, block) = empty();
524 let spill = store(&mut func, &mut names, block, R10, 16);
525 add(&mut func, &mut names, block, R10, RAX);
526 let reload = load(&mut func, &mut names, block, R10, 16);
527 let mut moves = Moves::default();
528 moves.record(spill, out(block, 0, R10));
529 moves.record(reload, back(block, 0, R10));
530
531 assert_eq!(gone(&mut func, &moves, &mut names), 0);
532 assert_eq!(left(&func, block), 3);
533 }
534
535 /// A word read back and written out again with nothing touching either end is a store of what
536 /// the slot holds already.
537 #[test]
538 fn a_spill_of_a_word_the_slot_still_holds_goes() {
539 let (mut names, mut func, block) = empty();
540 let reload = load(&mut func, &mut names, block, R10, 16);
541 let spill = store(&mut func, &mut names, block, R10, 16);
542 let mut moves = Moves::default();
543 moves.record(reload, back(block, 0, R10));
544 moves.record(spill, out(block, 0, R10));
545
546 assert_eq!(gone(&mut func, &moves, &mut names), 1);
547 assert_eq!(left(&func, block), 1);
548 assert_eq!(func.insts(block).next(), Some(reload));
549 }
550
551 /// A copy into a register that holds what it is being given writes what is there.
552 #[test]
553 fn a_copy_of_a_word_the_register_already_holds_goes() {
554 let (mut names, mut func, block) = empty();
555 let first = copy(&mut func, &mut names, block, RAX, R10);
556 let second = copy(&mut func, &mut names, block, RAX, R10);
557 let mut moves = Moves::default();
558 moves.record(first, across(block, RAX, R10));
559 moves.record(second, across(block, RAX, R10));
560
561 assert_eq!(gone(&mut func, &moves, &mut names), 1);
562 assert_eq!(left(&func, block), 1);
563 }
564
565 /// A reload of another slot reads another word, whatever address the two instructions were
566 /// written with.
567 #[test]
568 fn a_reload_of_a_different_slot_stays() {
569 let (mut names, mut func, block) = empty();
570 let spill = store(&mut func, &mut names, block, R10, 16);
571 let reload = load(&mut func, &mut names, block, R10, 24);
572 let mut moves = Moves::default();
573 moves.record(spill, out(block, 0, R10));
574 moves.record(reload, back(block, 1, R10));
575
576 assert_eq!(gone(&mut func, &moves, &mut names), 0);
577 assert_eq!(left(&func, block), 2);
578 }
579
580 /// A reload into another register puts the word somewhere it is not, so the instruction has
581 /// work to do. Where it takes the word from is the question, and the register that was spilled
582 /// still holds it, so the frame is not read.
583 #[test]
584 fn a_reload_into_a_different_register_becomes_a_copy() {
585 let (mut names, mut func, block) = empty();
586 let spill = store(&mut func, &mut names, block, R10, 16);
587 let reload = load(&mut func, &mut names, block, R11, 16);
588 let mut moves = Moves::default();
589 moves.record(spill, out(block, 0, R10));
590 moves.record(reload, back(block, 0, R11));
591
592 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned { gone: 0, copied: 1 });
593 assert_eq!(written(&func, block, &names), ["x64.mov_mr_64", "x64.mov_rr_64"]);
594 assert_eq!(reads(&func, block, 1), vec![R10]);
595 }
596
597 /// The same reload with nothing having put the word in a register. There is nowhere to read it
598 /// from but the frame, so the load is the instruction it was.
599 #[test]
600 fn a_reload_no_register_holds_the_word_of_stays_a_load() {
601 let (mut names, mut func, block) = empty();
602 let reload = load(&mut func, &mut names, block, R11, 16);
603 let mut moves = Moves::default();
604 moves.record(reload, back(block, 0, R11));
605
606 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
607 assert_eq!(written(&func, block, &names), ["x64.mov_rm_64"]);
608 }
609
610 /// Three registers holding one word is three right answers, and the pass takes the lowest
611 /// numbered of them every time rather than whichever the map hands back first.
612 #[test]
613 fn the_copy_is_written_out_of_the_lowest_numbered_register_that_has_the_word() {
614 let (mut names, mut func, block) = empty();
615 let spill = store(&mut func, &mut names, block, R10, 16);
616 let first = copy(&mut func, &mut names, block, R11, R10);
617 let second = copy(&mut func, &mut names, block, RAX, R10);
618 let reload = load(&mut func, &mut names, block, PhysReg::new(12), 16);
619 let mut moves = Moves::default();
620 moves.record(spill, out(block, 0, R10));
621 moves.record(first, across(block, R11, R10));
622 moves.record(second, across(block, RAX, R10));
623 moves.record(reload, back(block, 0, PhysReg::new(12)));
624
625 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned { gone: 0, copied: 1 });
626 assert_eq!(reads(&func, block, 3), vec![RAX], "rax is register zero");
627 }
628
629 /// A call takes the registers with it, so the word is in the frame and nowhere else and the
630 /// reload behind one is a load rather than a copy of a register that no longer has it.
631 #[test]
632 fn a_reload_behind_a_call_is_not_written_as_a_copy() {
633 let (mut names, mut func, block) = empty();
634 let spill = store(&mut func, &mut names, block, R10, 16);
635 let call = op(&mut names, "call");
636 func.build(block, call).uses(Reg::physical(RAX), GPR).finish();
637 let reload = load(&mut func, &mut names, block, R11, 16);
638 let mut moves = Moves::default();
639 moves.record(spill, out(block, 0, R10));
640 moves.record(reload, back(block, 0, R11));
641
642 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
643 assert_eq!(written(&func, block, &names), ["x64.mov_mr_64", "x64.call", "x64.mov_rm_64"]);
644 }
645
646 /// A word written out to the frame has to go to the frame, so a store is left alone however
647 /// many registers hold what it is storing.
648 #[test]
649 fn a_spill_of_a_word_another_register_holds_stays_a_store() {
650 let (mut names, mut func, block) = empty();
651 let copied = copy(&mut func, &mut names, block, R11, R10);
652 let spill = store(&mut func, &mut names, block, R11, 16);
653 let mut moves = Moves::default();
654 moves.record(copied, across(block, R11, R10));
655 moves.record(spill, out(block, 0, R11));
656
657 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
658 assert_eq!(written(&func, block, &names), ["x64.mov_rr_64", "x64.mov_mr_64"]);
659 }
660
661 /// A slot number is a slot number in the class that owns it, so a pair that agrees on
662 /// everything but the class is two different words and two registers that share a number.
663 #[test]
664 fn a_reload_of_another_class_stays() {
665 let (mut names, mut func, block) = empty();
666 let spill = store(&mut func, &mut names, block, R10, 16);
667 let reload = load(&mut func, &mut names, block, R10, 16);
668 let mut moves = Moves::default();
669 moves.record(spill, out(block, 0, R10));
670 moves.record(reload, Edit { class: XMM, ..back(block, 0, R10) });
671
672 assert_eq!(gone(&mut func, &moves, &mut names), 0);
673 assert_eq!(left(&func, block), 2);
674 }
675
676 /// The same two instructions, with nothing saying the allocator wrote them, which is what a
677 /// write to a local variable and a read of it back look like.
678 #[test]
679 fn a_store_and_a_load_the_allocator_did_not_write_stay() {
680 let (mut names, mut func, block) = empty();
681 store(&mut func, &mut names, block, R10, 16);
682 load(&mut func, &mut names, block, R10, 16);
683
684 assert_eq!(gone(&mut func, &Moves::default(), &mut names), 0);
685 assert_eq!(left(&func, block), 2);
686 }
687
688 /// The last instruction of one block and the first of another are not next to each other. What
689 /// ran before the second block is whatever jumped to it, which is any block that names it and
690 /// not the one the text happens to be under.
691 #[test]
692 fn a_reload_at_the_top_of_another_block_stays() {
693 let (mut names, mut func, first) = empty();
694 let second = func.create_block();
695 let spill = store(&mut func, &mut names, first, R10, 16);
696 let reload = load(&mut func, &mut names, second, R10, 16);
697 let mut moves = Moves::default();
698 moves.record(spill, out(first, 0, R10));
699 moves.record(reload, back(first, 0, R10));
700
701 assert_eq!(gone(&mut func, &moves, &mut names), 0);
702 assert_eq!(left(&func, second), 1);
703 }
704
705 /// A copy of the word somewhere else does not move the word, so the reload behind one is still
706 /// a read of what the register holds.
707 #[test]
708 fn a_copy_out_of_the_register_between_the_two_does_not_stop_it() {
709 let (mut names, mut func, block) = empty();
710 let spill = store(&mut func, &mut names, block, R10, 16);
711 let copied = copy(&mut func, &mut names, block, RAX, R10);
712 let reload = load(&mut func, &mut names, block, R10, 16);
713 let mut moves = Moves::default();
714 moves.record(spill, out(block, 0, R10));
715 moves.record(copied, across(block, RAX, R10));
716 moves.record(reload, back(block, 0, R10));
717
718 assert_eq!(gone(&mut func, &moves, &mut names), 1);
719 assert_eq!(left(&func, block), 2);
720 }
721
722 /// A call destroys the registers the convention does not preserve and says so with a
723 /// definition of each, except for the ones its arguments arrived in, which it names as reads.
724 /// So what a call leaves alone is not a question the operand vector answers and the whole map
725 /// goes.
726 #[test]
727 fn a_reload_behind_a_call_stays() {
728 let (mut names, mut func, block) = empty();
729 let spill = store(&mut func, &mut names, block, R10, 16);
730 let call = op(&mut names, "call");
731 func.build(block, call).uses(Reg::physical(RAX), GPR).finish();
732 let reload = load(&mut func, &mut names, block, R10, 16);
733 let mut moves = Moves::default();
734 moves.record(spill, out(block, 0, R10));
735 moves.record(reload, back(block, 0, R10));
736
737 assert_eq!(gone(&mut func, &moves, &mut names), 0);
738 assert_eq!(left(&func, block), 3);
739 }
740
741 /// A slot is an offset from the stack pointer, so an instruction that moves the pointer moves
742 /// every slot, and what a register holds is no longer what is at the address the reload names.
743 #[test]
744 fn a_reload_behind_a_write_of_the_stack_pointer_stays() {
745 let (mut names, mut func, block) = empty();
746 let spill = store(&mut func, &mut names, block, R10, 16);
747 let sub = op(&mut names, "sub_ri_64");
748 func.build(block, sub)
749 .def(Reg::physical(SYSV.stack_pointer), GPR)
750 .uses(Reg::physical(SYSV.stack_pointer), GPR)
751 .imm(32)
752 .finish();
753 let reload = load(&mut func, &mut names, block, R10, 16);
754 let mut moves = Moves::default();
755 moves.record(spill, out(block, 0, R10));
756 moves.record(reload, back(block, 0, R10));
757
758 assert_eq!(gone(&mut func, &moves, &mut names), 0);
759 assert_eq!(left(&func, block), 3);
760 }
761
762 /// An instruction the description does not name is one nothing is known about, including which
763 /// registers it writes.
764 #[test]
765 fn a_reload_behind_an_instruction_the_target_does_not_have_stays() {
766 let (mut names, mut func, block) = empty();
767 let spill = store(&mut func, &mut names, block, R10, 16);
768 let strange = op(&mut names, "nothing_of_that_name");
769 func.build(block, strange).finish();
770 let reload = load(&mut func, &mut names, block, R10, 16);
771 let mut moves = Moves::default();
772 moves.record(spill, out(block, 0, R10));
773 moves.record(reload, back(block, 0, R10));
774
775 assert_eq!(gone(&mut func, &moves, &mut names), 0);
776 assert_eq!(left(&func, block), 3);
777 }
778}