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