Skip to main content

rucc_codegen/
fold.rs

1//! Folding an address computation into the memory operand of whatever reads it.
2//!
3//! Design: `spec/10-backend.md` section 10.9, and `spec/optimizer/37-machine-level-optimization.md`
4//! section 37.4.
5//!
6//! The selector matches one instruction at a time and offers it its operands' operands, which is
7//! two levels of term and is exactly what an address needs to become a `lea`: `a + i * 4` is an
8//! add at the root with a multiply under it. Put that same address under a load and everything
9//! moves down a level, the multiply is at level two, and no plan the selector has reaches it. So
10//! an array read comes out of selection as two instructions, the `lea` that works the address out
11//! and the `mov` that reads through it, and the second one's addressing mode holds nothing but a
12//! base.
13//!
14//! Which is a pair a peephole can see. When an instruction reads the register a `lea` wrote as the
15//! base of its memory operand, the two addresses compose: the reader's displacement is a constant
16//! added to an address the `lea` already worked out, so adding the two displacements together gives
17//! the address the reader wanted in the mode the `lea` was using.
18//!
19//! The question is asked of the readers together rather than one at a time, which is what section
20//! 37.4 says the pass is really for. One address read at several offsets is what a structure
21//! written field by field comes out as, and what a loop the unroller took apart comes out as, and
22//! in neither of those does any one reader own the address. If every reader can take it then
23//! nothing reads the `lea` any more and it goes, and the arithmetic moved into addressing modes
24//! that were doing an addition anyway. If one reader cannot, folding into the rest buys nothing:
25//! the `lea` stays where it is for the one that refused, the address is worked out twice rather
26//! than once, and the registers it reads are now live across every reader as well. So it is all of
27//! them or none of them, and that is a property of the set rather than of a pair.
28//!
29//! # What it will not do
30//!
31//! A set with a reader in it that cannot take the address. Each of the refusals below is one
32//! reader's, and any one of them turns down the whole set it belongs to.
33//!
34//! An address relative to a symbol, with more than one reader. A reader that reads through a
35//! register has room in it for a register and a displacement, and an address made of registers and
36//! a displacement goes into that room whoever takes it. A symbol does not: the reader has to name
37//! the symbol, which is a whole address word rather than a register number, so each reader that
38//! takes one grows by the difference and several readers pay it several times while the `lea` is
39//! saved once. Taking those as well loses 2643 bytes over the corpus at -O2 and gains 386, and the
40//! loss is almost all soft float and bit counting expansions, which read one global thirty or
41//! forty times each. One reader keeps the old answer, since there the address word is written once
42//! either way and what goes is the whole `lea`.
43//!
44//! Two indexes. The reader having an index of its own means the composed address wants two scaled
45//! registers and this machine, like every machine, has one. Nothing looks for a way to put them
46//! together because there is not one.
47//!
48//! A displacement that does not fit. The two are added as `i64` and the answer has to be an `i32`,
49//! which is what the field holds. It is not a case that comes up in a program anybody wrote, and
50//! the check is there because the alternative to checking is wrapping.
51//!
52//! A reader in another block. Folding moves the work from where the `lea` is to where the reader
53//! is, and across a block boundary that can mean moving it into a loop. The same rule and the same
54//! reason as `crate::lower::Lowering::foldable`, which is the selector's version of this question.
55//!
56//! A register that something writes between the address and the last of its readers. Machine IR is
57//! in SSA form until the allocator has run, so a virtual register cannot be, but a physical one
58//! can: the frame pointer and the stack pointer are already physical here, and a call in between
59//! writes every register it is allowed to. Rather than ask which registers are the exceptions, the
60//! walk below drops a candidate the moment anything writes a register its address reads. The last
61//! reader rather than the first is what makes this the set's question too, since a write after the
62//! first reader and before the second is a write the one at a time version would never have seen.
63//!
64//! # The addresses into the frame
65//!
66//! A local's place in the frame and an argument's place in the caller's area is a distance from the
67//! stack pointer, and there is no frame until the allocator has finished, so [`crate::lower`]
68//! leaves those instructions with a zero in the displacement and [`crate::finish`] writes the
69//! number in later against a list of which instruction is which.
70//!
71//! This used to refuse them for that reason, and refusing was expensive: it is the shape of every
72//! access to a local that has to go through its address, and of every argument that arrives in the
73//! caller's area. What it takes to fold one is that the entry moves. The instruction the list names
74//! goes away and the ones that took the
75//! address arrive, so [`Pending`] rewrites the list as the fold is applied, and `finish` adds the
76//! frame's offset to the displacement rather than assigning it, because the reader brought a
77//! displacement of its own and the field it is reading is some way past where the object starts.
78//! tamnd/rucc#784.
79//!
80//! What they do not get is the whole of the set rule above. An address into the frame is off the
81//! stack pointer and a memory operand based on the stack pointer needs an index byte on this
82//! machine whether or not anything is indexed, so a reader that takes one grows by more than a
83//! reader that takes an address in an ordinary register does. Past three of them the bytes the
84//! readers put on are more than the whole `lea` was, which is the same arithmetic as the symbol
85//! above and comes out at a different number. `FRAME_READERS` below has the measurement.
86//!
87//! # Where it runs
88//!
89//! After selection and before the allocator, which is the one window where both instructions
90//! exist and the registers are still virtual. Running it after allocation would work on the
91//! arithmetic and would be reading a register file where the reader's base may have been reused
92//! for something else in between.
93
94use std::collections::HashMap;
95
96use rucc_base::Interner;
97use rucc_mir as mir;
98use rucc_target::{FrameInsts, Role};
99
100/// The addresses [`crate::finish`] has still to write a displacement into.
101///
102/// Three lists, because the frame holds three kinds of place this pass runs before the layout of:
103/// a local's address is an offset into this function's own objects, a stack argument's is an offset
104/// into the caller's area, and a variable length array's is an offset above wherever the stack
105/// pointer ended up. What they have in common is the shape, a `lea` off the stack pointer with the
106/// displacement left at zero, and what this type is for is that folding one of those away has to
107/// move the entry rather than lose it.
108///
109/// This used to be a set of instructions the pass refused to touch, and refusing was expensive.
110/// Every access to a local through its address was a `lea` and then a memory instruction reading
111/// through the register it wrote, which is one instruction more than it needs, on the shape any
112/// function whose locals have their address taken is full of. tamnd/rucc#784.
113#[derive(Debug)]
114pub struct Pending<'a> {
115    /// Which instruction carries the address of which of this function's stack objects.
116    pub addresses: &'a mut Vec<(mir::Inst, usize)>,
117    /// Which instruction reads which of the arguments the caller passed on the stack.
118    pub arguments: &'a mut Vec<(mir::Inst, u32)>,
119    /// Which instructions carry the address of a local whose size the program worked out.
120    ///
121    /// There is no number beside one of these, because where a variable length array starts is not
122    /// a place the frame layout hands back: the bytes are already off the stack pointer by the time
123    /// the address is taken, so what gets written in is how much of the bottom of the frame the
124    /// arguments of a call keep, which is the same for all of them.
125    pub dynamic: &'a mut Vec<mir::Inst>,
126}
127
128impl Pending<'_> {
129    /// Moves an entry from an address that has gone to the instructions that took it.
130    ///
131    /// One entry becomes as many as there were readers, because an address every reader has room
132    /// for is handed to all of them, and each of those now carries a displacement of its own that
133    /// the frame layout has still to be added to.
134    ///
135    /// An address on any of the lists reads the stack pointer and nothing else, so it never reads a
136    /// register another one of them wrote, which is what makes it impossible for a reader to end up
137    /// on a list twice and be given two offsets.
138    fn moved(&mut self, from: mir::Inst, into: &[mir::Inst]) {
139        move_entries(self.addresses, from, into);
140        move_entries(self.arguments, from, into);
141        if let Some(at) = self.dynamic.iter().position(|&inst| inst == from) {
142            self.dynamic.splice(at..=at, into.iter().copied());
143        }
144    }
145
146    /// Whether this instruction is on one of the lists, which is how many readers it may go to.
147    fn holds(&self, inst: mir::Inst) -> bool {
148        let named = self.addresses.iter().map(|&(at, _)| at);
149        let listed = named.chain(self.arguments.iter().map(|&(at, _)| at));
150        listed.chain(self.dynamic.iter().copied()).any(|at| at == inst)
151    }
152}
153
154/// How many readers an address into the frame may be handed to.
155///
156/// There is a limit at all for the same reason a symbol has one, in the list above. An address into
157/// the frame is off the stack pointer, and a memory operand whose base is the stack pointer needs
158/// an index byte on this machine whether or not anything is indexed, so every reader that takes one
159/// grows by that byte and by the displacement while the `lea` is saved once. Reading through a
160/// register the `lea` wrote is three or four bytes and reading the same place off the stack pointer
161/// is five or eight, against the five or eight the `lea` itself costs, so the readers are ahead of
162/// it while there are few of them and behind it once there are enough.
163///
164/// Three is where they turn, measured. Over the 1838 corpus programs that come out of both
165/// compilers at `-O2`, one reader is 757 bytes better than folding none of them, two is 806, three
166/// is 868, four is 848 and five is 520. Handing them to every reader with room, which is what every
167/// other address gets, is 528 bytes worse than folding none: 97 programs larger by 1117 bytes
168/// against 100 smaller by 589. Up to three, only two programs anywhere in the corpus are larger at
169/// all, by two bytes each.
170///
171/// 690 of the 868 are the ten `long-double` programs, which is the shape this is about at its
172/// plainest. A `long double` argument arrives in the caller's area and the `fld` that reads it is
173/// its only reader, so the address goes and the read costs nothing more than it did.
174const FRAME_READERS: usize = 3;
175
176/// The half of [`Pending::moved`] that does not care what the entry says.
177fn move_entries<T: Copy>(list: &mut Vec<(mir::Inst, T)>, from: mir::Inst, into: &[mir::Inst]) {
178    let Some(at) = list.iter().position(|&(inst, _)| inst == from) else { return };
179    let (_, what) = list[at];
180    list.splice(at..=at, into.iter().map(|&inst| (inst, what)));
181}
182
183/// Folds every address computation that one memory operand reads, and gives back how many.
184///
185/// `pending` is the addresses [`crate::finish`] has still to write a displacement into, and folding
186/// one moves its entry to the instruction that took it. The displacement composed in by the fold
187/// stays where it is and the frame's offset is added to it later, which is why that write is an
188/// addition rather than an assignment.
189///
190/// Run after lowering and before allocation. Running it twice can find more than running it once.
191/// Folding a `lea` into a second `lea` leaves that second one foldable in turn, and the walk below
192/// takes those in the one pass since it goes forwards. What it does not take in the one pass is the
193/// other order, where the second `lea` has a reader of its own and goes before the first one's set
194/// is complete, and that is a set the next run finds whole.
195pub fn addresses(
196    func: &mut mir::Func,
197    insts: &FrameInsts,
198    names: &mut Interner,
199    pending: &mut Pending<'_>,
200) -> usize {
201    let lea = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.lea)));
202    let reads = reads(func);
203    let mut folded = 0;
204    for block in func.blocks().collect::<Vec<_>>() {
205        // One `lea` per register it wrote, along with the folds its readers so far have agreed to.
206        // A register leaves the table the moment the set can no longer be all of them: anything
207        // writes what the address reads, or a reader turns up that cannot take it.
208        let mut open: HashMap<mir::Reg, Open> = HashMap::new();
209        for inst in func.insts(block).collect::<Vec<_>>() {
210            if let Some(ready) = offer(func, &mut open, inst) {
211                for folding in &ready.folds {
212                    let operands = func.push_operands(&folding.operands);
213                    let mem = func.add_amode(folding.amode);
214                    func[folding.into].operands = operands;
215                    func[folding.into].mem = Some(mem);
216                    folded += 1;
217                }
218                let took: Vec<mir::Inst> = ready.folds.iter().map(|fold| fold.into).collect();
219                pending.moved(ready.from, &took);
220                func.remove_inst(ready.from);
221                // Anything still open that was going to fold into the instruction just removed is
222                // holding a plan for an instruction that is not there any more. That is a chain
223                // whose middle went first, and the outer address waits for the next run of the
224                // pass rather than being written into a gap.
225                open.retain(|_, held| held.folds.iter().all(|fold| fold.into != ready.from));
226            }
227            for written in written(func, inst) {
228                open.retain(|reg, held| *reg != written && !touches(func, held.from, written));
229            }
230            if func[inst].opcode == lea {
231                let room = if pending.holds(inst) { FRAME_READERS } else { usize::MAX };
232                match folding_def(func, &reads, inst) {
233                    Some((reg, wanted))
234                        if wanted <= room && (wanted == 1 || fits_every_reader(func, inst)) =>
235                    {
236                        open.insert(reg, Open { from: inst, wanted, folds: Vec::new() });
237                    }
238                    _ => {}
239                }
240            }
241        }
242    }
243    folded
244}
245
246/// An address computation whose readers are still being counted.
247struct Open {
248    /// The address instruction, which goes once every one of its readers has taken it.
249    from: mir::Inst,
250    /// How many reads of the register it wrote there are in the whole function.
251    wanted: usize,
252    /// The folds agreed to so far, which are applied together or not at all.
253    folds: Vec<Folding>,
254}
255
256/// Offers an instruction the addresses that are open, and gives back the set that is now complete.
257///
258/// Every open register this instruction reads either takes the address into its own memory operand
259/// or ends the chance for the whole set. Reading it any other way is what makes it a reader nothing
260/// can fold into, and one of those is enough, so the register is dropped rather than the read being
261/// passed over. Reading it twice in the one instruction counts as that too, since only one of the
262/// two reads is the memory operand and the other would be left naming a register nothing writes.
263fn offer(func: &mir::Func, open: &mut HashMap<mir::Reg, Open>, inst: mir::Inst) -> Option<Open> {
264    let folding = candidate(func, open, inst);
265    let takes = |reg: mir::Reg| folding.as_ref().is_some_and(|fold| fold.base == reg);
266    let refused: Vec<mir::Reg> = open
267        .keys()
268        .copied()
269        .filter(|&reg| {
270            let times = times_read(func, inst, reg);
271            times > 0 && !(times == 1 && takes(reg))
272        })
273        .collect();
274    for reg in refused {
275        open.remove(&reg);
276    }
277    let folding = folding?;
278    let base = folding.base;
279    let held = open.get_mut(&base)?;
280    held.folds.push(folding);
281    if held.folds.len() < held.wanted {
282        return None;
283    }
284    open.remove(&base)
285}
286
287/// Whether an address is one every reader can carry in the room it already has, which is what
288/// makes handing it to more than one of them free.
289///
290/// A reader that reads an address through a register has room in it for a register and for a
291/// displacement, and an address made of registers and a displacement fits in exactly that room
292/// however many readers take it. An address relative to a symbol does not. The reader was naming a
293/// register and now has to name the symbol, which is a whole address word rather than a register
294/// number, so each reader that takes it grows by the difference and several readers pay it several
295/// times over while the `lea` is only saved once.
296///
297/// The measurement is what settled the size of that: folding symbol relative addresses into every
298/// reader as well loses 2643 bytes over the corpus at -O2 against 386 gained, and the 2643 is
299/// almost all soft float and bit counting expansions, which read one global thirty or forty times
300/// each and are the longest runs of straight line code in the corpus.
301///
302/// One reader is a different question and keeps the old answer, since there the address word is
303/// written once either way and what goes is the whole `lea`.
304fn fits_every_reader(func: &mir::Func, inst: mir::Inst) -> bool {
305    func[inst].mem.is_some_and(|mem| func[mem].symbol.is_none())
306}
307
308/// How many of an instruction's operands read that register.
309fn times_read(func: &mir::Func, inst: mir::Inst, reg: mir::Reg) -> usize {
310    func[func[inst].operands]
311        .iter()
312        .filter(|operand| operand.role == Role::Use && operand.reg == reg)
313        .count()
314}
315
316/// How many times each virtual register is read, counting the arguments an edge carries.
317///
318/// A `lea` is only worth folding when the instructions folding it are the whole of what reads the
319/// register, since folding does not delete the `lea` for anybody else and doing the address twice
320/// is not a saving. The count is what says when the set is complete, and it is taken over the whole
321/// function rather than over the block, so a read anywhere else is a set that never completes and
322/// an address that stays where it is. An argument on an edge is a read like any other and is not in
323/// any operand vector, which is the one place this is easy to get wrong.
324///
325/// [`crate::layout`] asks the same question about the byte a comparison wrote, for the same
326/// reason and while the registers are still virtual for the same reason, so it reads this rather
327/// than counting again.
328pub(crate) fn reads(func: &mir::Func) -> HashMap<mir::Reg, usize> {
329    let mut counts = HashMap::new();
330    for block in func.blocks() {
331        for inst in func.insts(block) {
332            for operand in &func[func[inst].operands] {
333                if operand.role == Role::Use {
334                    *counts.entry(operand.reg).or_insert(0) += 1;
335                }
336            }
337        }
338        for call in &func[block].succs {
339            for &arg in &call.args {
340                *counts.entry(arg).or_insert(0) += 1;
341            }
342        }
343    }
344    counts
345}
346
347/// The one virtual register an instruction writes, and how many reads of it there are, when it
348/// writes exactly one and something reads it.
349///
350/// A register nothing reads is left alone rather than folded into nothing, since an address whose
351/// answer is never wanted is dead code and belongs to the pass that removes dead code.
352fn folding_def(
353    func: &mir::Func,
354    reads: &HashMap<mir::Reg, usize>,
355    inst: mir::Inst,
356) -> Option<(mir::Reg, usize)> {
357    let operands = &func[func[inst].operands];
358    let mut defs = operands.iter().filter(|operand| operand.role != Role::Use);
359    let def = defs.next()?;
360    if defs.next().is_some() || !def.reg.is_virtual() {
361        return None;
362    }
363    let wanted = *reads.get(&def.reg)?;
364    (wanted > 0).then_some((def.reg, wanted))
365}
366
367/// The registers an instruction writes.
368fn written(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
369    func[func[inst].operands]
370        .iter()
371        .filter(|operand| operand.role != Role::Use)
372        .map(|operand| operand.reg)
373        .collect()
374}
375
376/// Whether an address computation reads that register, which is what makes writing it the end of
377/// the chance to fold it.
378fn touches(func: &mir::Func, inst: mir::Inst, reg: mir::Reg) -> bool {
379    let Some(mem) = func[inst].mem else { return false };
380    let amode = func[mem];
381    let operands = &func[func[inst].operands];
382    [amode.base, amode.index]
383        .into_iter()
384        .flatten()
385        .filter_map(|at| operands.get(usize::from(at)))
386        .any(|operand| operand.reg == reg)
387}
388
389/// The register an instruction's memory operand reads as its base, when that is the whole of what
390/// its memory operand is.
391///
392/// A symbol or an index means the two addresses do not compose, and this is where both are turned
393/// down, because the reader is the half of the pair with no room left in it.
394fn base_reg(func: &mir::Func, inst: mir::Inst) -> Option<mir::Reg> {
395    let amode = func[func[inst].mem?];
396    if amode.index.is_some() || amode.symbol.is_some() || amode.got {
397        return None;
398    }
399    Some(func[func[inst].operands].get(usize::from(amode.base?))?.reg)
400}
401
402/// A fold that has been checked and not yet done.
403///
404/// Everything the rewrite needs is worked out here rather than after the decision, so that the
405/// decision is the last thing that can go either way and the rewrite itself is three assignments
406/// that cannot fail.
407struct Folding {
408    /// The reader this rewrites, which is not always the instruction being looked at, since the
409    /// set is applied when its last reader arrives rather than as each one agrees.
410    into: mir::Inst,
411    /// The register the address instruction wrote, which is what ties this to its set.
412    base: mir::Reg,
413    /// What the reader's operands become.
414    operands: Vec<mir::Operand>,
415    /// What the reader's addressing mode becomes.
416    amode: mir::Amode,
417}
418
419/// The `lea` whose address this instruction should read directly, and what reading it directly
420/// makes of the instruction.
421///
422/// The operand vector is rebuilt rather than edited because the registers a memory operand names
423/// come last in it, which is the invariant [`mir::InstBuilder::mem`] keeps and the printer and the
424/// allocator both read. Dropping the base the reader had and putting the `lea`'s base and index on
425/// the end keeps it, and the indices in the new addressing mode are worked out from the length
426/// rather than carried over.
427fn candidate(func: &mir::Func, open: &HashMap<mir::Reg, Open>, inst: mir::Inst) -> Option<Folding> {
428    let base = base_reg(func, inst)?;
429    let from = open.get(&base)?.from;
430    let address = func[func[from].mem?];
431    // The reader holds the base in its last operand, and [`offer`] is what checks that nothing else
432    // in the same instruction names it. So the composed address is the `lea`'s with the reader's
433    // displacement added, and the only thing that can go wrong is the width of the field it goes
434    // in.
435    let disp = i64::from(address.disp) + i64::from(func[func[inst].mem?].disp);
436    let mut amode = mir::Amode { disp: i32::try_from(disp).ok()?, ..address };
437
438    let taken = &func[func[from].operands];
439    let reader = &func[func[inst].operands];
440    let mut operands = reader.get(..reader.len().checked_sub(1)?)?.to_vec();
441    for (at, into) in [(address.base, &mut amode.base), (address.index, &mut amode.index)] {
442        let Some(at) = at else { continue };
443        operands.push(*taken.get(usize::from(at))?);
444        *into = Some(u8::try_from(operands.len() - 1).ok()?);
445    }
446    Some(Folding { into: inst, base, operands, amode })
447}
448
449#[cfg(test)]
450mod tests {
451    use rucc_target::x86_64::{FRAME, GPR, RDI};
452
453    use super::*;
454
455    /// A function with one block, and the names it was built with.
456    fn empty() -> (Interner, mir::Func, mir::Block) {
457        let mut names = Interner::new();
458        let mut func = mir::Func::new(names.intern("f"));
459        let block = func.create_block();
460        (names, func, block)
461    }
462
463    /// The pass, run over a function with nothing owed a frame offset, which is most of these.
464    ///
465    /// The lists are still there because the pass rewrites them, and a test that is about what it
466    /// wrote in them builds its own rather than calling this.
467    fn folds(func: &mut mir::Func, names: &mut Interner) -> usize {
468        let (mut locals, mut arguments, mut growable) = (Vec::new(), Vec::new(), Vec::new());
469        addresses(
470            func,
471            &FRAME,
472            names,
473            &mut Pending {
474                addresses: &mut locals,
475                arguments: &mut arguments,
476                dynamic: &mut growable,
477            },
478        )
479    }
480
481    /// The opcode of that name on this target.
482    fn op(names: &mut Interner, name: &str) -> mir::Opcode {
483        mir::Opcode::new(names.intern(&format!("{}{name}", FRAME.prefix)))
484    }
485
486    /// What every instruction in a block came to, as opcodes and addressing modes.
487    fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<(String, mir::Amode)> {
488        func.insts(block)
489            .map(|inst| {
490                let amode = func[inst].mem.map_or(mir::Amode::NOTHING, |mem| func[mem]);
491                (names.resolve(func[inst].opcode.name()).to_owned(), amode)
492            })
493            .collect()
494    }
495
496    /// The registers a memory operand names, in the order the addressing mode names them.
497    fn address_regs(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
498        let amode = func[func[inst].mem.expect("a memory operand")];
499        let operands = &func[func[inst].operands];
500        [amode.base, amode.index]
501            .into_iter()
502            .flatten()
503            .map(|at| operands[usize::from(at)].reg)
504            .collect()
505    }
506
507    /// An array read as selection leaves it: a `lea` that scales the index and adds the base, and
508    /// a `mov` that reads through the register it wrote.
509    #[test]
510    fn an_address_a_load_reads_once_becomes_the_load_s_own_addressing_mode() {
511        let (mut names, mut func, block) = empty();
512        let array = func.new_vreg(GPR);
513        let index = func.new_vreg(GPR);
514        let address = func.new_vreg(GPR);
515        let value = func.new_vreg(GPR);
516        let lea = op(&mut names, FRAME.lea);
517        let load = op(&mut names, "mov_rm_32");
518        func.build(block, lea)
519            .def(address, GPR)
520            .mem(
521                mir::Mem::at(mir::Operand::read(array, GPR))
522                    .indexed(mir::Operand::read(index, GPR), 4),
523            )
524            .finish();
525        func.build(block, load)
526            .def(value, GPR)
527            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
528            .finish();
529
530        assert_eq!(folds(&mut func, &mut names), 1);
531
532        let left = shape(&func, &names, block);
533        assert_eq!(left.len(), 1, "the address is worked out twice: {left:?}");
534        assert_eq!(left[0].0, format!("{}mov_rm_32", FRAME.prefix));
535        assert_eq!(left[0].1.scale, 4);
536        assert_eq!(left[0].1.disp, 0);
537        let inst = func.insts(block).next().expect("the load is still there");
538        assert_eq!(address_regs(&func, inst), vec![array, index], "the load reads the wrong pair");
539    }
540
541    /// The two displacements are added, which is the whole of what composing them takes when one
542    /// of the two addresses has room for an index and the other has none.
543    #[test]
544    fn the_displacements_of_the_two_addresses_are_added() {
545        let (mut names, mut func, block) = empty();
546        let array = func.new_vreg(GPR);
547        let address = func.new_vreg(GPR);
548        let value = func.new_vreg(GPR);
549        let lea = op(&mut names, FRAME.lea);
550        let load = op(&mut names, "mov_rm_32");
551        func.build(block, lea)
552            .def(address, GPR)
553            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
554            .finish();
555        func.build(block, load)
556            .def(value, GPR)
557            .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(8))
558            .finish();
559
560        assert_eq!(folds(&mut func, &mut names), 1);
561
562        let left = shape(&func, &names, block);
563        assert_eq!(left.len(), 1);
564        assert_eq!(left[0].1.disp, 24, "the field is at the sum of the two offsets or nowhere");
565    }
566
567    /// A store keeps the value it writes, which is the operand the address does not name, and the
568    /// rebuilt operand vector has to hold on to it.
569    #[test]
570    fn a_store_keeps_the_value_it_is_storing() {
571        let (mut names, mut func, block) = empty();
572        let array = func.new_vreg(GPR);
573        let index = func.new_vreg(GPR);
574        let address = func.new_vreg(GPR);
575        let value = func.new_vreg(GPR);
576        let lea = op(&mut names, FRAME.lea);
577        let store = op(&mut names, "mov_mr_32");
578        func.build(block, lea)
579            .def(address, GPR)
580            .mem(
581                mir::Mem::at(mir::Operand::read(array, GPR))
582                    .indexed(mir::Operand::read(index, GPR), 8),
583            )
584            .finish();
585        func.build(block, store)
586            .uses(value, GPR)
587            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
588            .finish();
589
590        assert_eq!(folds(&mut func, &mut names), 1);
591
592        let inst = func.insts(block).next().expect("the store is still there");
593        let regs: Vec<mir::Reg> = func[func[inst].operands].iter().map(|op| op.reg).collect();
594        assert_eq!(regs, vec![value, array, index], "the value the store writes went missing");
595        assert_eq!(func[func[inst].mem.expect("a memory operand")].scale, 8);
596    }
597
598    /// One address at three offsets, which is what a structure written field by field comes out
599    /// as. Every reader can carry the whole of it in its own mode, so all three take it and the
600    /// `lea` has nothing left reading it. This is the case section 37.4 says the pass is for.
601    #[test]
602    fn an_address_every_reader_can_take_is_folded_into_all_of_them() {
603        let (mut names, mut func, block) = empty();
604        let array = func.new_vreg(GPR);
605        let address = func.new_vreg(GPR);
606        let value = func.new_vreg(GPR);
607        let lea = op(&mut names, FRAME.lea);
608        let store = op(&mut names, "mov_mr_32");
609        func.build(block, lea)
610            .def(address, GPR)
611            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
612            .finish();
613        for offset in [0, 12, 28] {
614            func.build(block, store)
615                .uses(value, GPR)
616                .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(offset))
617                .finish();
618        }
619
620        assert_eq!(folds(&mut func, &mut names), 3);
621
622        let left = shape(&func, &names, block);
623        assert_eq!(left.len(), 3, "the address is still worked out on its own: {left:?}");
624        let disps: Vec<i32> = left.iter().map(|(_, amode)| amode.disp).collect();
625        assert_eq!(disps, vec![16, 28, 44], "each store is at its own offset from the address");
626        for inst in func.insts(block).collect::<Vec<_>>() {
627            assert_eq!(address_regs(&func, inst), vec![array]);
628        }
629    }
630
631    /// Three readers and the middle one has an index of its own. Folding into the other two would
632    /// leave the `lea` where it is for the third, so the address would be worked out twice rather
633    /// than once and the two folds would have bought nothing but a longer live range for what it
634    /// reads. All or nothing over the set means none of them.
635    #[test]
636    fn an_address_one_reader_cannot_take_is_folded_into_none_of_them() {
637        let (mut names, mut func, block) = empty();
638        let array = func.new_vreg(GPR);
639        let index = func.new_vreg(GPR);
640        let address = func.new_vreg(GPR);
641        let lea = op(&mut names, FRAME.lea);
642        let load = op(&mut names, "mov_rm_32");
643        func.build(block, lea)
644            .def(address, GPR)
645            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
646            .finish();
647        for at in 0..3 {
648            let value = func.new_vreg(GPR);
649            let mem = mir::Mem::at(mir::Operand::read(address, GPR));
650            let mem = if at == 1 { mem.indexed(mir::Operand::read(index, GPR), 4) } else { mem };
651            func.build(block, load).def(value, GPR).mem(mem).finish();
652        }
653
654        assert_eq!(folds(&mut func, &mut names), 0);
655        assert_eq!(shape(&func, &names, block).len(), 4);
656    }
657
658    /// An indexed address with two readers, which both of them can take. The index goes into the
659    /// room the reader already has for one, the same as the base does, so this is the ordinary
660    /// case rather than a special one.
661    #[test]
662    fn an_indexed_address_every_reader_can_take_is_folded_into_all_of_them() {
663        let (mut names, mut func, block) = empty();
664        let array = func.new_vreg(GPR);
665        let index = func.new_vreg(GPR);
666        let address = func.new_vreg(GPR);
667        let lea = op(&mut names, FRAME.lea);
668        let load = op(&mut names, "mov_rm_32");
669        func.build(block, lea)
670            .def(address, GPR)
671            .mem(
672                mir::Mem::at(mir::Operand::read(array, GPR))
673                    .indexed(mir::Operand::read(index, GPR), 4),
674            )
675            .finish();
676        for offset in [0, 8] {
677            let value = func.new_vreg(GPR);
678            func.build(block, load)
679                .def(value, GPR)
680                .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(offset))
681                .finish();
682        }
683
684        assert_eq!(folds(&mut func, &mut names), 2);
685
686        let left = shape(&func, &names, block);
687        assert_eq!(left.len(), 2, "the address is gone and both loads carry it: {left:?}");
688        let disps: Vec<i32> = left.iter().map(|(_, amode)| amode.disp).collect();
689        assert_eq!(disps, vec![0, 8], "each load is at its own offset from the address");
690        for inst in func.insts(block).collect::<Vec<_>>() {
691            assert_eq!(address_regs(&func, inst), vec![array, index]);
692        }
693    }
694
695    /// A symbol relative address with two readers, which both of them could take and which is left
696    /// alone anyway. Each reader would have to name the symbol where it names a register now, and
697    /// a symbol is a whole address word, so two readers write that word twice to save one `lea`
698    /// that wrote it once. The corpus says that is a loss well before the reader count gets large.
699    #[test]
700    fn a_symbol_address_with_more_than_one_reader_is_left_where_it_is() {
701        let (mut names, mut func, block) = empty();
702        let address = func.new_vreg(GPR);
703        let lea = op(&mut names, FRAME.lea);
704        let load = op(&mut names, "mov_rm_32");
705        let cell = names.intern("cell");
706        func.build(block, lea).def(address, GPR).mem(mir::Mem::of(cell)).finish();
707        for offset in [0, 8] {
708            let value = func.new_vreg(GPR);
709            func.build(block, load)
710                .def(value, GPR)
711                .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(offset))
712                .finish();
713        }
714
715        assert_eq!(folds(&mut func, &mut names), 0);
716        assert_eq!(shape(&func, &names, block).len(), 3);
717    }
718
719    /// Two readers and one of them is in another block, which is the same refusal as the single
720    /// reader case and is caught by a different half of the pass. The count of reads is taken over
721    /// the whole function, so a set that leaves one out never becomes complete.
722    #[test]
723    fn an_address_read_outside_the_block_as_well_is_left_where_it_is() {
724        let (mut names, mut func, block) = empty();
725        let next = func.create_block();
726        let array = func.new_vreg(GPR);
727        let address = func.new_vreg(GPR);
728        let lea = op(&mut names, FRAME.lea);
729        let load = op(&mut names, "mov_rm_32");
730        func.build(block, lea)
731            .def(address, GPR)
732            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
733            .finish();
734        for at in [block, next] {
735            let value = func.new_vreg(GPR);
736            func.build(at, load)
737                .def(value, GPR)
738                .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
739                .finish();
740        }
741        *func.succs_mut(block) = vec![mir::BlockCall::to(next)];
742
743        assert_eq!(folds(&mut func, &mut names), 0);
744        assert_eq!(shape(&func, &names, block).len(), 2);
745    }
746
747    /// A register the address reads, written between the first reader and the second. This is the
748    /// one refusal the set adds that the pair version had no way to need, since a write after the
749    /// only reader is a write nobody was ever going to fold across.
750    #[test]
751    fn a_write_between_one_reader_and_the_next_ends_the_chance_for_the_set() {
752        let (mut names, mut func, block) = empty();
753        let array = mir::Reg::physical(RDI);
754        let address = func.new_vreg(GPR);
755        let first = func.new_vreg(GPR);
756        let second = func.new_vreg(GPR);
757        let lea = op(&mut names, FRAME.lea);
758        let load = op(&mut names, "mov_rm_32");
759        let put = op(&mut names, "mov_ri_64");
760        func.build(block, lea)
761            .def(address, GPR)
762            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
763            .finish();
764        func.build(block, load)
765            .def(first, GPR)
766            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
767            .finish();
768        func.build(block, put).def(array, GPR).imm(7).finish();
769        func.build(block, load)
770            .def(second, GPR)
771            .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(4))
772            .finish();
773
774        assert_eq!(folds(&mut func, &mut names), 0);
775        assert_eq!(shape(&func, &names, block).len(), 4);
776    }
777
778    /// A reader that is not reading it as an address at all. There is nowhere in an ordinary
779    /// operand to put a base and an index and a displacement, so that read is one no fold can take
780    /// and it turns down the set the way any other refusal does.
781    #[test]
782    fn an_address_something_reads_as_a_plain_operand_is_left_where_it_is() {
783        let (mut names, mut func, block) = empty();
784        let array = func.new_vreg(GPR);
785        let address = func.new_vreg(GPR);
786        let value = func.new_vreg(GPR);
787        let sum = func.new_vreg(GPR);
788        let lea = op(&mut names, FRAME.lea);
789        let load = op(&mut names, "mov_rm_32");
790        let add = op(&mut names, "add_rr_64");
791        func.build(block, lea)
792            .def(address, GPR)
793            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
794            .finish();
795        func.build(block, load)
796            .def(value, GPR)
797            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
798            .finish();
799        func.build(block, add).def(sum, GPR).uses(address, GPR).finish();
800
801        assert_eq!(folds(&mut func, &mut names), 0);
802        assert_eq!(shape(&func, &names, block).len(), 3);
803    }
804
805    /// The one instruction reading the address twice, once as the value it stores and once as the
806    /// place it stores to. Only one of those two reads is the memory operand, so folding would
807    /// leave the other one naming a register nothing writes any more.
808    #[test]
809    fn an_address_the_one_instruction_reads_twice_is_left_where_it_is() {
810        let (mut names, mut func, block) = empty();
811        let array = func.new_vreg(GPR);
812        let address = func.new_vreg(GPR);
813        let lea = op(&mut names, FRAME.lea);
814        let store = op(&mut names, "mov_mr_64");
815        func.build(block, lea)
816            .def(address, GPR)
817            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
818            .finish();
819        func.build(block, store)
820            .uses(address, GPR)
821            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
822            .finish();
823
824        assert_eq!(folds(&mut func, &mut names), 0);
825        assert_eq!(shape(&func, &names, block).len(), 2);
826    }
827
828    /// A chain whose middle has a reader of its own, so the inner address is complete while the
829    /// outer one is still waiting for its second reader. Folding the inner one away takes with it
830    /// the instruction the outer one's plan was written for, and the outer one waits rather than
831    /// being written into a gap. The second run is where it lands, which is the whole of what
832    /// waiting costs.
833    #[test]
834    fn a_chain_whose_middle_goes_first_leaves_the_outer_address_for_the_next_run() {
835        let (mut names, mut func, block) = empty();
836        let array = func.new_vreg(GPR);
837        let outer = func.new_vreg(GPR);
838        let inner = func.new_vreg(GPR);
839        let first = func.new_vreg(GPR);
840        let second = func.new_vreg(GPR);
841        let lea = op(&mut names, FRAME.lea);
842        let load = op(&mut names, "mov_rm_32");
843        func.build(block, lea)
844            .def(outer, GPR)
845            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
846            .finish();
847        func.build(block, lea)
848            .def(inner, GPR)
849            .mem(mir::Mem::at(mir::Operand::read(outer, GPR)).plus(4))
850            .finish();
851        func.build(block, load)
852            .def(first, GPR)
853            .mem(mir::Mem::at(mir::Operand::read(inner, GPR)))
854            .finish();
855        func.build(block, load)
856            .def(second, GPR)
857            .mem(mir::Mem::at(mir::Operand::read(outer, GPR)).plus(8))
858            .finish();
859
860        assert_eq!(folds(&mut func, &mut names), 1);
861        assert_eq!(shape(&func, &names, block).len(), 3, "the inner address is still there");
862
863        assert_eq!(folds(&mut func, &mut names), 2);
864        let left = shape(&func, &names, block);
865        assert_eq!(left.len(), 2, "the outer address is still there: {left:?}");
866        let disps: Vec<i32> = left.iter().map(|(_, amode)| amode.disp).collect();
867        assert_eq!(disps, vec![20, 24], "the two loads are at the two composed offsets");
868    }
869
870    /// The reader having an index of its own is the one shape that does not compose, since the
871    /// answer would want two scaled registers.
872    #[test]
873    fn a_reader_that_already_has_an_index_is_left_alone() {
874        let (mut names, mut func, block) = empty();
875        let array = func.new_vreg(GPR);
876        let index = func.new_vreg(GPR);
877        let address = func.new_vreg(GPR);
878        let value = func.new_vreg(GPR);
879        let lea = op(&mut names, FRAME.lea);
880        let load = op(&mut names, "mov_rm_32");
881        func.build(block, lea)
882            .def(address, GPR)
883            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
884            .finish();
885        func.build(block, load)
886            .def(value, GPR)
887            .mem(
888                mir::Mem::at(mir::Operand::read(address, GPR))
889                    .indexed(mir::Operand::read(index, GPR), 4),
890            )
891            .finish();
892
893        assert_eq!(folds(&mut func, &mut names), 0);
894        assert_eq!(shape(&func, &names, block).len(), 2);
895    }
896
897    /// The two displacements add up to more than the field holds, so the pair stays a pair. The
898    /// program that does this is one nobody wrote, and the point of the test is that the answer is
899    /// a refusal rather than a wrap.
900    #[test]
901    fn two_displacements_that_do_not_fit_together_are_not_put_together() {
902        let (mut names, mut func, block) = empty();
903        let array = func.new_vreg(GPR);
904        let address = func.new_vreg(GPR);
905        let value = func.new_vreg(GPR);
906        let lea = op(&mut names, FRAME.lea);
907        let load = op(&mut names, "mov_rm_32");
908        func.build(block, lea)
909            .def(address, GPR)
910            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(i32::MAX))
911            .finish();
912        func.build(block, load)
913            .def(value, GPR)
914            .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(1))
915            .finish();
916
917        assert_eq!(folds(&mut func, &mut names), 0);
918        assert_eq!(shape(&func, &names, block).len(), 2);
919    }
920
921    /// A physical register the address reads, written between the two. Machine IR is in SSA form
922    /// here so a virtual register cannot be, and this is why the walk asks anyway.
923    #[test]
924    fn a_register_the_address_reads_being_written_in_between_ends_the_chance() {
925        let (mut names, mut func, block) = empty();
926        let array = mir::Reg::physical(RDI);
927        let address = func.new_vreg(GPR);
928        let value = func.new_vreg(GPR);
929        let lea = op(&mut names, FRAME.lea);
930        let load = op(&mut names, "mov_rm_32");
931        let put = op(&mut names, "mov_ri_64");
932        func.build(block, lea)
933            .def(address, GPR)
934            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
935            .finish();
936        func.build(block, put).def(array, GPR).imm(7).finish();
937        func.build(block, load)
938            .def(value, GPR)
939            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
940            .finish();
941
942        assert_eq!(folds(&mut func, &mut names), 0);
943        assert_eq!(shape(&func, &names, block).len(), 3);
944    }
945
946    /// A reader in another block. Folding would move the address to wherever that block is, and
947    /// this pass has no way to know whether that is somewhere it runs more often.
948    #[test]
949    fn a_reader_in_another_block_is_not_one_this_folds_into() {
950        let (mut names, mut func, block) = empty();
951        let next = func.create_block();
952        let array = func.new_vreg(GPR);
953        let address = func.new_vreg(GPR);
954        let value = func.new_vreg(GPR);
955        let lea = op(&mut names, FRAME.lea);
956        let load = op(&mut names, "mov_rm_32");
957        func.build(block, lea)
958            .def(address, GPR)
959            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
960            .finish();
961        *func.succs_mut(block) = vec![mir::BlockCall::to(next)];
962        func.build(next, load)
963            .def(value, GPR)
964            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
965            .finish();
966
967        assert_eq!(folds(&mut func, &mut names), 0);
968    }
969
970    /// A chain of two, which is what an address of a field of an element of an array comes out as.
971    /// The walk goes forwards, so the second `lea` is folded into the load and then the first is
972    /// folded into what is left of the second, both in the one pass.
973    #[test]
974    fn a_chain_of_two_addresses_is_folded_the_whole_way_in_one_pass() {
975        let (mut names, mut func, block) = empty();
976        let array = func.new_vreg(GPR);
977        let index = func.new_vreg(GPR);
978        let element = func.new_vreg(GPR);
979        let field = func.new_vreg(GPR);
980        let value = func.new_vreg(GPR);
981        let lea = op(&mut names, FRAME.lea);
982        let load = op(&mut names, "mov_rm_32");
983        func.build(block, lea)
984            .def(element, GPR)
985            .mem(
986                mir::Mem::at(mir::Operand::read(array, GPR))
987                    .indexed(mir::Operand::read(index, GPR), 8),
988            )
989            .finish();
990        func.build(block, lea)
991            .def(field, GPR)
992            .mem(mir::Mem::at(mir::Operand::read(element, GPR)).plus(4))
993            .finish();
994        func.build(block, load)
995            .def(value, GPR)
996            .mem(mir::Mem::at(mir::Operand::read(field, GPR)))
997            .finish();
998
999        assert_eq!(folds(&mut func, &mut names), 2);
1000
1001        let left = shape(&func, &names, block);
1002        assert_eq!(left.len(), 1, "one of the two addresses is still its own instruction");
1003        assert_eq!(left[0].1.scale, 8);
1004        assert_eq!(left[0].1.disp, 4);
1005        let inst = func.insts(block).next().expect("the load is still there");
1006        assert_eq!(address_regs(&func, inst), vec![array, index]);
1007    }
1008
1009    /// An address of a global, which the `lea` holds as a symbol rather than as a register. It
1010    /// composes the same way and the reader ends up naming the symbol itself, which is one
1011    /// instruction rather than two for every read of a global with a constant subscript.
1012    #[test]
1013    fn an_address_of_a_global_folds_into_the_reader_symbol_and_all() {
1014        let (mut names, mut func, block) = empty();
1015        let global = names.intern("counters");
1016        let address = func.new_vreg(GPR);
1017        let value = func.new_vreg(GPR);
1018        let lea = op(&mut names, FRAME.lea);
1019        let load = op(&mut names, "mov_rm_32");
1020        func.build(block, lea).def(address, GPR).mem(mir::Mem::of(global)).finish();
1021        func.build(block, load)
1022            .def(value, GPR)
1023            .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(12))
1024            .finish();
1025
1026        assert_eq!(folds(&mut func, &mut names), 1);
1027
1028        let left = shape(&func, &names, block);
1029        assert_eq!(left.len(), 1);
1030        assert_eq!(left[0].1.symbol, Some(global));
1031        assert_eq!(left[0].1.disp, 12);
1032    }
1033
1034    /// An address into the frame, which reads as an address of nothing until `finish` writes the
1035    /// distance in. It folds like any other and the entry moves to the instruction that took it, so
1036    /// the distance is still written into something that runs, and into the reader's own
1037    /// displacement rather than over it.
1038    #[test]
1039    fn an_address_whose_displacement_is_still_to_be_written_folds_and_takes_its_entry_with_it() {
1040        let (mut names, mut func, block) = empty();
1041        let sp = mir::Reg::physical(RDI);
1042        let address = func.new_vreg(GPR);
1043        let value = func.new_vreg(GPR);
1044        let lea = op(&mut names, FRAME.lea);
1045        let load = op(&mut names, "mov_rm_32");
1046        let local = func
1047            .build(block, lea)
1048            .def(address, GPR)
1049            .mem(mir::Mem::at(mir::Operand::read(sp, GPR)))
1050            .finish();
1051        func.build(block, load)
1052            .def(value, GPR)
1053            .mem(mir::Mem::at(mir::Operand::read(address, GPR)).plus(8))
1054            .finish();
1055
1056        let (mut locals, mut arguments, mut growable) = (vec![(local, 3)], Vec::new(), Vec::new());
1057        let mut pending =
1058            Pending { addresses: &mut locals, arguments: &mut arguments, dynamic: &mut growable };
1059        assert_eq!(addresses(&mut func, &FRAME, &mut names, &mut pending), 1);
1060
1061        let left = shape(&func, &names, block);
1062        assert_eq!(left.len(), 1, "the address is worked out twice: {left:?}");
1063        assert_eq!(left[0].1.disp, 8, "the field's offset is what finish adds the frame's to");
1064        let reader = func.insts(block).next().expect("the load is still there");
1065        assert_eq!(locals, vec![(reader, 3)], "the offset is owed to whoever took the address");
1066    }
1067
1068    /// One address into the frame read at that many offsets, which is a structure written field by
1069    /// field. Gives back how many folded, which instructions are in the block afterwards, and what
1070    /// the caller is still owed an offset into.
1071    fn a_frame_address(readers: u32) -> (usize, Vec<mir::Inst>, Vec<(mir::Inst, u32)>) {
1072        let (mut names, mut func, block) = empty();
1073        let sp = mir::Reg::physical(RDI);
1074        let address = func.new_vreg(GPR);
1075        let lea = op(&mut names, FRAME.lea);
1076        let load = op(&mut names, "mov_rm_32");
1077        let local = func
1078            .build(block, lea)
1079            .def(address, GPR)
1080            .mem(mir::Mem::at(mir::Operand::read(sp, GPR)))
1081            .finish();
1082        for at in 0..readers {
1083            let value = func.new_vreg(GPR);
1084            func.build(block, load)
1085                .def(value, GPR)
1086                .mem(
1087                    mir::Mem::at(mir::Operand::read(address, GPR))
1088                        .plus(i32::try_from(at).unwrap_or(0) * 4),
1089                )
1090                .finish();
1091        }
1092
1093        let (mut locals, mut arguments, mut growable) = (Vec::new(), vec![(local, 7)], Vec::new());
1094        let mut pending =
1095            Pending { addresses: &mut locals, arguments: &mut arguments, dynamic: &mut growable };
1096        let folded = addresses(&mut func, &FRAME, &mut names, &mut pending);
1097        assert!(locals.is_empty(), "an argument is owed off the other list");
1098        (folded, func.insts(block).collect(), arguments)
1099    }
1100
1101    /// One entry on the list becomes one per reader, since each of them now carries a displacement
1102    /// the frame's offset has to be added to and there is no instruction left to add it to instead.
1103    #[test]
1104    fn an_address_into_the_frame_that_three_readers_take_is_owed_to_all_of_them() {
1105        let (folded, left, owed) = a_frame_address(3);
1106        assert_eq!(folded, 3);
1107        assert_eq!(left.len(), 3, "the address is not its own instruction any more");
1108        assert_eq!(owed, vec![(left[0], 7), (left[1], 7), (left[2], 7)]);
1109    }
1110
1111    /// And the reader after that is one too many, so none of them takes it. What each of them would
1112    /// put on is more than what the whole address instruction costs, which is [`FRAME_READERS`].
1113    #[test]
1114    fn an_address_into_the_frame_a_fourth_reader_wants_is_left_where_it_is() {
1115        let (folded, left, owed) = a_frame_address(4);
1116        assert_eq!(folded, 0);
1117        assert_eq!(left.len(), 5, "the address and its four readers");
1118        assert_eq!(owed, vec![(left[0], 7)], "the offset is still owed to the address itself");
1119    }
1120
1121    /// An instruction that is not the target's address instruction, writing a register a load
1122    /// reads. A load through the result of a load is two loads and folding one into the other
1123    /// would read the wrong memory, so the opcode is checked rather than the shape.
1124    #[test]
1125    fn only_the_target_s_address_instruction_is_one_this_folds() {
1126        let (mut names, mut func, block) = empty();
1127        let array = func.new_vreg(GPR);
1128        let address = func.new_vreg(GPR);
1129        let value = func.new_vreg(GPR);
1130        let load = op(&mut names, "mov_rm_64");
1131        let read = op(&mut names, "mov_rm_32");
1132        func.build(block, load)
1133            .def(address, GPR)
1134            .mem(mir::Mem::at(mir::Operand::read(array, GPR)).plus(16))
1135            .finish();
1136        func.build(block, read)
1137            .def(value, GPR)
1138            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
1139            .finish();
1140
1141        assert_eq!(folds(&mut func, &mut names), 0);
1142        assert_eq!(shape(&func, &names, block).len(), 2);
1143    }
1144}