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