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