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