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 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}