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 rucc_base::Interner;
149use rucc_base::hash::Map;
150use rucc_mir::{Block, Func, Inst, Opcode, Reg};
151use rucc_regalloc::assign::Place;
152use rucc_regalloc::rewrite::Edit;
153use rucc_target::{CallRegs, FrameInsts, MachineInsts, PhysReg, RegClass};
154
155use crate::finish::Moves;
156
157/// What one function came to.
158#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
159pub struct Cleaned {
160 /// Moves taken out, because the place each wrote held what it was moving already.
161 pub gone: usize,
162 /// Loads out of the frame written as copies instead, because a register held the word.
163 pub copied: usize,
164}
165
166/// Takes out every move of the allocator's that puts a value where it is already, and reads the
167/// rest out of a register wherever one has the word the frame does.
168///
169/// # Panics
170///
171/// Panics on a move of a class the target did not say how to move, which is the same frame
172/// description [`crate::finish`] wrote the move out of and so is the caller handing this a
173/// function and a target that were not worked out from each other.
174pub fn clean(
175 func: &mut Func,
176 moves: &Moves,
177 machine: &MachineInsts,
178 frame: &FrameInsts,
179 conv: &CallRegs,
180 names: &mut Interner,
181) -> Cleaned {
182 let blocks: Vec<Block> = func.blocks().collect();
183 let mut cleaned = Cleaned::default();
184 // Whether each opcode is a call or one the target does not have, asked once per opcode rather
185 // than by name for every instruction. tamnd/rucc#2233.
186 let mut stops: Map<Opcode, bool> = Map::default();
187 for block in blocks {
188 let insts: Vec<Inst> = func.insts(block).collect();
189 let mut holds = Holds::default();
190 let mut gone: Vec<Inst> = Vec::new();
191 for inst in insts {
192 // One of the allocator's own moves, which is the only kind of instruction this edits
193 // and the only kind it learns anything from.
194 if let Some(edit) = moves.at(inst) {
195 if holds.same(edit.class, edit.mov.to, edit.mov.from) {
196 gone.push(inst);
197 cleaned.gone += 1;
198 continue;
199 }
200 // A load of a word a register has. The copy goes where the load was and the load
201 // goes, and what arrives in the register is the same either way, so the map is
202 // told about the move below whichever of the two instructions is left.
203 if let Some((to, from)) = instead(&holds, &edit) {
204 let copy = copy(func, names, frame, edit.class, to, from);
205 func.insert_before(inst, copy);
206 gone.push(inst);
207 cleaned.copied += 1;
208 }
209 holds.moved(edit.class, edit.mov.to, edit.mov.from);
210 continue;
211 }
212 let opcode = func[inst].opcode;
213 let stop = *stops.entry(opcode).or_insert_with(|| {
214 let name = names.resolve(opcode.name());
215 machine.calls(name) || !machine.has(name)
216 });
217 if stop {
218 holds.nothing();
219 continue;
220 }
221 let mut addressing = false;
222 for operand in &func[func[inst].operands] {
223 if !operand.role.is_def() {
224 continue;
225 }
226 let Some(reg) = operand.reg.phys() else { continue };
227 addressing |= addresses(conv, operand.class, reg);
228 holds.wrote(operand.class, Place::Reg(reg));
229 }
230 if addressing {
231 holds.nothing();
232 }
233 }
234 for inst in gone {
235 func.remove_inst(inst);
236 }
237 }
238 cleaned
239}
240
241/// Whether that register is one the frame is addressed through, so writing it moves every slot.
242fn addresses(conv: &CallRegs, class: RegClass, reg: PhysReg) -> bool {
243 class == conv.int_class && (reg == conv.stack_pointer || reg == conv.frame_pointer)
244}
245
246/// The two registers a copy would be written between, where this edit is a load out of the frame
247/// of a word some register holds.
248///
249/// `None` for every other edit. A store has nowhere else to go, since the word has to reach the
250/// frame and no machine here writes the frame from anywhere but a register, and a copy between two
251/// registers is already the instruction this would be turning something into.
252fn instead(holds: &Holds, edit: &Edit) -> Option<(PhysReg, PhysReg)> {
253 let (Place::Reg(to), Place::Slot(_)) = (edit.mov.to, edit.mov.from) else { return None };
254 Some((to, holds.register(edit.class, edit.mov.from)?))
255}
256
257/// The instruction that copies a register of that class into another on this target.
258fn copy(
259 func: &mut Func,
260 names: &mut Interner,
261 frame: &FrameInsts,
262 class: RegClass,
263 to: PhysReg,
264 from: PhysReg,
265) -> Inst {
266 let moves = frame.moves(class).expect("a class the target says how to move");
267 let mov = Opcode::new(names.intern(&format!("{}{}", frame.prefix, moves.mov)));
268 func.build_loose(mov).def(Reg::physical(to), class).uses(Reg::physical(from), class).finish()
269}
270
271/// Which places are known to hold the same value as each other, over one block.
272///
273/// A value is a number and nothing more. Where it came from and what it means are questions this
274/// does not ask, because the only thing a move is taken out over is two places holding the same
275/// one.
276#[derive(Debug, Default)]
277struct Holds {
278 /// What is in each place, by the class it is a place of and the place itself, hashed with
279 /// [`rucc_base::hash::Mix`] because this is asked about every register every instruction
280 /// writes. A class is in the key because a register is a number inside its class and a slot
281 /// is a slot of one, so number four of one file and number four of another are two places.
282 what: Map<(u8, Place), u32>,
283 /// How many values have been named, so the next one is a number no other place holds.
284 named: u32,
285}
286
287impl Holds {
288 /// Whether both places are known to hold one value, which is what makes a move of the one into
289 /// the other write what is there.
290 fn same(&self, class: RegClass, to: Place, from: Place) -> bool {
291 let read = self.what.get(&(class.number(), from));
292 read.is_some() && read == self.what.get(&(class.number(), to))
293 }
294
295 /// A register known to hold what that place holds, and the lowest numbered of them where more
296 /// than one does.
297 ///
298 /// Lowest numbered rather than whichever the map hands back first, because the map is a hash
299 /// map and which entry comes out of one first is a fact about the hash rather than the input.
300 /// Reading it would tie the assembly to how the map happens to hash.
301 fn register(&self, class: RegClass, place: Place) -> Option<PhysReg> {
302 let value = *self.what.get(&(class.number(), place))?;
303 self.what
304 .iter()
305 .filter(|&(&(number, _), &held)| number == class.number() && held == value)
306 .filter_map(|(&(_, place), _)| match place {
307 Place::Reg(reg) => Some(reg),
308 Place::Slot(_) => None,
309 })
310 .min_by_key(|reg| reg.number())
311 }
312
313 /// Records a move that ran, so what it wrote holds what it read.
314 ///
315 /// A read of a place nothing is known about is what names a value: the bits are whatever they
316 /// are, the two ends of the move agree about them from here on, and that agreement is the only
317 /// thing this pass ever asks about.
318 fn moved(&mut self, class: RegClass, to: Place, from: Place) {
319 let value = match self.what.get(&(class.number(), from)) {
320 Some(&value) => value,
321 None => {
322 self.named += 1;
323 self.what.insert((class.number(), from), self.named);
324 self.named
325 }
326 };
327 self.what.insert((class.number(), to), value);
328 }
329
330 /// Records that something wrote a place, so whatever it held is no longer what is there.
331 fn wrote(&mut self, class: RegClass, place: Place) {
332 self.what.remove(&(class.number(), place));
333 }
334
335 /// Forgets the block so far, which is the answer to an instruction whose writes cannot all be
336 /// seen.
337 fn nothing(&mut self) {
338 self.what.clear();
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use rucc_mir::{Mem, Opcode, Operand, Reg};
345 use rucc_regalloc::moves::Move;
346 use rucc_regalloc::rewrite::{At, Edit};
347 use rucc_target::x86_64::{FRAME, GPR, MACHINE, RAX, SYSV, XMM};
348
349 use super::*;
350
351 /// A spill register to write with, which is the one x86-64 holds back for exactly this.
352 const R10: PhysReg = PhysReg::new(10);
353
354 /// The second of them, which is what an instruction with both operands on the stack reads the
355 /// other one into.
356 const R11: PhysReg = PhysReg::new(11);
357
358 /// A function with one block in it, and the names it was built with.
359 fn empty() -> (Interner, Func, Block) {
360 let mut names = Interner::new();
361 let mut func = Func::new(names.intern("f"));
362 let block = func.create_block();
363 (names, func, block)
364 }
365
366 /// The pass, with the one target these tests are written against.
367 fn clean(func: &mut Func, moves: &Moves, names: &mut Interner) -> Cleaned {
368 super::clean(func, moves, &MACHINE, &FRAME, &SYSV, names)
369 }
370
371 /// How many moves went, for a test about the half that removes.
372 fn gone(func: &mut Func, moves: &Moves, names: &mut Interner) -> usize {
373 clean(func, moves, names).gone
374 }
375
376 /// The opcode of that name on this target.
377 fn op(names: &mut Interner, name: &str) -> Opcode {
378 Opcode::new(names.intern(&format!("{}{name}", FRAME.prefix)))
379 }
380
381 /// A store of a physical register to a frame address, as [`crate::finish`] writes a spill.
382 fn store(func: &mut Func, names: &mut Interner, block: Block, reg: PhysReg, at: i32) -> Inst {
383 let store = op(names, "mov_mr_64");
384 let base = Operand::read(Reg::physical(RAX), GPR);
385 func.build(block, store).uses(Reg::physical(reg), GPR).mem(Mem::at(base).plus(at)).finish()
386 }
387
388 /// A load of a physical register from a frame address, as it writes a reload.
389 fn load(func: &mut Func, names: &mut Interner, block: Block, reg: PhysReg, at: i32) -> Inst {
390 let load = op(names, "mov_rm_64");
391 let base = Operand::read(Reg::physical(RAX), GPR);
392 func.build(block, load).def(Reg::physical(reg), GPR).mem(Mem::at(base).plus(at)).finish()
393 }
394
395 /// A copy of one physical register into another, as it writes one of the allocator's copies.
396 fn copy(
397 func: &mut Func,
398 names: &mut Interner,
399 block: Block,
400 to: PhysReg,
401 from: PhysReg,
402 ) -> Inst {
403 let copy = op(names, "mov_rr_64");
404 func.build(block, copy).def(Reg::physical(to), GPR).uses(Reg::physical(from), GPR).finish()
405 }
406
407 /// An instruction that reads a register and writes another, which is what a spilled value was
408 /// read back for.
409 fn add(
410 func: &mut Func,
411 names: &mut Interner,
412 block: Block,
413 to: PhysReg,
414 from: PhysReg,
415 ) -> Inst {
416 let add = op(names, "add_rr_64");
417 func.build(block, add).def(Reg::physical(to), GPR).uses(Reg::physical(from), GPR).finish()
418 }
419
420 /// What the allocator asked for, in the two shapes this pass is about.
421 ///
422 /// Where the edit was to go is not read by anything here, since what says two instructions are
423 /// next to each other is the function they were written into rather than what the allocator
424 /// said about where they belong.
425 fn out(block: Block, slot: u32, reg: PhysReg) -> Edit {
426 Edit {
427 at: At::StartOf(block),
428 mov: Move::new(Place::Slot(slot), Place::Reg(reg)),
429 class: GPR,
430 }
431 }
432
433 /// The other direction, and the one a dead reload is.
434 fn back(block: Block, slot: u32, reg: PhysReg) -> Edit {
435 Edit {
436 at: At::StartOf(block),
437 mov: Move::new(Place::Reg(reg), Place::Slot(slot)),
438 class: GPR,
439 }
440 }
441
442 /// A move of one register into another, which the allocator writes where the two ends of a
443 /// value could not be given the same register.
444 fn across(block: Block, to: PhysReg, from: PhysReg) -> Edit {
445 Edit {
446 at: At::StartOf(block),
447 mov: Move::new(Place::Reg(to), Place::Reg(from)),
448 class: GPR,
449 }
450 }
451
452 /// How many instructions a block has left.
453 fn left(func: &Func, block: Block) -> usize {
454 func.insts(block).count()
455 }
456
457 /// What the block says now, one opcode per instruction, which is how a test about a rewrite
458 /// says which instruction came out of it.
459 fn written(func: &Func, block: Block, names: &Interner) -> Vec<String> {
460 func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
461 }
462
463 /// The registers the instruction at that position in the block reads.
464 fn reads(func: &Func, block: Block, at: usize) -> Vec<PhysReg> {
465 let inst = func.insts(block).nth(at).expect("an instruction at that position");
466 func[func[inst].operands]
467 .iter()
468 .filter(|operand| !operand.role.is_def())
469 .filter_map(|operand| operand.reg.phys())
470 .collect()
471 }
472
473 /// The pair the pass started as: a word written out and read straight back into the register it
474 /// was written out of.
475 #[test]
476 fn a_reload_of_the_slot_the_instruction_in_front_of_it_spilled_goes() {
477 let (mut names, mut func, block) = empty();
478 let spill = store(&mut func, &mut names, block, R10, 16);
479 let reload = load(&mut func, &mut names, block, R10, 16);
480 let mut moves = Moves::default();
481 moves.record(spill, out(block, 0, R10));
482 moves.record(reload, back(block, 0, R10));
483
484 assert_eq!(gone(&mut func, &moves, &mut names), 1);
485 assert_eq!(left(&func, block), 1, "the spill went too, or the reload stayed");
486 assert_eq!(func.insts(block).next(), Some(spill));
487 }
488
489 /// Two reads of one word, which is one spill and two reloads, and the second is as dead as the
490 /// first because the register still holds what the first put in it.
491 #[test]
492 fn a_run_of_reloads_of_one_slot_goes_in_a_single_pass() {
493 let (mut names, mut func, block) = empty();
494 let spill = store(&mut func, &mut names, block, R10, 16);
495 let first = load(&mut func, &mut names, block, R10, 16);
496 let second = load(&mut func, &mut names, block, R10, 16);
497 let mut moves = Moves::default();
498 moves.record(spill, out(block, 0, R10));
499 moves.record(first, back(block, 0, R10));
500 moves.record(second, back(block, 0, R10));
501
502 assert_eq!(gone(&mut func, &moves, &mut names), 2);
503 assert_eq!(left(&func, block), 1);
504 }
505
506 /// The case the pass was grown for. What the value was read back for stands between the two
507 /// reloads, and it writes neither the slot nor the register the word is in, so the second read
508 /// is of a word that register still holds.
509 #[test]
510 fn a_reload_with_an_instruction_between_that_writes_neither_end_goes() {
511 let (mut names, mut func, block) = empty();
512 let spill = store(&mut func, &mut names, block, R10, 16);
513 let first = load(&mut func, &mut names, block, R10, 16);
514 add(&mut func, &mut names, block, RAX, R10);
515 let second = load(&mut func, &mut names, block, R10, 16);
516 let mut moves = Moves::default();
517 moves.record(spill, out(block, 0, R10));
518 moves.record(first, back(block, 0, R10));
519 moves.record(second, back(block, 0, R10));
520
521 assert_eq!(gone(&mut func, &moves, &mut names), 2);
522 assert_eq!(left(&func, block), 2, "the spill and the instruction between are the two");
523 }
524
525 /// The instruction between them writes the register this time, which is what a spilled value
526 /// is read back into a scratch register for, so the reload behind it reads a word that
527 /// register no longer holds.
528 #[test]
529 fn a_reload_behind_an_instruction_that_writes_the_register_stays() {
530 let (mut names, mut func, block) = empty();
531 let spill = store(&mut func, &mut names, block, R10, 16);
532 add(&mut func, &mut names, block, R10, RAX);
533 let reload = load(&mut func, &mut names, block, R10, 16);
534 let mut moves = Moves::default();
535 moves.record(spill, out(block, 0, R10));
536 moves.record(reload, back(block, 0, R10));
537
538 assert_eq!(gone(&mut func, &moves, &mut names), 0);
539 assert_eq!(left(&func, block), 3);
540 }
541
542 /// A word read back and written out again with nothing touching either end is a store of what
543 /// the slot holds already.
544 #[test]
545 fn a_spill_of_a_word_the_slot_still_holds_goes() {
546 let (mut names, mut func, block) = empty();
547 let reload = load(&mut func, &mut names, block, R10, 16);
548 let spill = store(&mut func, &mut names, block, R10, 16);
549 let mut moves = Moves::default();
550 moves.record(reload, back(block, 0, R10));
551 moves.record(spill, out(block, 0, R10));
552
553 assert_eq!(gone(&mut func, &moves, &mut names), 1);
554 assert_eq!(left(&func, block), 1);
555 assert_eq!(func.insts(block).next(), Some(reload));
556 }
557
558 /// A copy into a register that holds what it is being given writes what is there.
559 #[test]
560 fn a_copy_of_a_word_the_register_already_holds_goes() {
561 let (mut names, mut func, block) = empty();
562 let first = copy(&mut func, &mut names, block, RAX, R10);
563 let second = copy(&mut func, &mut names, block, RAX, R10);
564 let mut moves = Moves::default();
565 moves.record(first, across(block, RAX, R10));
566 moves.record(second, across(block, RAX, R10));
567
568 assert_eq!(gone(&mut func, &moves, &mut names), 1);
569 assert_eq!(left(&func, block), 1);
570 }
571
572 /// A reload of another slot reads another word, whatever address the two instructions were
573 /// written with.
574 #[test]
575 fn a_reload_of_a_different_slot_stays() {
576 let (mut names, mut func, block) = empty();
577 let spill = store(&mut func, &mut names, block, R10, 16);
578 let reload = load(&mut func, &mut names, block, R10, 24);
579 let mut moves = Moves::default();
580 moves.record(spill, out(block, 0, R10));
581 moves.record(reload, back(block, 1, R10));
582
583 assert_eq!(gone(&mut func, &moves, &mut names), 0);
584 assert_eq!(left(&func, block), 2);
585 }
586
587 /// A reload into another register puts the word somewhere it is not, so the instruction has
588 /// work to do. Where it takes the word from is the question, and the register that was spilled
589 /// still holds it, so the frame is not read.
590 #[test]
591 fn a_reload_into_a_different_register_becomes_a_copy() {
592 let (mut names, mut func, block) = empty();
593 let spill = store(&mut func, &mut names, block, R10, 16);
594 let reload = load(&mut func, &mut names, block, R11, 16);
595 let mut moves = Moves::default();
596 moves.record(spill, out(block, 0, R10));
597 moves.record(reload, back(block, 0, R11));
598
599 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned { gone: 0, copied: 1 });
600 assert_eq!(written(&func, block, &names), ["x64.mov_mr_64", "x64.mov_rr_64"]);
601 assert_eq!(reads(&func, block, 1), vec![R10]);
602 }
603
604 /// The same reload with nothing having put the word in a register. There is nowhere to read it
605 /// from but the frame, so the load is the instruction it was.
606 #[test]
607 fn a_reload_no_register_holds_the_word_of_stays_a_load() {
608 let (mut names, mut func, block) = empty();
609 let reload = load(&mut func, &mut names, block, R11, 16);
610 let mut moves = Moves::default();
611 moves.record(reload, back(block, 0, R11));
612
613 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
614 assert_eq!(written(&func, block, &names), ["x64.mov_rm_64"]);
615 }
616
617 /// Three registers holding one word is three right answers, and the pass takes the lowest
618 /// numbered of them every time rather than whichever the map hands back first.
619 #[test]
620 fn the_copy_is_written_out_of_the_lowest_numbered_register_that_has_the_word() {
621 let (mut names, mut func, block) = empty();
622 let spill = store(&mut func, &mut names, block, R10, 16);
623 let first = copy(&mut func, &mut names, block, R11, R10);
624 let second = copy(&mut func, &mut names, block, RAX, R10);
625 let reload = load(&mut func, &mut names, block, PhysReg::new(12), 16);
626 let mut moves = Moves::default();
627 moves.record(spill, out(block, 0, R10));
628 moves.record(first, across(block, R11, R10));
629 moves.record(second, across(block, RAX, R10));
630 moves.record(reload, back(block, 0, PhysReg::new(12)));
631
632 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned { gone: 0, copied: 1 });
633 assert_eq!(reads(&func, block, 3), vec![RAX], "rax is register zero");
634 }
635
636 /// A call takes the registers with it, so the word is in the frame and nowhere else and the
637 /// reload behind one is a load rather than a copy of a register that no longer has it.
638 #[test]
639 fn a_reload_behind_a_call_is_not_written_as_a_copy() {
640 let (mut names, mut func, block) = empty();
641 let spill = store(&mut func, &mut names, block, R10, 16);
642 let call = op(&mut names, "call");
643 func.build(block, call).uses(Reg::physical(RAX), GPR).finish();
644 let reload = load(&mut func, &mut names, block, R11, 16);
645 let mut moves = Moves::default();
646 moves.record(spill, out(block, 0, R10));
647 moves.record(reload, back(block, 0, R11));
648
649 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
650 assert_eq!(written(&func, block, &names), ["x64.mov_mr_64", "x64.call", "x64.mov_rm_64"]);
651 }
652
653 /// A word written out to the frame has to go to the frame, so a store is left alone however
654 /// many registers hold what it is storing.
655 #[test]
656 fn a_spill_of_a_word_another_register_holds_stays_a_store() {
657 let (mut names, mut func, block) = empty();
658 let copied = copy(&mut func, &mut names, block, R11, R10);
659 let spill = store(&mut func, &mut names, block, R11, 16);
660 let mut moves = Moves::default();
661 moves.record(copied, across(block, R11, R10));
662 moves.record(spill, out(block, 0, R11));
663
664 assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
665 assert_eq!(written(&func, block, &names), ["x64.mov_rr_64", "x64.mov_mr_64"]);
666 }
667
668 /// A slot number is a slot number in the class that owns it, so a pair that agrees on
669 /// everything but the class is two different words and two registers that share a number.
670 #[test]
671 fn a_reload_of_another_class_stays() {
672 let (mut names, mut func, block) = empty();
673 let spill = store(&mut func, &mut names, block, R10, 16);
674 let reload = load(&mut func, &mut names, block, R10, 16);
675 let mut moves = Moves::default();
676 moves.record(spill, out(block, 0, R10));
677 moves.record(reload, Edit { class: XMM, ..back(block, 0, R10) });
678
679 assert_eq!(gone(&mut func, &moves, &mut names), 0);
680 assert_eq!(left(&func, block), 2);
681 }
682
683 /// The same two instructions, with nothing saying the allocator wrote them, which is what a
684 /// write to a local variable and a read of it back look like.
685 #[test]
686 fn a_store_and_a_load_the_allocator_did_not_write_stay() {
687 let (mut names, mut func, block) = empty();
688 store(&mut func, &mut names, block, R10, 16);
689 load(&mut func, &mut names, block, R10, 16);
690
691 assert_eq!(gone(&mut func, &Moves::default(), &mut names), 0);
692 assert_eq!(left(&func, block), 2);
693 }
694
695 /// The last instruction of one block and the first of another are not next to each other. What
696 /// ran before the second block is whatever jumped to it, which is any block that names it and
697 /// not the one the text happens to be under.
698 #[test]
699 fn a_reload_at_the_top_of_another_block_stays() {
700 let (mut names, mut func, first) = empty();
701 let second = func.create_block();
702 let spill = store(&mut func, &mut names, first, R10, 16);
703 let reload = load(&mut func, &mut names, second, R10, 16);
704 let mut moves = Moves::default();
705 moves.record(spill, out(first, 0, R10));
706 moves.record(reload, back(first, 0, R10));
707
708 assert_eq!(gone(&mut func, &moves, &mut names), 0);
709 assert_eq!(left(&func, second), 1);
710 }
711
712 /// A copy of the word somewhere else does not move the word, so the reload behind one is still
713 /// a read of what the register holds.
714 #[test]
715 fn a_copy_out_of_the_register_between_the_two_does_not_stop_it() {
716 let (mut names, mut func, block) = empty();
717 let spill = store(&mut func, &mut names, block, R10, 16);
718 let copied = copy(&mut func, &mut names, block, RAX, R10);
719 let reload = load(&mut func, &mut names, block, R10, 16);
720 let mut moves = Moves::default();
721 moves.record(spill, out(block, 0, R10));
722 moves.record(copied, across(block, RAX, R10));
723 moves.record(reload, back(block, 0, R10));
724
725 assert_eq!(gone(&mut func, &moves, &mut names), 1);
726 assert_eq!(left(&func, block), 2);
727 }
728
729 /// A call destroys the registers the convention does not preserve and says so with a
730 /// definition of each, except for the ones its arguments arrived in, which it names as reads.
731 /// So what a call leaves alone is not a question the operand vector answers and the whole map
732 /// goes.
733 #[test]
734 fn a_reload_behind_a_call_stays() {
735 let (mut names, mut func, block) = empty();
736 let spill = store(&mut func, &mut names, block, R10, 16);
737 let call = op(&mut names, "call");
738 func.build(block, call).uses(Reg::physical(RAX), GPR).finish();
739 let reload = load(&mut func, &mut names, block, R10, 16);
740 let mut moves = Moves::default();
741 moves.record(spill, out(block, 0, R10));
742 moves.record(reload, back(block, 0, R10));
743
744 assert_eq!(gone(&mut func, &moves, &mut names), 0);
745 assert_eq!(left(&func, block), 3);
746 }
747
748 /// A slot is an offset from the stack pointer, so an instruction that moves the pointer moves
749 /// every slot, and what a register holds is no longer what is at the address the reload names.
750 #[test]
751 fn a_reload_behind_a_write_of_the_stack_pointer_stays() {
752 let (mut names, mut func, block) = empty();
753 let spill = store(&mut func, &mut names, block, R10, 16);
754 let sub = op(&mut names, "sub_ri_64");
755 func.build(block, sub)
756 .def(Reg::physical(SYSV.stack_pointer), GPR)
757 .uses(Reg::physical(SYSV.stack_pointer), GPR)
758 .imm(32)
759 .finish();
760 let reload = load(&mut func, &mut names, block, R10, 16);
761 let mut moves = Moves::default();
762 moves.record(spill, out(block, 0, R10));
763 moves.record(reload, back(block, 0, R10));
764
765 assert_eq!(gone(&mut func, &moves, &mut names), 0);
766 assert_eq!(left(&func, block), 3);
767 }
768
769 /// An instruction the description does not name is one nothing is known about, including which
770 /// registers it writes.
771 #[test]
772 fn a_reload_behind_an_instruction_the_target_does_not_have_stays() {
773 let (mut names, mut func, block) = empty();
774 let spill = store(&mut func, &mut names, block, R10, 16);
775 let strange = op(&mut names, "nothing_of_that_name");
776 func.build(block, strange).finish();
777 let reload = load(&mut func, &mut names, block, R10, 16);
778 let mut moves = Moves::default();
779 moves.record(spill, out(block, 0, R10));
780 moves.record(reload, back(block, 0, R10));
781
782 assert_eq!(gone(&mut func, &moves, &mut names), 0);
783 assert_eq!(left(&func, block), 3);
784 }
785}