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