Skip to main content

rucc_codegen/
combine.rs

1//! Putting a run of machine instructions together into the shorter run the machine has for it.
2//!
3//! Design: `spec/10-backend.md` section 10.9, and `spec/optimizer/37-machine-level-optimization.md`
4//! sections 37.3 and 37.4.
5//!
6//! Section 37.4 names this pass first of the ten it says are genuinely machine level, and says what
7//! shape it should be: a match over machine instructions in SSA form, inside one block, over a
8//! window of a few instructions, which is `gcc/late-combine.cc` rather than `gcc/combine.cc`. The
9//! reason for the smaller of the two is in the same section. Combine is fifteen thousand lines
10//! because it was written without def-use chains and had to find them again each time, and every
11//! RTL pass GCC has written since is on the SSA form it added later for exactly that.
12//!
13//! Section 37.3 says what the pass does once it has found a run: substitute the earlier instruction
14//! into the later one, and ask the machine description whether what came out is an instruction this
15//! target has. That is [`crate::changes`] and this pass does not repeat any of it.
16//!
17//! # The run it puts together
18//!
19//! A value read out of memory and then used once, by arithmetic that this machine could have read
20//! it out of memory itself:
21//!
22//! ```text
23//!   movq 16(%rax), %rcx
24//!   addq %rcx, %rdx        ->    addq 16(%rax), %rdx
25//! ```
26//!
27//! Two instructions become one. The register the load wrote is not written at all, which is one
28//! fewer value for the allocator to find a place for, and the bytes come down because an addressing
29//! mode costs what it costs whichever instruction carries it and the load's own opcode byte goes.
30//!
31//! It is the commonest pair in the machine IR this compiler writes. Counting adjacent instructions
32//! over the corpus at `-O2`, where the first writes what the second reads, the largest family by a
33//! long way is a move into arithmetic, and an addition at eight bytes is the largest single entry
34//! in it. What the pass gets over that corpus is 865 of these at `-O2` and 845 fewer instructions
35//! once the allocator has had its say, with the difference between the two explained below.
36//!
37//! # Why no rule does it
38//!
39//! The selector matches a term, and a term is one value. A load is a term and an addition is a
40//! term, and the pattern that would cover both is an addition with a load under it, which the
41//! selector does offer: it shows a rule the operands of its operands. What it cannot offer is the
42//! rest of the condition. Whether the load may move down to where the addition is depends on what
43//! is written between the two, and whether the load's value is wanted anywhere else depends on the
44//! whole function. Neither is a fact about the term, so neither can be in a pattern.
45//!
46//! # When the load may move
47//!
48//! The load stops being where it was and starts being part of an instruction further down the
49//! block, so everything between the two has to be something the load can pass. Two things are not.
50//!
51//! Anything that touches memory, whether it reads or writes. A write is the obvious half: whether
52//! it writes the bytes this load reads is a question about two addresses, and telling two addresses
53//! apart is an analysis nothing below selection has, so the walk below stops at a store rather than
54//! guessing. [`MachineInsts::touches_mem`] is the target's answer and [`MachineInsts::calls`] is the
55//! rest of it, since what a call does to memory is not in the instruction at all.
56//!
57//! A read is the half that is easy to argue away and is the one that matters. Moving a read past a
58//! read changes the order two accesses happen in, and the machine IR does not say which accesses
59//! the program insisted on: a `volatile` read and an ordinary one are the same instruction with the
60//! same operands here, as [`crate::copies`] says at more length about the same problem. So
61//! `volatile int a, b; return b - a;` is two loads and a subtract, and folding the first of them
62//! into the subtract would read `b` before `a` when the program said otherwise. Stopping at any
63//! access at all is what rules that out, and it costs almost nothing: the load that the arithmetic
64//! reads is nearly always the last access before it, so it is still the one that folds.
65//!
66//! What follows from that is the shape of the walk. There is one load in hand rather than a list of
67//! them, and it is always the last memory access there was.
68//!
69//! Anything that writes a register the address reads. Machine IR is in SSA form until the
70//! allocator has run, so a virtual register cannot be written twice, but the stack pointer and the
71//! frame pointer are physical here and an address into the frame reads one of them.
72//!
73//! # When the load is wanted elsewhere
74//!
75//! Exactly one instruction may read what the load wrote, and it has to be the one taking the load
76//! in. [`Reads`] is that count, kept across the commits of the pass the way [`crate::fold`] keeps
77//! it, and a count of one is the whole of the test because a virtual register is written once. Two
78//! readers and the load has to stay where it is, so putting it into one of them buys nothing and
79//! costs a second read of memory.
80//!
81//! An argument an edge carries is a read like any other and is in no operand vector, which is the
82//! one place a count of this shape is easy to get wrong. [`Reads::of`] counts those, which is what
83//! keeps a load whose value leaves the block out of this.
84//!
85//! # Which arithmetic
86//!
87//! [`FOLDS`] is the list, and it is a list rather than a rule about names because the two ends of
88//! each entry are instructions the target describes separately and the widths have to agree. A
89//! sixty four bit addition takes a sixty four bit load and nothing else: reading four bytes where
90//! the program asked for eight is a different instruction, and reading eight where it asked for
91//! four is three bytes nobody said were there.
92//!
93//! The eight bit multiply is the one member of the family with no entry. This machine has no
94//! two-operand multiply narrower than sixteen bits, so an eight bit one is written as a thirty two
95//! bit `imul` and reads a register whose upper bits nothing looks at. A memory operand has no
96//! upper bits to not look at, so there is nothing to read there and the entry is left out.
97//!
98//! # Either source, when the operation does not care
99//!
100//! An addition reads two registers and it is the second of them the memory operand replaces,
101//! because the first is the one the destination is tied to. Where the load feeds the first instead,
102//! the two sources are swapped first, which is a change to the instruction and not to what it
103//! computes as long as the operation commutes. Five of the six here do and subtraction does not,
104//! which is what [`Fold::commutes`] says.
105//!
106//! # The window
107//!
108//! A load is carried forward at most [`WINDOW`] instructions and then dropped. The bound is what
109//! makes the pass cost a fixed amount per instruction rather than an amount that grows with the
110//! block, which section 37.3 records as GCC's own answer: `max-combine-insns` is four and has been
111//! for decades.
112//!
113//! It is also nearly all of it already at one. The measurement in [`WINDOW`] is that a bound of one
114//! finds 852 folds over the corpus and a bound of thirty two finds 865, which follows from the rule
115//! above about memory rather than from anything about how the selector writes code: the load that
116//! folds is the last access to memory before the arithmetic, and the last access before it is
117//! usually the instruction in front of it. The window is there to bound the walk and it earns
118//! thirteen folds along the way.
119//!
120//! # Where it costs something
121//!
122//! A fold takes out exactly one instruction, so the number of folds and the number of instructions
123//! saved should be the same number, and they are not: 865 folds against 845 instructions over the
124//! corpus at `-O2`, and 1609 against 1444 over the SQLite amalgamation. The gap is the allocator.
125//!
126//! Taking the load out changes which values are live where, so the allocator makes different
127//! choices, and a few of them are worse. Two programs in the corpus come out two instructions
128//! longer at every level above `-O0`, both for the same reason: the folded addition is given a
129//! callee saved register while a caller saved one was free, which buys a push, a pop and a copy for
130//! a value that dies before the next call. That is the allocator preferring the wrong end of its
131//! own list rather than anything this pass did, and it is worth fixing where it is rather than
132//! worth not folding over.
133//!
134//! The trade is the other thing the gap is, and it is a real one rather than an accounting error.
135//! Two instructions become one and the one that is left both reads memory and computes, so it is
136//! two operations in one slot rather than one, which a machine that issues several instructions at
137//! once may not want. The measurement that settles it is run time rather than instruction count,
138//! and section 38.6's scheduler is where that argument belongs, since a scheduler is the pass that
139//! can see whether the slot was going to be used.
140//!
141//! # What it does not do yet
142//!
143//! A comparison. This machine compares against memory as readily as it adds to it, and the reason
144//! there is no entry for one is that a comparison here is one opcode holding a compare and the byte
145//! behind it, so the memory form is a third instruction rather than a second and the target has to
146//! describe it before this can write it.
147//!
148//! A store the arithmetic feeds. `addq %rax, 16(%rcx)` is the same saving again on the other side
149//! and this machine has the instruction, as [`rucc_target::x86_64::Form::Rmw`] already. What it
150//! needs is the reverse of the walk below, a store looking back at what wrote the value it is
151//! storing, and that is a second entry in the list rather than a second pass.
152//!
153//! Anything that is not a pair. Section 37.3 says GCC goes to four instructions, and the run this
154//! finds is two. What makes three worth having is a rule set that has something to say about
155//! three, and the rule set here grows one measured entry at a time.
156
157use rucc_base::Interner;
158use rucc_mir::{Func, Inst, Opcode, Reg};
159use rucc_target::MachineInsts;
160
161use crate::changes::{Changes, Plan, Reads};
162use crate::fold::Pending;
163
164/// How far a load is carried looking for the instruction that takes it in.
165///
166/// Measured over the corpus at `-O2`, which folds this many loads at each bound:
167///
168/// ```text
169///   1     2     4     8    16    32
170/// 852   858   863   864   865   865
171/// ```
172///
173/// Sixteen, because that is where the curve stops. Doubling it again finds nothing, and the pass
174/// still costs a fixed amount per instruction, which is what the bound is for.
175///
176/// The curve is that flat because of the rule about memory rather than because of anything the
177/// selector does. The load that folds is the last access to memory before the arithmetic, and
178/// almost always that is the instruction immediately in front of it. What the room past one buys is
179/// the thirteen where a register was written or a constant made in between.
180pub const WINDOW: usize = 16;
181
182/// One arithmetic instruction that could read its second source out of memory, and the load that
183/// would fill it.
184///
185/// A table rather than a rule about spellings, because the three names in each row are three things
186/// the target describes on their own and nothing about `add_rr_64` says that `mov_rm_64` is the
187/// load of the same width. Writing the three together is what makes a mismatched width a line
188/// somebody can see rather than a string that was built at run time.
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub struct Fold {
191    /// The arithmetic as the selector wrote it, reading both its sources from registers.
192    pub from: &'static str,
193    /// The same arithmetic reading its second source out of memory.
194    pub into: &'static str,
195    /// The load that would have filled that register, which has to be of the same width.
196    pub load: &'static str,
197    /// Whether the two sources may be swapped, which is what lets the load feed either of them.
198    pub commutes: bool,
199}
200
201/// The arithmetic a load can move into on this machine.
202///
203/// Every two-address integer operation the target has, at every width it has one, except the eight
204/// bit multiply the module documentation gives the reason for. Subtraction is the one that does not
205/// commute.
206pub static FOLDS: &[Fold] = &[
207    Fold { from: "add_rr_8", into: "add_rm_8", load: "mov_rm_8", commutes: true },
208    Fold { from: "add_rr_16", into: "add_rm_16", load: "mov_rm_16", commutes: true },
209    Fold { from: "add_rr_32", into: "add_rm_32", load: "mov_rm_32", commutes: true },
210    Fold { from: "add_rr_64", into: "add_rm_64", load: "mov_rm_64", commutes: true },
211    Fold { from: "sub_rr_8", into: "sub_rm_8", load: "mov_rm_8", commutes: false },
212    Fold { from: "sub_rr_16", into: "sub_rm_16", load: "mov_rm_16", commutes: false },
213    Fold { from: "sub_rr_32", into: "sub_rm_32", load: "mov_rm_32", commutes: false },
214    Fold { from: "sub_rr_64", into: "sub_rm_64", load: "mov_rm_64", commutes: false },
215    Fold { from: "and_rr_8", into: "and_rm_8", load: "mov_rm_8", commutes: true },
216    Fold { from: "and_rr_16", into: "and_rm_16", load: "mov_rm_16", commutes: true },
217    Fold { from: "and_rr_32", into: "and_rm_32", load: "mov_rm_32", commutes: true },
218    Fold { from: "and_rr_64", into: "and_rm_64", load: "mov_rm_64", commutes: true },
219    Fold { from: "or_rr_8", into: "or_rm_8", load: "mov_rm_8", commutes: true },
220    Fold { from: "or_rr_16", into: "or_rm_16", load: "mov_rm_16", commutes: true },
221    Fold { from: "or_rr_32", into: "or_rm_32", load: "mov_rm_32", commutes: true },
222    Fold { from: "or_rr_64", into: "or_rm_64", load: "mov_rm_64", commutes: true },
223    Fold { from: "xor_rr_8", into: "xor_rm_8", load: "mov_rm_8", commutes: true },
224    Fold { from: "xor_rr_16", into: "xor_rm_16", load: "mov_rm_16", commutes: true },
225    Fold { from: "xor_rr_32", into: "xor_rm_32", load: "mov_rm_32", commutes: true },
226    Fold { from: "xor_rr_64", into: "xor_rm_64", load: "mov_rm_64", commutes: true },
227    Fold { from: "imul_rr_16", into: "imul_rm_16", load: "mov_rm_16", commutes: true },
228    Fold { from: "imul_rr_32", into: "imul_rm_32", load: "mov_rm_32", commutes: true },
229    Fold { from: "imul_rr_64", into: "imul_rm_64", load: "mov_rm_64", commutes: true },
230];
231
232/// The load this block has passed that could still end up inside something.
233///
234/// One rather than a list of them, because anything that touches memory ends the one being carried,
235/// so the one being carried is always the last memory access there was.
236#[derive(Debug, Clone, Copy)]
237struct Waiting {
238    /// The load.
239    inst: Inst,
240    /// The register it wrote, which is what the arithmetic has to be reading.
241    reg: Reg,
242    /// Which load it is, so that the width can be held against the arithmetic's.
243    load: &'static str,
244    /// How far along the block it is, which is what [`WINDOW`] is counted in.
245    at: usize,
246}
247
248/// Puts every load that can move into the arithmetic that reads it, and gives back how many.
249///
250/// `pending` is the addresses [`crate::finish`] has still to write a displacement into, and a load
251/// that moves takes its entry with it, the same way one folded into a reader does. An address into
252/// the frame arrives here already inside the load, because [`crate::fold`] has run.
253///
254/// Run after selection and after the addresses are folded, and before allocation. Before the
255/// allocator because what makes the pair safe to put together is that a virtual register is written
256/// once, and after the addresses because a load whose address is still a `lea` in front of it has
257/// nothing in its own memory operand worth carrying.
258pub fn loads(
259    func: &mut Func,
260    machine: &MachineInsts,
261    names: &mut Interner,
262    pending: &mut Pending<'_>,
263) -> usize {
264    let mut reads = Reads::of(func);
265    let mut done = 0;
266    for block in func.blocks().collect::<Vec<_>>() {
267        let mut waiting: Option<Waiting> = None;
268        for (at, inst) in func.insts(block).collect::<Vec<_>>().into_iter().enumerate() {
269            let name = names.resolve(func[inst].opcode.name()).to_owned();
270            let bare = machine.bare(&name).to_owned();
271            // Asked before the rewrite below rather than after it, because the rewrite turns an
272            // instruction that touched no memory into one that does, and asking afterwards would
273            // throw away the load that had just gone into it over the load that had just gone into
274            // it. Nothing else about the answer moves: the other end of a row of the fold table is
275            // arithmetic this target describes and is not a call.
276            let barrier = machine.calls(&name) || !machine.has(&name) || machine.touches_mem(&name);
277            if let Some(carried) = waiting {
278                if let Some(plan) = joined(func, &reads, carried, machine, names, inst, &bare) {
279                    let mut set = Changes::new();
280                    set.rewrite(inst, plan);
281                    set.remove(carried.inst);
282                    if set.commit(func, &mut reads, names, machine).is_ok() {
283                        pending.moved(carried.inst, &[inst]);
284                        waiting = None;
285                        done += 1;
286                    }
287                }
288            }
289            if barrier {
290                waiting = None;
291            }
292            if let Some(carried) = waiting {
293                if at - carried.at >= WINDOW || writes_what_it_reads(func, inst, &carried) {
294                    waiting = None;
295                }
296            }
297            if let Some(load) = FOLDS.iter().find(|fold| fold.load == bare).map(|fold| fold.load) {
298                let operands = &func[func[inst].operands];
299                if let Some(first) = operands.first().filter(|operand| operand.role.is_def()) {
300                    waiting = Some(Waiting { inst, reg: first.reg, load, at });
301                }
302            }
303        }
304    }
305    done
306}
307
308/// Whether this instruction writes a register the carried load needs left alone.
309///
310/// The registers its address reads, and the register it wrote. The second is there for the same
311/// reason the first is: a virtual register cannot be written twice while the IR is in SSA form, and
312/// these are the physical ones a function has before the allocator runs.
313fn writes_what_it_reads(func: &Func, inst: Inst, carried: &Waiting) -> bool {
314    let written: Vec<Reg> = func[func[inst].operands]
315        .iter()
316        .filter(|operand| operand.role.is_def())
317        .map(|operand| operand.reg)
318        .collect();
319    func[func[carried.inst].operands].iter().any(|operand| written.contains(&operand.reg))
320}
321
322/// What this instruction becomes with the carried load inside it, or `None`.
323///
324/// Nothing here changes anything. What comes back is a proposal, and whether the target has the
325/// instruction it describes is [`Changes`]'s answer rather than this one.
326fn joined(
327    func: &Func,
328    reads: &Reads,
329    carried: Waiting,
330    machine: &MachineInsts,
331    names: &mut Interner,
332    inst: Inst,
333    bare: &str,
334) -> Option<Plan> {
335    let fold = FOLDS.iter().find(|fold| fold.from == bare)?;
336    if carried.load != fold.load || reads.count(carried.reg) != 1 {
337        return None;
338    }
339    let operands = func[func[inst].operands].to_vec();
340    let [answer, first, second] = operands[..] else { return None };
341    // The second source is the one the memory operand replaces, because the answer is tied to the
342    // first. Where the load feeds the first source instead and the operation commutes, the two are
343    // swapped, which leaves the instruction computing what it computed.
344    let kept = if second.reg == carried.reg {
345        first
346    } else if fold.commutes && first.reg == carried.reg {
347        second
348    } else {
349        return None;
350    };
351    let load = carried.inst;
352    let address = func[func[load].operands][1..].to_vec();
353    let mut amode = func[func[load].mem?];
354    // The registers an address names are operands behind the ones the instruction writes down, and
355    // there is one of those in front of them here where there was none in front of them in the
356    // load, so every position the mode holds moves along by one.
357    amode.base = amode.base.map(|at| at + 1);
358    amode.index = amode.index.map(|at| at + 1);
359    let into = names.intern(&format!("{}{}", machine.prefix, fold.into));
360    Some(Plan {
361        opcode: Opcode::new(into),
362        operands: [answer, kept].into_iter().chain(address).collect(),
363        imm: None,
364        amode: Some(amode),
365        symbol: func[load].symbol,
366    })
367}
368
369#[cfg(test)]
370mod tests {
371    use rucc_mir::{self as mir, Constraint, Mem, Operand};
372    use rucc_target::x86_64::{GPR, MACHINE};
373
374    use super::*;
375
376    /// A function with one block, and the names it was built with.
377    fn empty() -> (Interner, Func, mir::Block) {
378        let mut names = Interner::new();
379        let mut func = Func::new(names.intern("f"));
380        let block = func.create_block();
381        (names, func, block)
382    }
383
384    /// The opcode of that name on this target.
385    fn op(names: &mut Interner, name: &str) -> Opcode {
386        Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
387    }
388
389    /// A load of eight bytes off that register.
390    fn load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
391        let into = func.new_vreg(GPR);
392        let mov = op(names, "mov_rm_64");
393        func.build(block, mov)
394            .def(into, GPR)
395            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
396            .finish();
397        into
398    }
399
400    /// Two-address arithmetic of that name on those two registers, in that order.
401    fn alu(
402        func: &mut Func,
403        names: &mut Interner,
404        block: mir::Block,
405        name: &str,
406        first: Reg,
407        second: Reg,
408    ) -> Reg {
409        let answer = func.new_vreg(GPR);
410        let opcode = op(names, name);
411        func.build(block, opcode)
412            .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
413            .uses(first, GPR)
414            .uses(second, GPR)
415            .finish();
416        answer
417    }
418
419    /// What every instruction in a block came to, as opcodes.
420    fn shape(func: &Func, names: &Interner, block: mir::Block) -> Vec<String> {
421        func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
422    }
423
424    /// The pass, with lists nothing is on.
425    fn combine(func: &mut Func, names: &mut Interner) -> usize {
426        let mut addresses = Vec::new();
427        let mut arguments = Vec::new();
428        let mut dynamic = Vec::new();
429        let mut pending =
430            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
431        loads(func, &MACHINE, names, &mut pending)
432    }
433
434    /// The shape the whole pass is for.
435    #[test]
436    fn a_load_read_once_by_an_addition_becomes_its_memory_operand() {
437        let (mut names, mut func, block) = empty();
438        let base = func.new_vreg(GPR);
439        let other = func.new_vreg(GPR);
440        let word = load(&mut func, &mut names, block, base);
441        alu(&mut func, &mut names, block, "add_rr_64", other, word);
442
443        assert_eq!(combine(&mut func, &mut names), 1);
444        assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
445        let inst = func.insts(block).next().expect("the addition");
446        let mem = func[inst].mem.expect("the addition reads memory now");
447        assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
448        assert_eq!(func[mem].base, Some(2), "and names the operand behind the source it kept");
449        assert_eq!(func[func[inst].operands][1].reg, other, "the source it kept");
450        assert_eq!(func[func[inst].operands][2].reg, base, "the address it took on");
451    }
452
453    /// The same load feeding the source the answer is tied to. The two sources are swapped, which
454    /// an addition does not mind and is what lets this fold at all.
455    #[test]
456    fn a_load_feeding_the_first_source_of_an_addition_is_swapped_and_folded() {
457        let (mut names, mut func, block) = empty();
458        let base = func.new_vreg(GPR);
459        let other = func.new_vreg(GPR);
460        let word = load(&mut func, &mut names, block, base);
461        alu(&mut func, &mut names, block, "add_rr_64", word, other);
462
463        assert_eq!(combine(&mut func, &mut names), 1);
464        assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
465        let inst = func.insts(block).next().expect("the addition");
466        assert_eq!(func[func[inst].operands][1].reg, other);
467    }
468
469    /// A subtraction with the load on the left, which is the one place the swap above would change
470    /// the answer.
471    #[test]
472    fn a_load_feeding_the_left_of_a_subtraction_stays_a_load() {
473        let (mut names, mut func, block) = empty();
474        let base = func.new_vreg(GPR);
475        let other = func.new_vreg(GPR);
476        let word = load(&mut func, &mut names, block, base);
477        alu(&mut func, &mut names, block, "sub_rr_64", word, other);
478
479        assert_eq!(combine(&mut func, &mut names), 0);
480        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.sub_rr_64"]);
481    }
482
483    /// And the same subtraction the other way round, which is the one that folds.
484    #[test]
485    fn a_load_feeding_the_right_of_a_subtraction_folds() {
486        let (mut names, mut func, block) = empty();
487        let base = func.new_vreg(GPR);
488        let other = func.new_vreg(GPR);
489        let word = load(&mut func, &mut names, block, base);
490        alu(&mut func, &mut names, block, "sub_rr_64", other, word);
491
492        assert_eq!(combine(&mut func, &mut names), 1);
493        assert_eq!(shape(&func, &names, block), ["x64.sub_rm_64"]);
494    }
495
496    /// Two readers. The load has to stay where it is for the second of them, so putting it into the
497    /// first buys nothing and reads the memory twice.
498    #[test]
499    fn a_load_two_instructions_read_stays_a_load() {
500        let (mut names, mut func, block) = empty();
501        let base = func.new_vreg(GPR);
502        let other = func.new_vreg(GPR);
503        let word = load(&mut func, &mut names, block, base);
504        alu(&mut func, &mut names, block, "add_rr_64", other, word);
505        alu(&mut func, &mut names, block, "xor_rr_64", other, word);
506
507        assert_eq!(combine(&mut func, &mut names), 0);
508        assert_eq!(
509            shape(&func, &names, block),
510            ["x64.mov_rm_64", "x64.add_rr_64", "x64.xor_rr_64"]
511        );
512    }
513
514    /// A store between the two. Whether it writes what the load reads is a question about two
515    /// addresses, and the answer to not being able to tell is to leave the load where it is.
516    #[test]
517    fn a_load_with_a_store_between_it_and_its_reader_stays_a_load() {
518        let (mut names, mut func, block) = empty();
519        let base = func.new_vreg(GPR);
520        let other = func.new_vreg(GPR);
521        let word = load(&mut func, &mut names, block, base);
522        let store = op(&mut names, "mov_mr_64");
523        func.build(block, store).uses(other, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
524        alu(&mut func, &mut names, block, "add_rr_64", other, word);
525
526        assert_eq!(combine(&mut func, &mut names), 0);
527        assert_eq!(
528            shape(&func, &names, block),
529            ["x64.mov_rm_64", "x64.mov_mr_64", "x64.add_rr_64"]
530        );
531    }
532
533    /// Another load between the two, which writes nothing and is still not passed.
534    ///
535    /// This is the one that would be wrong if the walk asked only about writes. Where both reads
536    /// are `volatile` the program said which of them happens first, and nothing here can tell that
537    /// program from the one that did not say it, so neither may be reordered.
538    #[test]
539    fn a_load_with_another_load_between_it_and_its_reader_stays_a_load() {
540        let (mut names, mut func, block) = empty();
541        let base = func.new_vreg(GPR);
542        let other = func.new_vreg(GPR);
543        let word = load(&mut func, &mut names, block, base);
544        load(&mut func, &mut names, block, other);
545        alu(&mut func, &mut names, block, "add_rr_64", other, word);
546
547        assert_eq!(combine(&mut func, &mut names), 0);
548        assert_eq!(
549            shape(&func, &names, block),
550            ["x64.mov_rm_64", "x64.mov_rm_64", "x64.add_rr_64"]
551        );
552    }
553
554    /// The second of two loads, read by arithmetic that reads the first as well. Nothing moves past
555    /// anything, which is what makes this one the shape the pass is allowed to take.
556    #[test]
557    fn the_later_of_two_loads_is_the_one_that_folds() {
558        let (mut names, mut func, block) = empty();
559        let base = func.new_vreg(GPR);
560        let other = func.new_vreg(GPR);
561        let first = load(&mut func, &mut names, block, base);
562        let second = load(&mut func, &mut names, block, other);
563        alu(&mut func, &mut names, block, "add_rr_64", first, second);
564
565        assert_eq!(combine(&mut func, &mut names), 1);
566        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rm_64"]);
567        let addition = func.insts(block).nth(1).expect("the addition");
568        assert_eq!(func[func[addition].operands][1].reg, first, "the earlier load is still read");
569        assert_eq!(func[func[addition].operands][2].reg, other, "and the later one is the address");
570    }
571
572    /// A call between the two. What a call does to memory is not in the instruction, so it is the
573    /// same answer as the store and reached without asking about the address.
574    #[test]
575    fn a_load_with_a_call_between_it_and_its_reader_stays_a_load() {
576        let (mut names, mut func, block) = empty();
577        let base = func.new_vreg(GPR);
578        let other = func.new_vreg(GPR);
579        let word = load(&mut func, &mut names, block, base);
580        let call = op(&mut names, "call");
581        func.build(block, call).finish();
582        alu(&mut func, &mut names, block, "add_rr_64", other, word);
583
584        assert_eq!(combine(&mut func, &mut names), 0);
585        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.call", "x64.add_rr_64"]);
586    }
587
588    /// Something writing the register the address reads. A physical register is the only one this
589    /// can happen to while the IR is in SSA form, and the frame is addressed through two of them.
590    #[test]
591    fn a_load_whose_address_register_is_written_between_the_two_stays_a_load() {
592        let (mut names, mut func, block) = empty();
593        let base = Reg::physical(rucc_target::x86_64::RSP);
594        let other = func.new_vreg(GPR);
595        let word = load(&mut func, &mut names, block, base);
596        let sub = op(&mut names, "sub_ri_64");
597        func.build(block, sub)
598            .operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
599            .uses(base, GPR)
600            .imm(32)
601            .finish();
602        alu(&mut func, &mut names, block, "add_rr_64", other, word);
603
604        assert_eq!(combine(&mut func, &mut names), 0);
605    }
606
607    /// A load of four bytes under an addition of eight. The register held what the load put in it
608    /// and a memory operand holds what is at the address, which is a different number of bytes.
609    #[test]
610    fn a_load_of_the_wrong_width_stays_a_load() {
611        let (mut names, mut func, block) = empty();
612        let base = func.new_vreg(GPR);
613        let other = func.new_vreg(GPR);
614        let into = func.new_vreg(GPR);
615        let narrow = op(&mut names, "mov_rm_32");
616        func.build(block, narrow).def(into, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
617        alu(&mut func, &mut names, block, "add_rr_64", other, into);
618
619        assert_eq!(combine(&mut func, &mut names), 0);
620        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_32", "x64.add_rr_64"]);
621    }
622
623    /// A load whose value leaves the block on an edge. It is read by nothing in any operand vector
624    /// and is read all the same, which is the count that is easy to get wrong.
625    #[test]
626    fn a_load_whose_value_an_edge_carries_stays_a_load() {
627        let (mut names, mut func, block) = empty();
628        let next = func.create_block();
629        let base = func.new_vreg(GPR);
630        let other = func.new_vreg(GPR);
631        let word = load(&mut func, &mut names, block, base);
632        alu(&mut func, &mut names, block, "add_rr_64", other, word);
633        let arrived = func.new_vreg(GPR);
634        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
635        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![word])];
636
637        assert_eq!(combine(&mut func, &mut names), 0);
638        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
639    }
640
641    /// A reader in another block, which is the whole of what block local means here.
642    #[test]
643    fn a_reader_in_another_block_stays_where_it_is() {
644        let (mut names, mut func, block) = empty();
645        let next = func.create_block();
646        let base = func.new_vreg(GPR);
647        let other = func.new_vreg(GPR);
648        let word = load(&mut func, &mut names, block, base);
649        alu(&mut func, &mut names, next, "add_rr_64", other, word);
650
651        assert_eq!(combine(&mut func, &mut names), 0);
652        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
653        assert_eq!(shape(&func, &names, next), ["x64.add_rr_64"]);
654    }
655
656    /// A reader further down the block than the window reaches.
657    #[test]
658    fn a_reader_past_the_window_stays_where_it_is() {
659        let (mut names, mut func, block) = empty();
660        let base = func.new_vreg(GPR);
661        let other = func.new_vreg(GPR);
662        let word = load(&mut func, &mut names, block, base);
663        let nop = op(&mut names, "nop");
664        for _ in 0..WINDOW {
665            func.build(block, nop).finish();
666        }
667        alu(&mut func, &mut names, block, "add_rr_64", other, word);
668
669        assert_eq!(combine(&mut func, &mut names), 0);
670    }
671
672    /// And one instruction closer, which is the last place it still folds.
673    #[test]
674    fn a_reader_at_the_edge_of_the_window_folds() {
675        let (mut names, mut func, block) = empty();
676        let base = func.new_vreg(GPR);
677        let other = func.new_vreg(GPR);
678        let word = load(&mut func, &mut names, block, base);
679        let nop = op(&mut names, "nop");
680        for _ in 0..WINDOW - 1 {
681            func.build(block, nop).finish();
682        }
683        alu(&mut func, &mut names, block, "add_rr_64", other, word);
684
685        assert_eq!(combine(&mut func, &mut names), 1);
686    }
687
688    /// The entry a frame layout is waiting on moves with the load. Without this the displacement
689    /// of a local would be written into an instruction that has gone.
690    #[test]
691    fn the_frame_entry_of_a_load_that_moves_goes_with_it() {
692        let (mut names, mut func, block) = empty();
693        let base = Reg::physical(rucc_target::x86_64::RSP);
694        let other = func.new_vreg(GPR);
695        let word = load(&mut func, &mut names, block, base);
696        let reader = func.insts(block).nth(1);
697        assert!(reader.is_none(), "the block holds the load alone so far");
698        alu(&mut func, &mut names, block, "add_rr_64", other, word);
699        let held = func.insts(block).next().expect("the load");
700
701        let mut addresses = vec![(held, 3usize)];
702        let mut arguments = Vec::new();
703        let mut dynamic = Vec::new();
704        let mut pending =
705            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
706        assert_eq!(loads(&mut func, &MACHINE, &mut names, &mut pending), 1);
707
708        let inst = func.insts(block).next().expect("the addition");
709        assert_eq!(addresses, [(inst, 3usize)], "the entry names the instruction that took it");
710    }
711
712    /// Every row of the table names instructions this target has, and names a load and an
713    /// arithmetic whose widths agree. A row that got one of the three wrong would propose an
714    /// instruction the change framework turns down, which is a fold that silently never happens.
715    #[test]
716    fn every_row_of_the_table_is_three_instructions_this_target_has() {
717        for fold in FOLDS {
718            assert!(MACHINE.has(fold.from), "{} is not an instruction", fold.from);
719            assert!(MACHINE.has(fold.into), "{} is not an instruction", fold.into);
720            assert!(MACHINE.has(fold.load), "{} is not an instruction", fold.load);
721            let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
722            assert_eq!(width(fold.from), width(fold.into), "{} changes width", fold.from);
723            assert_eq!(width(fold.from), width(fold.load), "{} loads another width", fold.from);
724            assert!((MACHINE.takes_mem)(fold.into), "{} reads no memory", fold.into);
725            assert!(!(MACHINE.takes_mem)(fold.from), "{} already reads memory", fold.from);
726        }
727    }
728
729    /// One row per arithmetic instruction the target has that could take one. The count is here so
730    /// that an instruction added to the target without a row shows up as a number rather than as a
731    /// fold nobody noticed was missing.
732    #[test]
733    fn the_table_covers_the_arithmetic_this_target_has() {
734        assert_eq!(FOLDS.len(), 23, "six operations at four widths, less the eight bit multiply");
735        let commuting = FOLDS.iter().filter(|fold| fold.commutes).count();
736        assert_eq!(commuting, 19, "everything but the four subtractions");
737    }
738}