rucc_codegen/combine.rs
1//! Putting a run of machine instructions together into the shorter run the machine has for it.
2//!
3//! Design: `spec/10-backend.md` section 10.9, and `spec/optimizer/37-machine-level-optimization.md`
4//! sections 37.3 and 37.4.
5//!
6//! Section 37.4 names this pass first of the ten it says are genuinely machine level, and says what
7//! shape it should be: a match over machine instructions in SSA form, inside one block, over a
8//! window of a few instructions, which is `gcc/late-combine.cc` rather than `gcc/combine.cc`. The
9//! reason for the smaller of the two is in the same section. Combine is fifteen thousand lines
10//! because it was written without def-use chains and had to find them again each time, and every
11//! RTL pass GCC has written since is on the SSA form it added later for exactly that.
12//!
13//! Section 37.3 says what the pass does once it has found a run: substitute the earlier instruction
14//! into the later one, and ask the machine description whether what came out is an instruction this
15//! target has. That is [`crate::changes`] and this pass does not repeat any of it.
16//!
17//! # The run it puts together
18//!
19//! A value read out of memory and then used once, by arithmetic that this machine could have read
20//! it out of memory itself:
21//!
22//! ```text
23//! movq 16(%rax), %rcx
24//! addq %rcx, %rdx -> addq 16(%rax), %rdx
25//! ```
26//!
27//! Two instructions become one. The register the load wrote is not written at all, which is one
28//! fewer value for the allocator to find a place for, and the bytes come down because an addressing
29//! mode costs what it costs whichever instruction carries it and the load's own opcode byte goes.
30//!
31//! It is the commonest pair in the machine IR this compiler writes. Counting adjacent instructions
32//! over the corpus at `-O2`, where the first writes what the second reads, the largest family by a
33//! long way is a move into arithmetic, and an addition at eight bytes is the largest single entry
34//! in it. What the pass gets over that corpus is 865 of these at `-O2` and 845 fewer instructions
35//! once the allocator has had its say, with the difference between the two explained below.
36//!
37//! # Why no rule does it
38//!
39//! The selector matches a term, and a term is one value. A load is a term and an addition is a
40//! term, and the pattern that would cover both is an addition with a load under it, which the
41//! selector does offer: it shows a rule the operands of its operands. What it cannot offer is the
42//! rest of the condition. Whether the load may move down to where the addition is depends on what
43//! is written between the two, and whether the load's value is wanted anywhere else depends on the
44//! whole function. Neither is a fact about the term, so neither can be in a pattern.
45//!
46//! # When the load may move
47//!
48//! The load stops being where it was and starts being part of an instruction further down the
49//! block, so everything between the two has to be something the load can pass. Two things are not.
50//!
51//! Anything that touches memory, whether it reads or writes. A write is the obvious half: whether
52//! it writes the bytes this load reads is a question about two addresses, and telling two addresses
53//! apart is an analysis nothing below selection has, so the walk below stops at a store rather than
54//! guessing. [`MachineInsts::touches_mem`] is the target's answer and [`MachineInsts::calls`] is the
55//! rest of it, since what a call does to memory is not in the instruction at all.
56//!
57//! A read is the half that is easy to argue away and is the one that matters. Moving a read past a
58//! read changes the order two accesses happen in, and the machine IR does not say which accesses
59//! the program insisted on: a `volatile` read and an ordinary one are the same instruction with the
60//! same operands here, as [`crate::copies`] says at more length about the same problem. So
61//! `volatile int a, b; return b - a;` is two loads and a subtract, and folding the first of them
62//! into the subtract would read `b` before `a` when the program said otherwise. Stopping at any
63//! access at all is what rules that out, and it costs almost nothing: the load that the arithmetic
64//! reads is nearly always the last access before it, so it is still the one that folds.
65//!
66//! What follows from that is the shape of the walk. There is one load in hand rather than a list of
67//! them, and it is always the last memory access there was.
68//!
69//! Anything that writes a register the address reads. Machine IR is in SSA form until the
70//! allocator has run, so a virtual register cannot be written twice, but the stack pointer and the
71//! frame pointer are physical here and an address into the frame reads one of them.
72//!
73//! # When the load is wanted elsewhere
74//!
75//! Exactly one instruction may read what the load wrote, and it has to be the one taking the load
76//! in. [`Reads`] is that count, kept across the commits of the pass the way [`crate::fold`] keeps
77//! it, and a count of one is the whole of the test because a virtual register is written once. Two
78//! readers and the load has to stay where it is, so putting it into one of them buys nothing and
79//! costs a second read of memory.
80//!
81//! An argument an edge carries is a read like any other and is in no operand vector, which is the
82//! one place a count of this shape is easy to get wrong. [`Reads::of`] counts those, which is what
83//! keeps a load whose value leaves the block out of this.
84//!
85//! # Which arithmetic
86//!
87//! [`FOLDS`] is the list, and it is a list rather than a rule about names because the two ends of
88//! each entry are instructions the target describes separately and the widths have to agree. A
89//! sixty four bit addition takes a sixty four bit load and nothing else: reading four bytes where
90//! the program asked for eight is a different instruction, and reading eight where it asked for
91//! four is three bytes nobody said were there.
92//!
93//! The eight bit multiply is the one member of the family with no entry. This machine has no
94//! two-operand multiply narrower than sixteen bits, so an eight bit one is written as a thirty two
95//! bit `imul` and reads a register whose upper bits nothing looks at. A memory operand has no
96//! upper bits to not look at, so there is nothing to read there and the entry is left out.
97//!
98//! # Either source, when the operation does not care
99//!
100//! An addition reads two registers and it is the second of them the memory operand replaces,
101//! because the first is the one the destination is tied to. Where the load feeds the first instead,
102//! the two sources are swapped first, which is a change to the instruction and not to what it
103//! computes as long as the operation commutes. Five of the six here do and subtraction does not,
104//! which is what [`Fold::commutes`] says.
105//!
106//! # The window
107//!
108//! A load is carried forward at most [`WINDOW`] instructions and then dropped. The bound is what
109//! makes the pass cost a fixed amount per instruction rather than an amount that grows with the
110//! block, which section 37.3 records as GCC's own answer: `max-combine-insns` is four and has been
111//! for decades.
112//!
113//! It is also nearly all of it already at one. The measurement in [`WINDOW`] is that a bound of one
114//! finds 852 folds over the corpus and a bound of thirty two finds 865, which follows from the rule
115//! above about memory rather than from anything about how the selector writes code: the load that
116//! folds is the last access to memory before the arithmetic, and the last access before it is
117//! usually the instruction in front of it. The window is there to bound the walk and it earns
118//! thirteen folds along the way.
119//!
120//! # Where it costs something
121//!
122//! A fold takes out exactly one instruction, so the number of folds and the number of instructions
123//! saved should be the same number, and they are not: 865 folds against 845 instructions over the
124//! corpus at `-O2`, and 1609 against 1444 over the SQLite amalgamation. The gap is the allocator.
125//!
126//! Taking the load out changes which values are live where, so the allocator makes different
127//! choices, and a few of them are worse. Two programs in the corpus come out two instructions
128//! longer at every level above `-O0`, both for the same reason: the folded addition is given a
129//! callee saved register while a caller saved one was free, which buys a push, a pop and a copy for
130//! a value that dies before the next call. That is the allocator preferring the wrong end of its
131//! own list rather than anything this pass did, and it is worth fixing where it is rather than
132//! worth not folding over.
133//!
134//! The trade is the other thing the gap is, and it is a real one rather than an accounting error.
135//! Two instructions become one and the one that is left both reads memory and computes, so it is
136//! two operations in one slot rather than one, which a machine that issues several instructions at
137//! once may not want. The measurement that settles it is run time rather than instruction count,
138//! and section 38.6's scheduler is where that argument belongs, since a scheduler is the pass that
139//! can see whether the slot was going to be used.
140//!
141//! # What it does not do yet
142//!
143//! A comparison. This machine compares against memory as readily as it adds to it, and the reason
144//! there is no entry for one is that a comparison here is one opcode holding a compare and the byte
145//! behind it, so the memory form is a third instruction rather than a second and the target has to
146//! describe it before this can write it.
147//!
148//! A store the arithmetic feeds. `addq %rax, 16(%rcx)` is the same saving again on the other side
149//! and this machine has the instruction, as [`rucc_target::x86_64::Form::Rmw`] already. What it
150//! needs is the reverse of the walk below, a store looking back at what wrote the value it is
151//! storing, and that is a second entry in the list rather than a second pass.
152//!
153//! Anything that is not a pair. Section 37.3 says GCC goes to four instructions, and the run this
154//! finds is two. What makes three worth having is a rule set that has something to say about
155//! three, and the rule set here grows one measured entry at a time.
156
157use rucc_base::Interner;
158use rucc_mir::{Func, Inst, Opcode, Reg};
159use rucc_target::MachineInsts;
160
161use crate::changes::{Changes, Plan, Reads};
162use crate::fold::Pending;
163
164/// How far a load is carried looking for the instruction that takes it in.
165///
166/// Measured over the corpus at `-O2`, which folds this many loads at each bound:
167///
168/// ```text
169/// 1 2 4 8 16 32
170/// 852 858 863 864 865 865
171/// ```
172///
173/// Sixteen, because that is where the curve stops. Doubling it again finds nothing, and the pass
174/// still costs a fixed amount per instruction, which is what the bound is for.
175///
176/// The curve is that flat because of the rule about memory rather than because of anything the
177/// selector does. The load that folds is the last access to memory before the arithmetic, and
178/// almost always that is the instruction immediately in front of it. What the room past one buys is
179/// the thirteen where a register was written or a constant made in between.
180pub const WINDOW: usize = 16;
181
182/// One arithmetic instruction that could read its second source out of memory, and the load that
183/// would fill it.
184///
185/// A table rather than a rule about spellings, because the three names in each row are three things
186/// the target describes on their own and nothing about `add_rr_64` says that `mov_rm_64` is the
187/// load of the same width. Writing the three together is what makes a mismatched width a line
188/// somebody can see rather than a string that was built at run time.
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub struct Fold {
191 /// The arithmetic as the selector wrote it, reading both its sources from registers.
192 pub from: &'static str,
193 /// The same arithmetic reading its second source out of memory.
194 pub into: &'static str,
195 /// The load that would have filled that register, which has to be of the same width.
196 pub load: &'static str,
197 /// Whether the two sources may be swapped, which is what lets the load feed either of them.
198 pub commutes: bool,
199}
200
201/// The arithmetic a load can move into on this machine.
202///
203/// Every two-address integer operation the target has, at every width it has one, except the eight
204/// bit multiply the module documentation gives the reason for. Subtraction is the one that does not
205/// commute.
206pub static FOLDS: &[Fold] = &[
207 Fold { from: "add_rr_8", into: "add_rm_8", load: "mov_rm_8", commutes: true },
208 Fold { from: "add_rr_16", into: "add_rm_16", load: "mov_rm_16", commutes: true },
209 Fold { from: "add_rr_32", into: "add_rm_32", load: "mov_rm_32", commutes: true },
210 Fold { from: "add_rr_64", into: "add_rm_64", load: "mov_rm_64", commutes: true },
211 Fold { from: "sub_rr_8", into: "sub_rm_8", load: "mov_rm_8", commutes: false },
212 Fold { from: "sub_rr_16", into: "sub_rm_16", load: "mov_rm_16", commutes: false },
213 Fold { from: "sub_rr_32", into: "sub_rm_32", load: "mov_rm_32", commutes: false },
214 Fold { from: "sub_rr_64", into: "sub_rm_64", load: "mov_rm_64", commutes: false },
215 Fold { from: "and_rr_8", into: "and_rm_8", load: "mov_rm_8", commutes: true },
216 Fold { from: "and_rr_16", into: "and_rm_16", load: "mov_rm_16", commutes: true },
217 Fold { from: "and_rr_32", into: "and_rm_32", load: "mov_rm_32", commutes: true },
218 Fold { from: "and_rr_64", into: "and_rm_64", load: "mov_rm_64", commutes: true },
219 Fold { from: "or_rr_8", into: "or_rm_8", load: "mov_rm_8", commutes: true },
220 Fold { from: "or_rr_16", into: "or_rm_16", load: "mov_rm_16", commutes: true },
221 Fold { from: "or_rr_32", into: "or_rm_32", load: "mov_rm_32", commutes: true },
222 Fold { from: "or_rr_64", into: "or_rm_64", load: "mov_rm_64", commutes: true },
223 Fold { from: "xor_rr_8", into: "xor_rm_8", load: "mov_rm_8", commutes: true },
224 Fold { from: "xor_rr_16", into: "xor_rm_16", load: "mov_rm_16", commutes: true },
225 Fold { from: "xor_rr_32", into: "xor_rm_32", load: "mov_rm_32", commutes: true },
226 Fold { from: "xor_rr_64", into: "xor_rm_64", load: "mov_rm_64", commutes: true },
227 Fold { from: "imul_rr_16", into: "imul_rm_16", load: "mov_rm_16", commutes: true },
228 Fold { from: "imul_rr_32", into: "imul_rm_32", load: "mov_rm_32", commutes: true },
229 Fold { from: "imul_rr_64", into: "imul_rm_64", load: "mov_rm_64", commutes: true },
230];
231
232/// The load this block has passed that could still end up inside something.
233///
234/// One rather than a list of them, because anything that touches memory ends the one being carried,
235/// so the one being carried is always the last memory access there was.
236#[derive(Debug, Clone, Copy)]
237struct Waiting {
238 /// The load.
239 inst: Inst,
240 /// The register it wrote, which is what the arithmetic has to be reading.
241 reg: Reg,
242 /// Which load it is, so that the width can be held against the arithmetic's.
243 load: &'static str,
244 /// How far along the block it is, which is what [`WINDOW`] is counted in.
245 at: usize,
246}
247
248/// Puts every load that can move into the arithmetic that reads it, and gives back how many.
249///
250/// `pending` is the addresses [`crate::finish`] has still to write a displacement into, and a load
251/// that moves takes its entry with it, the same way one folded into a reader does. An address into
252/// the frame arrives here already inside the load, because [`crate::fold`] has run.
253///
254/// Run after selection and after the addresses are folded, and before allocation. Before the
255/// allocator because what makes the pair safe to put together is that a virtual register is written
256/// once, and after the addresses because a load whose address is still a `lea` in front of it has
257/// nothing in its own memory operand worth carrying.
258pub fn loads(
259 func: &mut Func,
260 machine: &MachineInsts,
261 names: &mut Interner,
262 pending: &mut Pending<'_>,
263) -> usize {
264 let mut reads = Reads::of(func);
265 let mut done = 0;
266 for block in func.blocks().collect::<Vec<_>>() {
267 let mut waiting: Option<Waiting> = None;
268 for (at, inst) in func.insts(block).collect::<Vec<_>>().into_iter().enumerate() {
269 let name = names.resolve(func[inst].opcode.name()).to_owned();
270 let bare = machine.bare(&name).to_owned();
271 // Asked before the rewrite below rather than after it, because the rewrite turns an
272 // instruction that touched no memory into one that does, and asking afterwards would
273 // throw away the load that had just gone into it over the load that had just gone into
274 // it. Nothing else about the answer moves: the other end of a row of the fold table is
275 // arithmetic this target describes and is not a call.
276 let barrier = machine.calls(&name) || !machine.has(&name) || machine.touches_mem(&name);
277 if let Some(carried) = waiting {
278 if let Some(plan) = joined(func, &reads, carried, machine, names, inst, &bare) {
279 let mut set = Changes::new();
280 set.rewrite(inst, plan);
281 set.remove(carried.inst);
282 if set.commit(func, &mut reads, names, machine).is_ok() {
283 pending.moved(carried.inst, &[inst]);
284 waiting = None;
285 done += 1;
286 }
287 }
288 }
289 if barrier {
290 waiting = None;
291 }
292 if let Some(carried) = waiting {
293 if at - carried.at >= WINDOW || writes_what_it_reads(func, inst, &carried) {
294 waiting = None;
295 }
296 }
297 if let Some(load) = FOLDS.iter().find(|fold| fold.load == bare).map(|fold| fold.load) {
298 let operands = &func[func[inst].operands];
299 if let Some(first) = operands.first().filter(|operand| operand.role.is_def()) {
300 waiting = Some(Waiting { inst, reg: first.reg, load, at });
301 }
302 }
303 }
304 }
305 done
306}
307
308/// Whether this instruction writes a register the carried load needs left alone.
309///
310/// The registers its address reads, and the register it wrote. The second is there for the same
311/// reason the first is: a virtual register cannot be written twice while the IR is in SSA form, and
312/// these are the physical ones a function has before the allocator runs.
313fn writes_what_it_reads(func: &Func, inst: Inst, carried: &Waiting) -> bool {
314 let written: Vec<Reg> = func[func[inst].operands]
315 .iter()
316 .filter(|operand| operand.role.is_def())
317 .map(|operand| operand.reg)
318 .collect();
319 func[func[carried.inst].operands].iter().any(|operand| written.contains(&operand.reg))
320}
321
322/// What this instruction becomes with the carried load inside it, or `None`.
323///
324/// Nothing here changes anything. What comes back is a proposal, and whether the target has the
325/// instruction it describes is [`Changes`]'s answer rather than this one.
326fn joined(
327 func: &Func,
328 reads: &Reads,
329 carried: Waiting,
330 machine: &MachineInsts,
331 names: &mut Interner,
332 inst: Inst,
333 bare: &str,
334) -> Option<Plan> {
335 let fold = FOLDS.iter().find(|fold| fold.from == bare)?;
336 if carried.load != fold.load || reads.count(carried.reg) != 1 {
337 return None;
338 }
339 let operands = func[func[inst].operands].to_vec();
340 let [answer, first, second] = operands[..] else { return None };
341 // The second source is the one the memory operand replaces, because the answer is tied to the
342 // first. Where the load feeds the first source instead and the operation commutes, the two are
343 // swapped, which leaves the instruction computing what it computed.
344 let kept = if second.reg == carried.reg {
345 first
346 } else if fold.commutes && first.reg == carried.reg {
347 second
348 } else {
349 return None;
350 };
351 let load = carried.inst;
352 let address = func[func[load].operands][1..].to_vec();
353 let mut amode = func[func[load].mem?];
354 // The registers an address names are operands behind the ones the instruction writes down, and
355 // there is one of those in front of them here where there was none in front of them in the
356 // load, so every position the mode holds moves along by one.
357 amode.base = amode.base.map(|at| at + 1);
358 amode.index = amode.index.map(|at| at + 1);
359 let into = names.intern(&format!("{}{}", machine.prefix, fold.into));
360 Some(Plan {
361 opcode: Opcode::new(into),
362 operands: [answer, kept].into_iter().chain(address).collect(),
363 imm: None,
364 amode: Some(amode),
365 symbol: func[load].symbol,
366 })
367}
368
369#[cfg(test)]
370mod tests {
371 use rucc_mir::{self as mir, Constraint, Mem, Operand};
372 use rucc_target::x86_64::{GPR, MACHINE};
373
374 use super::*;
375
376 /// A function with one block, and the names it was built with.
377 fn empty() -> (Interner, Func, mir::Block) {
378 let mut names = Interner::new();
379 let mut func = Func::new(names.intern("f"));
380 let block = func.create_block();
381 (names, func, block)
382 }
383
384 /// The opcode of that name on this target.
385 fn op(names: &mut Interner, name: &str) -> Opcode {
386 Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
387 }
388
389 /// A load of eight bytes off that register.
390 fn load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
391 let into = func.new_vreg(GPR);
392 let mov = op(names, "mov_rm_64");
393 func.build(block, mov)
394 .def(into, GPR)
395 .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
396 .finish();
397 into
398 }
399
400 /// Two-address arithmetic of that name on those two registers, in that order.
401 fn alu(
402 func: &mut Func,
403 names: &mut Interner,
404 block: mir::Block,
405 name: &str,
406 first: Reg,
407 second: Reg,
408 ) -> Reg {
409 let answer = func.new_vreg(GPR);
410 let opcode = op(names, name);
411 func.build(block, opcode)
412 .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
413 .uses(first, GPR)
414 .uses(second, GPR)
415 .finish();
416 answer
417 }
418
419 /// What every instruction in a block came to, as opcodes.
420 fn shape(func: &Func, names: &Interner, block: mir::Block) -> Vec<String> {
421 func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
422 }
423
424 /// The pass, with lists nothing is on.
425 fn combine(func: &mut Func, names: &mut Interner) -> usize {
426 let mut addresses = Vec::new();
427 let mut arguments = Vec::new();
428 let mut dynamic = Vec::new();
429 let mut pending =
430 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
431 loads(func, &MACHINE, names, &mut pending)
432 }
433
434 /// The shape the whole pass is for.
435 #[test]
436 fn a_load_read_once_by_an_addition_becomes_its_memory_operand() {
437 let (mut names, mut func, block) = empty();
438 let base = func.new_vreg(GPR);
439 let other = func.new_vreg(GPR);
440 let word = load(&mut func, &mut names, block, base);
441 alu(&mut func, &mut names, block, "add_rr_64", other, word);
442
443 assert_eq!(combine(&mut func, &mut names), 1);
444 assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
445 let inst = func.insts(block).next().expect("the addition");
446 let mem = func[inst].mem.expect("the addition reads memory now");
447 assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
448 assert_eq!(func[mem].base, Some(2), "and names the operand behind the source it kept");
449 assert_eq!(func[func[inst].operands][1].reg, other, "the source it kept");
450 assert_eq!(func[func[inst].operands][2].reg, base, "the address it took on");
451 }
452
453 /// The same load feeding the source the answer is tied to. The two sources are swapped, which
454 /// an addition does not mind and is what lets this fold at all.
455 #[test]
456 fn a_load_feeding_the_first_source_of_an_addition_is_swapped_and_folded() {
457 let (mut names, mut func, block) = empty();
458 let base = func.new_vreg(GPR);
459 let other = func.new_vreg(GPR);
460 let word = load(&mut func, &mut names, block, base);
461 alu(&mut func, &mut names, block, "add_rr_64", word, other);
462
463 assert_eq!(combine(&mut func, &mut names), 1);
464 assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
465 let inst = func.insts(block).next().expect("the addition");
466 assert_eq!(func[func[inst].operands][1].reg, other);
467 }
468
469 /// A subtraction with the load on the left, which is the one place the swap above would change
470 /// the answer.
471 #[test]
472 fn a_load_feeding_the_left_of_a_subtraction_stays_a_load() {
473 let (mut names, mut func, block) = empty();
474 let base = func.new_vreg(GPR);
475 let other = func.new_vreg(GPR);
476 let word = load(&mut func, &mut names, block, base);
477 alu(&mut func, &mut names, block, "sub_rr_64", word, other);
478
479 assert_eq!(combine(&mut func, &mut names), 0);
480 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.sub_rr_64"]);
481 }
482
483 /// And the same subtraction the other way round, which is the one that folds.
484 #[test]
485 fn a_load_feeding_the_right_of_a_subtraction_folds() {
486 let (mut names, mut func, block) = empty();
487 let base = func.new_vreg(GPR);
488 let other = func.new_vreg(GPR);
489 let word = load(&mut func, &mut names, block, base);
490 alu(&mut func, &mut names, block, "sub_rr_64", other, word);
491
492 assert_eq!(combine(&mut func, &mut names), 1);
493 assert_eq!(shape(&func, &names, block), ["x64.sub_rm_64"]);
494 }
495
496 /// Two readers. The load has to stay where it is for the second of them, so putting it into the
497 /// first buys nothing and reads the memory twice.
498 #[test]
499 fn a_load_two_instructions_read_stays_a_load() {
500 let (mut names, mut func, block) = empty();
501 let base = func.new_vreg(GPR);
502 let other = func.new_vreg(GPR);
503 let word = load(&mut func, &mut names, block, base);
504 alu(&mut func, &mut names, block, "add_rr_64", other, word);
505 alu(&mut func, &mut names, block, "xor_rr_64", other, word);
506
507 assert_eq!(combine(&mut func, &mut names), 0);
508 assert_eq!(
509 shape(&func, &names, block),
510 ["x64.mov_rm_64", "x64.add_rr_64", "x64.xor_rr_64"]
511 );
512 }
513
514 /// A store between the two. Whether it writes what the load reads is a question about two
515 /// addresses, and the answer to not being able to tell is to leave the load where it is.
516 #[test]
517 fn a_load_with_a_store_between_it_and_its_reader_stays_a_load() {
518 let (mut names, mut func, block) = empty();
519 let base = func.new_vreg(GPR);
520 let other = func.new_vreg(GPR);
521 let word = load(&mut func, &mut names, block, base);
522 let store = op(&mut names, "mov_mr_64");
523 func.build(block, store).uses(other, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
524 alu(&mut func, &mut names, block, "add_rr_64", other, word);
525
526 assert_eq!(combine(&mut func, &mut names), 0);
527 assert_eq!(
528 shape(&func, &names, block),
529 ["x64.mov_rm_64", "x64.mov_mr_64", "x64.add_rr_64"]
530 );
531 }
532
533 /// Another load between the two, which writes nothing and is still not passed.
534 ///
535 /// This is the one that would be wrong if the walk asked only about writes. Where both reads
536 /// are `volatile` the program said which of them happens first, and nothing here can tell that
537 /// program from the one that did not say it, so neither may be reordered.
538 #[test]
539 fn a_load_with_another_load_between_it_and_its_reader_stays_a_load() {
540 let (mut names, mut func, block) = empty();
541 let base = func.new_vreg(GPR);
542 let other = func.new_vreg(GPR);
543 let word = load(&mut func, &mut names, block, base);
544 load(&mut func, &mut names, block, other);
545 alu(&mut func, &mut names, block, "add_rr_64", other, word);
546
547 assert_eq!(combine(&mut func, &mut names), 0);
548 assert_eq!(
549 shape(&func, &names, block),
550 ["x64.mov_rm_64", "x64.mov_rm_64", "x64.add_rr_64"]
551 );
552 }
553
554 /// The second of two loads, read by arithmetic that reads the first as well. Nothing moves past
555 /// anything, which is what makes this one the shape the pass is allowed to take.
556 #[test]
557 fn the_later_of_two_loads_is_the_one_that_folds() {
558 let (mut names, mut func, block) = empty();
559 let base = func.new_vreg(GPR);
560 let other = func.new_vreg(GPR);
561 let first = load(&mut func, &mut names, block, base);
562 let second = load(&mut func, &mut names, block, other);
563 alu(&mut func, &mut names, block, "add_rr_64", first, second);
564
565 assert_eq!(combine(&mut func, &mut names), 1);
566 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rm_64"]);
567 let addition = func.insts(block).nth(1).expect("the addition");
568 assert_eq!(func[func[addition].operands][1].reg, first, "the earlier load is still read");
569 assert_eq!(func[func[addition].operands][2].reg, other, "and the later one is the address");
570 }
571
572 /// A call between the two. What a call does to memory is not in the instruction, so it is the
573 /// same answer as the store and reached without asking about the address.
574 #[test]
575 fn a_load_with_a_call_between_it_and_its_reader_stays_a_load() {
576 let (mut names, mut func, block) = empty();
577 let base = func.new_vreg(GPR);
578 let other = func.new_vreg(GPR);
579 let word = load(&mut func, &mut names, block, base);
580 let call = op(&mut names, "call");
581 func.build(block, call).finish();
582 alu(&mut func, &mut names, block, "add_rr_64", other, word);
583
584 assert_eq!(combine(&mut func, &mut names), 0);
585 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.call", "x64.add_rr_64"]);
586 }
587
588 /// Something writing the register the address reads. A physical register is the only one this
589 /// can happen to while the IR is in SSA form, and the frame is addressed through two of them.
590 #[test]
591 fn a_load_whose_address_register_is_written_between_the_two_stays_a_load() {
592 let (mut names, mut func, block) = empty();
593 let base = Reg::physical(rucc_target::x86_64::RSP);
594 let other = func.new_vreg(GPR);
595 let word = load(&mut func, &mut names, block, base);
596 let sub = op(&mut names, "sub_ri_64");
597 func.build(block, sub)
598 .operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
599 .uses(base, GPR)
600 .imm(32)
601 .finish();
602 alu(&mut func, &mut names, block, "add_rr_64", other, word);
603
604 assert_eq!(combine(&mut func, &mut names), 0);
605 }
606
607 /// A load of four bytes under an addition of eight. The register held what the load put in it
608 /// and a memory operand holds what is at the address, which is a different number of bytes.
609 #[test]
610 fn a_load_of_the_wrong_width_stays_a_load() {
611 let (mut names, mut func, block) = empty();
612 let base = func.new_vreg(GPR);
613 let other = func.new_vreg(GPR);
614 let into = func.new_vreg(GPR);
615 let narrow = op(&mut names, "mov_rm_32");
616 func.build(block, narrow).def(into, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
617 alu(&mut func, &mut names, block, "add_rr_64", other, into);
618
619 assert_eq!(combine(&mut func, &mut names), 0);
620 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_32", "x64.add_rr_64"]);
621 }
622
623 /// A load whose value leaves the block on an edge. It is read by nothing in any operand vector
624 /// and is read all the same, which is the count that is easy to get wrong.
625 #[test]
626 fn a_load_whose_value_an_edge_carries_stays_a_load() {
627 let (mut names, mut func, block) = empty();
628 let next = func.create_block();
629 let base = func.new_vreg(GPR);
630 let other = func.new_vreg(GPR);
631 let word = load(&mut func, &mut names, block, base);
632 alu(&mut func, &mut names, block, "add_rr_64", other, word);
633 let arrived = func.new_vreg(GPR);
634 func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
635 *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![word])];
636
637 assert_eq!(combine(&mut func, &mut names), 0);
638 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
639 }
640
641 /// A reader in another block, which is the whole of what block local means here.
642 #[test]
643 fn a_reader_in_another_block_stays_where_it_is() {
644 let (mut names, mut func, block) = empty();
645 let next = func.create_block();
646 let base = func.new_vreg(GPR);
647 let other = func.new_vreg(GPR);
648 let word = load(&mut func, &mut names, block, base);
649 alu(&mut func, &mut names, next, "add_rr_64", other, word);
650
651 assert_eq!(combine(&mut func, &mut names), 0);
652 assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
653 assert_eq!(shape(&func, &names, next), ["x64.add_rr_64"]);
654 }
655
656 /// A reader further down the block than the window reaches.
657 #[test]
658 fn a_reader_past_the_window_stays_where_it_is() {
659 let (mut names, mut func, block) = empty();
660 let base = func.new_vreg(GPR);
661 let other = func.new_vreg(GPR);
662 let word = load(&mut func, &mut names, block, base);
663 let nop = op(&mut names, "nop");
664 for _ in 0..WINDOW {
665 func.build(block, nop).finish();
666 }
667 alu(&mut func, &mut names, block, "add_rr_64", other, word);
668
669 assert_eq!(combine(&mut func, &mut names), 0);
670 }
671
672 /// And one instruction closer, which is the last place it still folds.
673 #[test]
674 fn a_reader_at_the_edge_of_the_window_folds() {
675 let (mut names, mut func, block) = empty();
676 let base = func.new_vreg(GPR);
677 let other = func.new_vreg(GPR);
678 let word = load(&mut func, &mut names, block, base);
679 let nop = op(&mut names, "nop");
680 for _ in 0..WINDOW - 1 {
681 func.build(block, nop).finish();
682 }
683 alu(&mut func, &mut names, block, "add_rr_64", other, word);
684
685 assert_eq!(combine(&mut func, &mut names), 1);
686 }
687
688 /// The entry a frame layout is waiting on moves with the load. Without this the displacement
689 /// of a local would be written into an instruction that has gone.
690 #[test]
691 fn the_frame_entry_of_a_load_that_moves_goes_with_it() {
692 let (mut names, mut func, block) = empty();
693 let base = Reg::physical(rucc_target::x86_64::RSP);
694 let other = func.new_vreg(GPR);
695 let word = load(&mut func, &mut names, block, base);
696 let reader = func.insts(block).nth(1);
697 assert!(reader.is_none(), "the block holds the load alone so far");
698 alu(&mut func, &mut names, block, "add_rr_64", other, word);
699 let held = func.insts(block).next().expect("the load");
700
701 let mut addresses = vec![(held, 3usize)];
702 let mut arguments = Vec::new();
703 let mut dynamic = Vec::new();
704 let mut pending =
705 Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
706 assert_eq!(loads(&mut func, &MACHINE, &mut names, &mut pending), 1);
707
708 let inst = func.insts(block).next().expect("the addition");
709 assert_eq!(addresses, [(inst, 3usize)], "the entry names the instruction that took it");
710 }
711
712 /// Every row of the table names instructions this target has, and names a load and an
713 /// arithmetic whose widths agree. A row that got one of the three wrong would propose an
714 /// instruction the change framework turns down, which is a fold that silently never happens.
715 #[test]
716 fn every_row_of_the_table_is_three_instructions_this_target_has() {
717 for fold in FOLDS {
718 assert!(MACHINE.has(fold.from), "{} is not an instruction", fold.from);
719 assert!(MACHINE.has(fold.into), "{} is not an instruction", fold.into);
720 assert!(MACHINE.has(fold.load), "{} is not an instruction", fold.load);
721 let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
722 assert_eq!(width(fold.from), width(fold.into), "{} changes width", fold.from);
723 assert_eq!(width(fold.from), width(fold.load), "{} loads another width", fold.from);
724 assert!((MACHINE.takes_mem)(fold.into), "{} reads no memory", fold.into);
725 assert!(!(MACHINE.takes_mem)(fold.from), "{} already reads memory", fold.from);
726 }
727 }
728
729 /// One row per arithmetic instruction the target has that could take one. The count is here so
730 /// that an instruction added to the target without a row shows up as a number rather than as a
731 /// fold nobody noticed was missing.
732 #[test]
733 fn the_table_covers_the_arithmetic_this_target_has() {
734 assert_eq!(FOLDS.len(), 23, "six operations at four widths, less the eight bit multiply");
735 let commuting = FOLDS.iter().filter(|fold| fold.commutes).count();
736 assert_eq!(commuting, 19, "everything but the four subtractions");
737 }
738}