Skip to main content

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}