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