Skip to main content

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 runs it puts together
18//!
19//! Two of them. A value read out of memory and then used once, by arithmetic that this machine
20//! could have read it out of memory itself, which is [`loads`]:
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//! And the same value written back where it came from, which is [`stores`]:
38//!
39//! ```text
40//!   movq 16(%rax), %rcx
41//!   addq %rdx, %rcx        ->    addq %rdx, 16(%rax)
42//!   movq %rcx, 16(%rax)
43//! ```
44//!
45//! Three instructions become one, and this is what a C program writes as `*p += x`. The register in
46//! the middle goes the way the load's register goes above, and so does the second addressing mode,
47//! which was the same address written down twice.
48//!
49//! [`stores`] takes the same run with a constant in it, which is what a C program writes as
50//! `*p += 1` and is the commoner of the two:
51//!
52//! ```text
53//!   movq 16(%rax), %rcx
54//!   addq $1, %rcx          ->    addq $1, 16(%rax)
55//!   movq %rcx, 16(%rax)
56//! ```
57//!
58//! Nothing is left holding a register here at all. The instruction that comes out reads the place,
59//! adds the constant the instruction carries and writes the place, so the whole run costs the
60//! addressing mode and the constant and no operand the allocator has to answer for.
61//!
62//! [`stores`] runs first. Its run is three instructions as the selector wrote them, and folding the
63//! load into the middle one first would leave the same run written a second way that the walk would
64//! then have to know about. Whatever it does not take is still a pair for [`loads`].
65//!
66//! # Why no rule does it
67//!
68//! The selector matches a term, and a term is one value. A load is a term and an addition is a
69//! term, and the pattern that would cover both is an addition with a load under it, which the
70//! selector does offer: it shows a rule the operands of its operands. What it cannot offer is the
71//! rest of the condition. Whether the load may move down to where the addition is depends on what
72//! is written between the two, and whether the load's value is wanted anywhere else depends on the
73//! whole function. Neither is a fact about the term, so neither can be in a pattern.
74//!
75//! # When the load may move
76//!
77//! The load stops being where it was and starts being part of an instruction further down the
78//! block, so everything between the two has to be something the load can pass. Two things are not.
79//!
80//! Anything that touches memory, whether it reads or writes. A write is the obvious half: whether
81//! it writes the bytes this load reads is a question about two addresses, and telling two addresses
82//! apart is an analysis nothing below selection has, so the walk below stops at a store rather than
83//! guessing. [`MachineInsts::touches_mem`] is the target's answer and [`MachineInsts::calls`] is the
84//! rest of it, since what a call does to memory is not in the instruction at all.
85//!
86//! A read is the half that is easy to argue away and is the one that matters. Moving a read past a
87//! read changes the order two accesses happen in, and the machine IR does not say which accesses
88//! the program insisted on: a `volatile` read and an ordinary one are the same instruction with the
89//! same operands here, as [`crate::copies`] says at more length about the same problem. So
90//! `volatile int a, b; return b - a;` is two loads and a subtract, and folding the first of them
91//! into the subtract would read `b` before `a` when the program said otherwise. Stopping at any
92//! access at all is what rules that out, and it costs almost nothing: the load that the arithmetic
93//! reads is nearly always the last access before it, so it is still the one that folds.
94//!
95//! What follows from that is the shape of the walk. There is one load in hand rather than a list of
96//! them, and it is always the last memory access there was.
97//!
98//! Anything that writes a register the address reads. Machine IR is in SSA form until the
99//! allocator has run, so a virtual register cannot be written twice, but the stack pointer and the
100//! frame pointer are physical here and an address into the frame reads one of them.
101//!
102//! # When the load is wanted elsewhere
103//!
104//! Exactly one instruction may read what the load wrote, and it has to be the one taking the load
105//! in. [`Reads`] is that count, kept across the commits of the pass the way [`crate::fold`] keeps
106//! it, and a count of one is the whole of the test because a virtual register is written once. Two
107//! readers and the load has to stay where it is, so putting it into one of them buys nothing and
108//! costs a second read of memory.
109//!
110//! An argument an edge carries is a read like any other and is in no operand vector, which is the
111//! one place a count of this shape is easy to get wrong. [`Reads::of`] counts those, which is what
112//! keeps a load whose value leaves the block out of this.
113//!
114//! # Which arithmetic
115//!
116//! [`FOLDS`] is the list, and it is a list rather than a rule about names because the two ends of
117//! each entry are instructions the target describes separately and the widths have to agree. A
118//! sixty four bit addition takes a sixty four bit load and nothing else: reading four bytes where
119//! the program asked for eight is a different instruction, and reading eight where it asked for
120//! four is three bytes nobody said were there.
121//!
122//! The eight bit multiply is the one member of the family with no entry. This machine has no
123//! two-operand multiply narrower than sixteen bits, so an eight bit one is written as a thirty two
124//! bit `imul` and reads a register whose upper bits nothing looks at. A memory operand has no
125//! upper bits to not look at, so there is nothing to read there and the entry is left out.
126//!
127//! # Either source, when the operation does not care
128//!
129//! An addition reads two registers and it is the second of them the memory operand replaces,
130//! because the first is the one the destination is tied to. Where the load feeds the first instead,
131//! the two sources are swapped first, which is a change to the instruction and not to what it
132//! computes as long as the operation commutes. Five of the six here do and subtraction does not,
133//! which is what [`Fold::commutes`] says.
134//!
135//! # What a `volatile` access gets
136//!
137//! The same thing an ordinary one does, and that is worth writing down rather than leaving to be
138//! noticed. `volatile int *p; *p += x;` comes out of here as one instruction that reads the place
139//! and writes it back, where GCC writes the load, the arithmetic and the store. Both do one read
140//! and one write of that address, which is what the abstract machine says has to happen, so the
141//! program is the program either way. What they differ on is whether the two are one instruction,
142//! and a machine whose memory does something when it is read cares about that.
143//!
144//! The reason it happens is the one this module already gives twice: the machine IR does not carry
145//! the flag. `rucc_ir::Flags::VOLATILE` says an access happens exactly once and is never moved or
146//! merged, every pass above selection reads it, and the instruction that reaches this pass is the
147//! same instruction whether it was set or not. [`loads`] has the same hole and has had it since it
148//! landed: a `volatile` load folds into the arithmetic that reads it, which is still one read and
149//! is still not what GCC writes. Carrying the flag down is what closes both, and it is
150//! tamnd/rucc#1302 rather than something this pass can decide on its own.
151//!
152//! # What makes the three one
153//!
154//! The same three questions as the pair, and one more. The word the load read is read by the
155//! arithmetic and by nothing else, the answer the arithmetic wrote is read by the store and by
156//! nothing else, and nothing between the load and the store touches memory or writes a register the
157//! instruction that is left still reads. The run collapses onto the store, so the read of memory
158//! moves down the block to where the write already was, which is the move the memory rule is about.
159//!
160//! The one more is that the two addressing modes have to name the same place. The same registers,
161//! the same scale, the same displacement and the same symbol is most of it, and the frame is the
162//! rest: the displacement of a local is a number [`crate::finish`] has still to add the frame's own
163//! offset to, so two locals can be the same three registers and the same zero here and be two
164//! different places. The list itself is what tells those apart, and the entry the load was
165//! waiting on comes off the list when the run is joined, since the store is already waiting on the
166//! same one.
167//!
168//! # The window
169//!
170//! A load is carried forward at most [`WINDOW`] instructions and then dropped. The bound is what
171//! makes the pass cost a fixed amount per instruction rather than an amount that grows with the
172//! block, which section 37.3 records as GCC's own answer: `max-combine-insns` is four and has been
173//! for decades.
174//!
175//! It is also nearly all of it already at one. The measurement in [`WINDOW`] is that a bound of one
176//! finds 852 folds over the corpus and a bound of thirty two finds 865, which follows from the rule
177//! above about memory rather than from anything about how the selector writes code: the load that
178//! folds is the last access to memory before the arithmetic, and the last access before it is
179//! usually the instruction in front of it. The window is there to bound the walk and it earns
180//! thirteen folds along the way.
181//!
182//! # Where it costs something
183//!
184//! A fold takes out exactly one instruction, so the number of folds and the number of instructions
185//! saved should be the same number, and they are not: 865 folds against 845 instructions over the
186//! corpus at `-O2`, and 1609 against 1444 over the SQLite amalgamation. The gap is the allocator.
187//!
188//! Taking the load out changes which values are live where, so the allocator makes different
189//! choices, and a few of them are worse. Two programs in the corpus come out two instructions
190//! longer at every level above `-O0`, both for the same reason: the folded addition is given a
191//! callee saved register while a caller saved one was free, which buys a push, a pop and a copy for
192//! a value that dies before the next call. That is the allocator preferring the wrong end of its
193//! own list rather than anything this pass did, and it is worth fixing where it is rather than
194//! worth not folding over.
195//!
196//! The trade is the other thing the gap is, and it is a real one rather than an accounting error.
197//! Two instructions become one and the one that is left both reads memory and computes, so it is
198//! two operations in one slot rather than one, which a machine that issues several instructions at
199//! once may not want. The measurement that settles it is run time rather than instruction count,
200//! and section 38.6's scheduler is where that argument belongs, since a scheduler is the pass that
201//! can see whether the slot was going to be used.
202//!
203//! # What it does not do yet
204//!
205//! A comparison. This machine compares against memory as readily as it adds to it, and the reason
206//! there is no entry for one is that a comparison here is one opcode holding a compare and the byte
207//! behind it, so the memory form is a third instruction rather than a second and the target has to
208//! describe it before this can write it.
209//!
210//! Arithmetic against a constant, in either run. `addq $1, 16(%rcx)` is `*p += 1`, which is at
211//! least as common as `*p += x`, and the target has no form that carries an addressing mode and an
212//! immediate together. That is a third instruction description rather than a rule, the way the
213//! comparison above is.
214//!
215//! Anything longer than the two runs above. Section 37.3 says GCC goes to four instructions, and
216//! the longer of the two here is three. What makes a fourth worth having is a rule set that has
217//! something to say about four, and the rule set here grows one measured entry at a time.
218
219use rucc_base::Interner;
220use rucc_mir::{Amode, Func, Inst, Opcode, Operand, Reg};
221use rucc_target::MachineInsts;
222
223use crate::changes::{Changes, Plan, Reads};
224use crate::fold::Pending;
225
226/// How far a load is carried looking for the instruction that takes it in.
227///
228/// Measured over the corpus at `-O2`, which folds this many loads at each bound:
229///
230/// ```text
231///   1     2     4     8    16    32
232/// 852   858   863   864   865   865
233/// ```
234///
235/// Sixteen, because that is where the curve stops. Doubling it again finds nothing, and the pass
236/// still costs a fixed amount per instruction, which is what the bound is for.
237///
238/// The curve is that flat because of the rule about memory rather than because of anything the
239/// selector does. The load that folds is the last access to memory before the arithmetic, and
240/// almost always that is the instruction immediately in front of it. What the room past one buys is
241/// the thirteen where a register was written or a constant made in between.
242pub const WINDOW: usize = 16;
243
244/// One arithmetic instruction that could read its second source out of memory, and the load that
245/// would fill it.
246///
247/// A table rather than a rule about spellings, because the three names in each row are three things
248/// the target describes on their own and nothing about `add_rr_64` says that `mov_rm_64` is the
249/// load of the same width. Writing the three together is what makes a mismatched width a line
250/// somebody can see rather than a string that was built at run time.
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub struct Fold {
253    /// The arithmetic as the selector wrote it, reading both its sources from registers.
254    pub from: &'static str,
255    /// The same arithmetic reading its second source out of memory.
256    pub into: &'static str,
257    /// The load that would have filled that register, which has to be of the same width.
258    pub load: &'static str,
259    /// Whether the two sources may be swapped, which is what lets the load feed either of them.
260    pub commutes: bool,
261}
262
263/// The arithmetic a load can move into on this machine.
264///
265/// Every two-address integer operation the target has, at every width it has one, except the eight
266/// bit multiply the module documentation gives the reason for. Subtraction is the one that does not
267/// commute.
268pub static FOLDS: &[Fold] = &[
269    Fold { from: "add_rr_8", into: "add_rm_8", load: "mov_rm_8", commutes: true },
270    Fold { from: "add_rr_16", into: "add_rm_16", load: "mov_rm_16", commutes: true },
271    Fold { from: "add_rr_32", into: "add_rm_32", load: "mov_rm_32", commutes: true },
272    Fold { from: "add_rr_64", into: "add_rm_64", load: "mov_rm_64", commutes: true },
273    Fold { from: "sub_rr_8", into: "sub_rm_8", load: "mov_rm_8", commutes: false },
274    Fold { from: "sub_rr_16", into: "sub_rm_16", load: "mov_rm_16", commutes: false },
275    Fold { from: "sub_rr_32", into: "sub_rm_32", load: "mov_rm_32", commutes: false },
276    Fold { from: "sub_rr_64", into: "sub_rm_64", load: "mov_rm_64", commutes: false },
277    Fold { from: "and_rr_8", into: "and_rm_8", load: "mov_rm_8", commutes: true },
278    Fold { from: "and_rr_16", into: "and_rm_16", load: "mov_rm_16", commutes: true },
279    Fold { from: "and_rr_32", into: "and_rm_32", load: "mov_rm_32", commutes: true },
280    Fold { from: "and_rr_64", into: "and_rm_64", load: "mov_rm_64", commutes: true },
281    Fold { from: "or_rr_8", into: "or_rm_8", load: "mov_rm_8", commutes: true },
282    Fold { from: "or_rr_16", into: "or_rm_16", load: "mov_rm_16", commutes: true },
283    Fold { from: "or_rr_32", into: "or_rm_32", load: "mov_rm_32", commutes: true },
284    Fold { from: "or_rr_64", into: "or_rm_64", load: "mov_rm_64", commutes: true },
285    Fold { from: "xor_rr_8", into: "xor_rm_8", load: "mov_rm_8", commutes: true },
286    Fold { from: "xor_rr_16", into: "xor_rm_16", load: "mov_rm_16", commutes: true },
287    Fold { from: "xor_rr_32", into: "xor_rm_32", load: "mov_rm_32", commutes: true },
288    Fold { from: "xor_rr_64", into: "xor_rm_64", load: "mov_rm_64", commutes: true },
289    Fold { from: "imul_rr_16", into: "imul_rm_16", load: "mov_rm_16", commutes: true },
290    Fold { from: "imul_rr_32", into: "imul_rm_32", load: "mov_rm_32", commutes: true },
291    Fold { from: "imul_rr_64", into: "imul_rm_64", load: "mov_rm_64", commutes: true },
292];
293
294/// One arithmetic instruction that could work on memory rather than on a register, and the load
295/// and the store that would be the rest of the run.
296///
297/// A table for the reason [`Fold`] is one, and four names in a row rather than three because the
298/// run is three instructions rather than two. The widths of all four have to agree, and writing
299/// them out is what makes a row that got one wrong something a reader can see.
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub struct Update {
302    /// The arithmetic as the selector wrote it, on two registers.
303    pub from: &'static str,
304    /// The same arithmetic reading one source out of memory and leaving its answer there.
305    pub into: &'static str,
306    /// The load that put the memory's word in a register.
307    pub load: &'static str,
308    /// The store that put the answer back.
309    pub store: &'static str,
310    /// Whether the two sources may be swapped, which is what lets the load feed either of them.
311    pub commutes: bool,
312}
313
314/// The arithmetic that can work on memory in place on this machine.
315///
316/// The five operations that share an opcode column, at every width. The multiply is not one of
317/// them: `imul` writes a register and there is no encoding of it that leaves the product where it
318/// read one of its sources, so there is no instruction for a row to name.
319///
320/// Subtraction is here and does not commute, and the two facts are related. `subq %rax, (%rcx)`
321/// takes the register away from the memory, so the run it matches is the one where the load feeds
322/// the left source, which is the one arrangement [`FOLDS`] cannot use. The other four take either
323/// source, because the answer does not depend on which of the two came out of memory.
324pub static UPDATES: &[Update] = &[
325    Update {
326        from: "add_rr_8",
327        into: "add_mr_8",
328        load: "mov_rm_8",
329        store: "mov_mr_8",
330        commutes: true,
331    },
332    Update {
333        from: "add_rr_16",
334        into: "add_mr_16",
335        load: "mov_rm_16",
336        store: "mov_mr_16",
337        commutes: true,
338    },
339    Update {
340        from: "add_rr_32",
341        into: "add_mr_32",
342        load: "mov_rm_32",
343        store: "mov_mr_32",
344        commutes: true,
345    },
346    Update {
347        from: "add_rr_64",
348        into: "add_mr_64",
349        load: "mov_rm_64",
350        store: "mov_mr_64",
351        commutes: true,
352    },
353    Update {
354        from: "sub_rr_8",
355        into: "sub_mr_8",
356        load: "mov_rm_8",
357        store: "mov_mr_8",
358        commutes: false,
359    },
360    Update {
361        from: "sub_rr_16",
362        into: "sub_mr_16",
363        load: "mov_rm_16",
364        store: "mov_mr_16",
365        commutes: false,
366    },
367    Update {
368        from: "sub_rr_32",
369        into: "sub_mr_32",
370        load: "mov_rm_32",
371        store: "mov_mr_32",
372        commutes: false,
373    },
374    Update {
375        from: "sub_rr_64",
376        into: "sub_mr_64",
377        load: "mov_rm_64",
378        store: "mov_mr_64",
379        commutes: false,
380    },
381    Update {
382        from: "and_rr_8",
383        into: "and_mr_8",
384        load: "mov_rm_8",
385        store: "mov_mr_8",
386        commutes: true,
387    },
388    Update {
389        from: "and_rr_16",
390        into: "and_mr_16",
391        load: "mov_rm_16",
392        store: "mov_mr_16",
393        commutes: true,
394    },
395    Update {
396        from: "and_rr_32",
397        into: "and_mr_32",
398        load: "mov_rm_32",
399        store: "mov_mr_32",
400        commutes: true,
401    },
402    Update {
403        from: "and_rr_64",
404        into: "and_mr_64",
405        load: "mov_rm_64",
406        store: "mov_mr_64",
407        commutes: true,
408    },
409    Update {
410        from: "or_rr_8",
411        into: "or_mr_8",
412        load: "mov_rm_8",
413        store: "mov_mr_8",
414        commutes: true,
415    },
416    Update {
417        from: "or_rr_16",
418        into: "or_mr_16",
419        load: "mov_rm_16",
420        store: "mov_mr_16",
421        commutes: true,
422    },
423    Update {
424        from: "or_rr_32",
425        into: "or_mr_32",
426        load: "mov_rm_32",
427        store: "mov_mr_32",
428        commutes: true,
429    },
430    Update {
431        from: "or_rr_64",
432        into: "or_mr_64",
433        load: "mov_rm_64",
434        store: "mov_mr_64",
435        commutes: true,
436    },
437    Update {
438        from: "xor_rr_8",
439        into: "xor_mr_8",
440        load: "mov_rm_8",
441        store: "mov_mr_8",
442        commutes: true,
443    },
444    Update {
445        from: "xor_rr_16",
446        into: "xor_mr_16",
447        load: "mov_rm_16",
448        store: "mov_mr_16",
449        commutes: true,
450    },
451    Update {
452        from: "xor_rr_32",
453        into: "xor_mr_32",
454        load: "mov_rm_32",
455        store: "mov_mr_32",
456        commutes: true,
457    },
458    Update {
459        from: "xor_rr_64",
460        into: "xor_mr_64",
461        load: "mov_rm_64",
462        store: "mov_mr_64",
463        commutes: true,
464    },
465];
466
467/// One arithmetic instruction against a constant that could work on memory, and the load and the
468/// store that would be the rest of the run.
469///
470/// [`Update`] with the register source replaced by an immediate, and a field shorter for it. There
471/// is no `commutes`, because there is nothing to swap: the constant is on the instruction and
472/// cannot be anywhere else, so the memory is always the left source and every row reads the same
473/// way. Subtraction is in the table without a note attached for the same reason. `subl $1, (%rax)`
474/// takes one away from the place, which is the run this matches and the only one it could be.
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476pub struct Bump {
477    /// The arithmetic as the selector wrote it, on a register and a constant.
478    pub from: &'static str,
479    /// The same arithmetic reading memory and leaving its answer there.
480    pub into: &'static str,
481    /// The load that put the memory's word in a register.
482    pub load: &'static str,
483    /// The store that put the answer back.
484    pub store: &'static str,
485}
486
487/// The arithmetic against a constant that can work on memory in place on this machine.
488///
489/// The same five operations [`UPDATES`] has, at the same four widths, and the multiply is missing
490/// for the same reason. The eight bit inclusive or is also the instruction a probing prologue
491/// writes, which is one instruction described once rather than two things that happen to encode
492/// alike.
493///
494/// Four rows take nothing today. The narrow inclusive or and exclusive or against a constant are on
495/// `crate::select::x86_64`'s list of instructions no rule selects yet, which went out under
496/// tamnd/rucc#368 and come back with the width narrowing in tamnd/rucc#375, so a program that writes
497/// `*p |= 4` through a `char` gets a constant in a register and a run this cannot match. The rows
498/// are here for the reason the descriptions of those instructions stayed: what the machine can do
499/// is true whether or not anything asks for it today, and the rows would otherwise be a second
500/// thing to remember when #375 lands.
501pub static BUMPS: &[Bump] = &[
502    Bump { from: "add_ri_8", into: "add_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
503    Bump { from: "add_ri_16", into: "add_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
504    Bump { from: "add_ri_32", into: "add_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
505    Bump { from: "add_ri_64", into: "add_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
506    Bump { from: "sub_ri_8", into: "sub_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
507    Bump { from: "sub_ri_16", into: "sub_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
508    Bump { from: "sub_ri_32", into: "sub_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
509    Bump { from: "sub_ri_64", into: "sub_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
510    Bump { from: "and_ri_8", into: "and_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
511    Bump { from: "and_ri_16", into: "and_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
512    Bump { from: "and_ri_32", into: "and_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
513    Bump { from: "and_ri_64", into: "and_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
514    Bump { from: "or_ri_8", into: "or_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
515    Bump { from: "or_ri_16", into: "or_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
516    Bump { from: "or_ri_32", into: "or_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
517    Bump { from: "or_ri_64", into: "or_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
518    Bump { from: "xor_ri_8", into: "xor_mi_8", load: "mov_rm_8", store: "mov_mr_8" },
519    Bump { from: "xor_ri_16", into: "xor_mi_16", load: "mov_rm_16", store: "mov_mr_16" },
520    Bump { from: "xor_ri_32", into: "xor_mi_32", load: "mov_rm_32", store: "mov_mr_32" },
521    Bump { from: "xor_ri_64", into: "xor_mi_64", load: "mov_rm_64", store: "mov_mr_64" },
522];
523
524/// The load this block has passed that could still end up inside something.
525///
526/// One rather than a list of them, because anything that touches memory ends the one being carried,
527/// so the one being carried is always the last memory access there was.
528#[derive(Debug, Clone, Copy)]
529struct Waiting {
530    /// The load.
531    inst: Inst,
532    /// The register it wrote, which is what the arithmetic has to be reading.
533    reg: Reg,
534    /// Which load it is, so that the width can be held against the arithmetic's.
535    load: &'static str,
536    /// How far along the block it is, which is what [`WINDOW`] is counted in.
537    at: usize,
538}
539
540/// Puts every load that can move into the arithmetic that reads it, and gives back how many.
541///
542/// `pending` is the addresses [`crate::finish`] has still to write a displacement into, and a load
543/// that moves takes its entry with it, the same way one folded into a reader does. An address into
544/// the frame arrives here already inside the load, because [`crate::fold`] has run.
545///
546/// Run after selection and after the addresses are folded, and before allocation. Before the
547/// allocator because what makes the pair safe to put together is that a virtual register is written
548/// once, and after the addresses because a load whose address is still a `lea` in front of it has
549/// nothing in its own memory operand worth carrying.
550pub fn loads(
551    func: &mut Func,
552    machine: &MachineInsts,
553    names: &mut Interner,
554    pending: &mut Pending<'_>,
555) -> usize {
556    let mut reads = Reads::of(func);
557    let mut done = 0;
558    for block in func.blocks().collect::<Vec<_>>() {
559        let mut waiting: Option<Waiting> = None;
560        for (at, inst) in func.insts(block).collect::<Vec<_>>().into_iter().enumerate() {
561            let name = names.resolve(func[inst].opcode.name()).to_owned();
562            let bare = machine.bare(&name).to_owned();
563            // Asked before the rewrite below rather than after it, because the rewrite turns an
564            // instruction that touched no memory into one that does, and asking afterwards would
565            // throw away the load that had just gone into it over the load that had just gone into
566            // it. Nothing else about the answer moves: the other end of a row of the fold table is
567            // arithmetic this target describes and is not a call.
568            let barrier = machine.calls(&name) || !machine.has(&name) || machine.touches_mem(&name);
569            if let Some(carried) = waiting {
570                if let Some(plan) = joined(func, &reads, carried, machine, names, inst, &bare) {
571                    let mut set = Changes::new();
572                    set.rewrite(inst, plan);
573                    set.remove(carried.inst);
574                    if set.commit(func, &mut reads, names, machine).is_ok() {
575                        pending.moved(carried.inst, &[inst]);
576                        waiting = None;
577                        done += 1;
578                    }
579                }
580            }
581            if barrier {
582                waiting = None;
583            }
584            if let Some(carried) = waiting {
585                if at - carried.at >= WINDOW || writes_what_it_reads(func, inst, &carried) {
586                    waiting = None;
587                }
588            }
589            if let Some(load) = FOLDS.iter().find(|fold| fold.load == bare).map(|fold| fold.load) {
590                let operands = &func[func[inst].operands];
591                if let Some(first) = operands.first().filter(|operand| operand.role.is_def()) {
592                    waiting = Some(Waiting { inst, reg: first.reg, load, at });
593                }
594            }
595        }
596    }
597    done
598}
599
600/// The three instructions that read a place, compute on what was there and write it back.
601#[derive(Debug, Clone, Copy)]
602struct Run {
603    /// The load that read the place.
604    load: Inst,
605    /// The arithmetic that read what the load put in a register.
606    alu: Inst,
607    /// The store that put the answer back where the load got it.
608    store: Inst,
609    /// Which row of [`UPDATES`] the run is.
610    update: &'static Update,
611    /// The source the arithmetic is left reading, which is the one the memory is not.
612    kept: Operand,
613}
614
615/// The same three instructions with a constant where the other source was.
616///
617/// A separate shape from [`Run`] rather than the same one with an option in it, because the two
618/// differ in what they carry and in nothing else. This one holds the constant the instruction that
619/// comes out will carry, and has no `kept`, since the arithmetic is left reading nothing at all.
620#[derive(Debug, Clone, Copy)]
621struct Bumped {
622    /// The load that read the place.
623    load: Inst,
624    /// The arithmetic that read what the load put in a register.
625    alu: Inst,
626    /// The store that put the answer back where the load got it.
627    store: Inst,
628    /// Which row of [`BUMPS`] the run is.
629    bump: &'static Bump,
630    /// The constant the arithmetic was against.
631    imm: i64,
632}
633
634/// Puts every run that reads a place, computes on it and writes it back into the one instruction
635/// this machine has for all three, and gives back how many.
636///
637/// `pending` is the addresses [`crate::finish`] has still to write a displacement into. The store
638/// is the instruction that survives and it is already waiting on the entry the load was waiting on,
639/// since the two name the same place, so the load's entry is taken off rather than moved.
640///
641/// Run before [`loads`] rather than after it. The run this looks for is three instructions the
642/// selector wrote, and folding the load into the arithmetic first would leave two instructions that
643/// are the same thing written differently, so the walk would have to know both spellings. Whatever
644/// this does not take is still there for [`loads`] to take the load out of.
645///
646/// The run whose arithmetic is against a constant is looked for after the one whose arithmetic is
647/// against a register, and the order between those two does not matter: the middle instruction
648/// decides which of them a run is, and no instruction is both an [`UPDATES`] row and a [`BUMPS`]
649/// row.
650pub fn stores(
651    func: &mut Func,
652    machine: &MachineInsts,
653    names: &mut Interner,
654    pending: &mut Pending<'_>,
655) -> usize {
656    let mut reads = Reads::of(func);
657    let mut done = 0;
658    for block in func.blocks().collect::<Vec<_>>() {
659        let insts: Vec<Inst> = func.insts(block).collect();
660        for at in 0..insts.len() {
661            let found = match run(func, &reads, machine, names, &insts, at) {
662                Some(found) => Some((
663                    found.load,
664                    found.alu,
665                    found.store,
666                    updated(func, machine, names, &found),
667                )),
668                None => constant(func, &reads, machine, names, &insts, at).map(|found| {
669                    (found.load, found.alu, found.store, bumped(func, machine, names, &found))
670                }),
671            };
672            let Some((load, alu, store, plan)) = found else { continue };
673            if !pending.alike(load, store) {
674                continue;
675            }
676            let mut set = Changes::new();
677            set.rewrite(store, plan);
678            set.remove(alu);
679            set.remove(load);
680            if set.commit(func, &mut reads, names, machine).is_ok() {
681                pending.moved(load, &[]);
682                done += 1;
683            }
684        }
685    }
686    done
687}
688
689/// The run ending in the instruction at that position, or `None`.
690///
691/// Walked backwards from the store, because the store is the end of the run and is the instruction
692/// that is left when the run is joined. Everything the walk needs is behind it: which register it
693/// is storing says which arithmetic to look for, and which source that arithmetic reads says which
694/// load.
695///
696/// An instruction an earlier fold took out is still in `insts` and is read here as though it were
697/// where it was. That costs a fold and never takes one: a removed instruction is one more thing in
698/// the way, and it cannot be the arithmetic or the load this is looking for, because each of those
699/// is the one writer of a register something still reads.
700fn run(
701    func: &Func,
702    reads: &Reads,
703    machine: &MachineInsts,
704    names: &Interner,
705    insts: &[Inst],
706    at: usize,
707) -> Option<Run> {
708    let store = insts[at];
709    let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
710    let value = *func[func[store].operands].first()?;
711    if value.role.is_def() || reads.count(value.reg) != 1 {
712        return None;
713    }
714    // One bound over the whole run rather than one per pair, so that what the window means is how
715    // far apart the first and the last of the three may be.
716    let earliest = at.saturating_sub(WINDOW);
717    let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
718    let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
719    let update = UPDATES.iter().find(|row| row.from == bare && row.store == stored)?;
720    let operands = func[func[insts[alu]].operands].to_vec();
721    let [_, first, second] = operands[..] else { return None };
722    // The left source is the one the memory takes the place of, because the answer is left where
723    // the memory operand points and the answer is tied to the left source. Where the load feeds the
724    // right one instead and the operation commutes, the two swap, which leaves the instruction
725    // computing what it computed.
726    let both = [(first, second), (second, first)];
727    let tried = if update.commutes { &both[..] } else { &both[..1] };
728    for &(source, kept) in tried {
729        if reads.count(source.reg) != 1 {
730            continue;
731        }
732        let Some(from) = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg)) else {
733            continue;
734        };
735        let load = insts[from];
736        if machine.bare(names.resolve(func[load].opcode.name())) != update.load {
737            continue;
738        }
739        if !same_place(func, load, store) {
740            continue;
741        }
742        // The registers the one instruction left is reading, which are the ones nothing between the
743        // load and the store may write. The arithmetic itself passes this without being left out of
744        // it: what it writes is the value the store is storing, and that register is not one of
745        // these.
746        let mut wanted: Vec<Reg> =
747            func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
748        wanted.push(kept.reg);
749        if !clear(func, machine, names, insts, (from, at), &wanted) {
750            continue;
751        }
752        return Some(Run { load, alu: insts[alu], store, update, kept });
753    }
754    None
755}
756
757/// The run against a constant ending in the instruction at that position, or `None`.
758///
759/// [`run`] with the arithmetic's second source gone. Walked backwards from the store for the same
760/// reason, and asking the same four questions: the stored register is read once, the source the
761/// arithmetic reads is written once by a load of the right width, that load names the same place as
762/// the store, and nothing between the two is in the way. There is no arrangement to choose between,
763/// because the constant is on the instruction and only the left source can be the memory.
764///
765/// One question [`run`] does not ask is here: where the addressing mode's registers are. The
766/// instruction that comes out has no operand in front of them, so each of them moves one place
767/// towards the front of the vector, and a mode that already pointed at the front would have to move
768/// to nowhere. That cannot happen, since the front is the value the store is storing, and refusing
769/// the run is what it costs to say so rather than to assume it.
770fn constant(
771    func: &Func,
772    reads: &Reads,
773    machine: &MachineInsts,
774    names: &Interner,
775    insts: &[Inst],
776    at: usize,
777) -> Option<Bumped> {
778    let store = insts[at];
779    let stored = machine.bare(names.resolve(func[store].opcode.name())).to_owned();
780    let value = *func[func[store].operands].first()?;
781    if value.role.is_def() || reads.count(value.reg) != 1 {
782        return None;
783    }
784    let mem = func[func[store].mem?];
785    if mem.base == Some(0) || mem.index == Some(0) {
786        return None;
787    }
788    let earliest = at.saturating_sub(WINDOW);
789    let alu = (earliest..at).rev().find(|&k| writes(func, insts[k], value.reg))?;
790    let bare = machine.bare(names.resolve(func[insts[alu]].opcode.name())).to_owned();
791    let bump = BUMPS.iter().find(|row| row.from == bare && row.store == stored)?;
792    let operands = func[func[insts[alu]].operands].to_vec();
793    let [_, source] = operands[..] else { return None };
794    let imm = func[func[insts[alu]].imm?].0;
795    if reads.count(source.reg) != 1 {
796        return None;
797    }
798    let from = (earliest..alu).rev().find(|&k| writes(func, insts[k], source.reg))?;
799    let load = insts[from];
800    if machine.bare(names.resolve(func[load].opcode.name())) != bump.load {
801        return None;
802    }
803    if !same_place(func, load, store) {
804        return None;
805    }
806    // The registers the one instruction left is reading, which are the ones in its address and no
807    // others, since the constant is not in a register and the arithmetic is left reading nothing.
808    let wanted: Vec<Reg> =
809        func[func[store].operands][1..].iter().map(|operand| operand.reg).collect();
810    if !clear(func, machine, names, insts, (from, at), &wanted) {
811        return None;
812    }
813    Some(Bumped { load, alu: insts[alu], store, bump, imm })
814}
815
816/// Whether this instruction writes that register.
817fn writes(func: &Func, inst: Inst, reg: Reg) -> bool {
818    func[func[inst].operands].iter().any(|operand| operand.role.is_def() && operand.reg == reg)
819}
820
821/// Whether the two instructions name the same place in memory.
822///
823/// The same addressing mode, the same symbol, and the same registers where the mode holds operand
824/// positions. Both instructions here write their value down first and their address behind it, so
825/// the positions line up, and the registers are compared anyway rather than the positions, because
826/// what makes two addresses one place is which registers they read.
827fn same_place(func: &Func, one: Inst, other: Inst) -> bool {
828    let (Some(here), Some(there)) = (func[one].mem, func[other].mem) else { return false };
829    let (here, there) = (func[here], func[there]);
830    if func[one].symbol != func[other].symbol {
831        return false;
832    }
833    let bare = |amode: Amode| Amode { base: None, index: None, ..amode };
834    if bare(here) != bare(there) {
835        return false;
836    }
837    let same = |left: Option<u8>, right: Option<u8>| match (left, right) {
838        (None, None) => true,
839        (Some(left), Some(right)) => {
840            func[func[one].operands][usize::from(left)].reg
841                == func[func[other].operands][usize::from(right)].reg
842        }
843        _ => false,
844    };
845    same(here.base, there.base) && same(here.index, there.index)
846}
847
848/// Whether everything between the two positions may be passed.
849///
850/// The run becomes one instruction where the store is, so the read of memory the load was doing
851/// moves down the block to there. Nothing that touches memory may be passed, for the reason the
852/// module documentation gives about [`loads`], and nothing may write a register the instruction
853/// that is left still reads.
854fn clear(
855    func: &Func,
856    machine: &MachineInsts,
857    names: &Interner,
858    insts: &[Inst],
859    span: (usize, usize),
860    wanted: &[Reg],
861) -> bool {
862    let (from, to) = span;
863    insts[from + 1..to].iter().all(|&inst| {
864        let name = names.resolve(func[inst].opcode.name());
865        if machine.calls(name) || !machine.has(name) || machine.touches_mem(name) {
866            return false;
867        }
868        !func[func[inst].operands]
869            .iter()
870            .any(|operand| operand.role.is_def() && wanted.contains(&operand.reg))
871    })
872}
873
874/// What the store becomes with the rest of the run inside it.
875///
876/// The store's own addressing mode and the source the arithmetic kept, which is the whole of it.
877/// The mode is left exactly as it was, because the operand it was written against is the value the
878/// store was storing and what takes that operand's place is one operand as well.
879fn updated(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Run) -> Plan {
880    let operands = func[func[run.store].operands].to_vec();
881    let into = names.intern(&format!("{}{}", machine.prefix, run.update.into));
882    Plan {
883        opcode: Opcode::new(into),
884        operands: [run.kept].into_iter().chain(operands[1..].iter().copied()).collect(),
885        imm: None,
886        amode: func[run.store].mem.map(|mem| func[mem]),
887        symbol: func[run.store].symbol,
888    }
889}
890
891/// What the store becomes with the rest of a constant run inside it.
892///
893/// The store's own addressing mode again, and the constant the arithmetic carried. The mode does
894/// not come through untouched this time. The value the store was storing has nothing taking its
895/// place, so the registers behind it each move one place towards the front of the operand vector,
896/// and the positions the mode holds are positions in that vector and move with them. [`constant`]
897/// is what makes sure there is a place for each of them to move to.
898fn bumped(func: &Func, machine: &MachineInsts, names: &mut Interner, run: &Bumped) -> Plan {
899    let operands = func[func[run.store].operands][1..].to_vec();
900    let into = names.intern(&format!("{}{}", machine.prefix, run.bump.into));
901    let back = |at: Option<u8>| at.map(|at| at - 1);
902    Plan {
903        opcode: Opcode::new(into),
904        operands,
905        imm: Some(run.imm),
906        amode: func[run.store].mem.map(|mem| {
907            let mem = func[mem];
908            Amode { base: back(mem.base), index: back(mem.index), ..mem }
909        }),
910        symbol: func[run.store].symbol,
911    }
912}
913
914/// Whether this instruction writes a register the carried load needs left alone.
915///
916/// The registers its address reads, and the register it wrote. The second is there for the same
917/// reason the first is: a virtual register cannot be written twice while the IR is in SSA form, and
918/// these are the physical ones a function has before the allocator runs.
919fn writes_what_it_reads(func: &Func, inst: Inst, carried: &Waiting) -> bool {
920    let written: Vec<Reg> = func[func[inst].operands]
921        .iter()
922        .filter(|operand| operand.role.is_def())
923        .map(|operand| operand.reg)
924        .collect();
925    func[func[carried.inst].operands].iter().any(|operand| written.contains(&operand.reg))
926}
927
928/// What this instruction becomes with the carried load inside it, or `None`.
929///
930/// Nothing here changes anything. What comes back is a proposal, and whether the target has the
931/// instruction it describes is [`Changes`]'s answer rather than this one.
932fn joined(
933    func: &Func,
934    reads: &Reads,
935    carried: Waiting,
936    machine: &MachineInsts,
937    names: &mut Interner,
938    inst: Inst,
939    bare: &str,
940) -> Option<Plan> {
941    let fold = FOLDS.iter().find(|fold| fold.from == bare)?;
942    if carried.load != fold.load || reads.count(carried.reg) != 1 {
943        return None;
944    }
945    let operands = func[func[inst].operands].to_vec();
946    let [answer, first, second] = operands[..] else { return None };
947    // The second source is the one the memory operand replaces, because the answer is tied to the
948    // first. Where the load feeds the first source instead and the operation commutes, the two are
949    // swapped, which leaves the instruction computing what it computed.
950    let kept = if second.reg == carried.reg {
951        first
952    } else if fold.commutes && first.reg == carried.reg {
953        second
954    } else {
955        return None;
956    };
957    let load = carried.inst;
958    let address = func[func[load].operands][1..].to_vec();
959    let mut amode = func[func[load].mem?];
960    // The registers an address names are operands behind the ones the instruction writes down, and
961    // there is one of those in front of them here where there was none in front of them in the
962    // load, so every position the mode holds moves along by one.
963    amode.base = amode.base.map(|at| at + 1);
964    amode.index = amode.index.map(|at| at + 1);
965    let into = names.intern(&format!("{}{}", machine.prefix, fold.into));
966    Some(Plan {
967        opcode: Opcode::new(into),
968        operands: [answer, kept].into_iter().chain(address).collect(),
969        imm: None,
970        amode: Some(amode),
971        symbol: func[load].symbol,
972    })
973}
974
975#[cfg(test)]
976mod tests {
977    use rucc_mir::{self as mir, Constraint, Mem, Operand};
978    use rucc_target::x86_64::{GPR, MACHINE};
979
980    use super::*;
981
982    /// A function with one block, and the names it was built with.
983    fn empty() -> (Interner, Func, mir::Block) {
984        let mut names = Interner::new();
985        let mut func = Func::new(names.intern("f"));
986        let block = func.create_block();
987        (names, func, block)
988    }
989
990    /// The opcode of that name on this target.
991    fn op(names: &mut Interner, name: &str) -> Opcode {
992        Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
993    }
994
995    /// A load of eight bytes off that register.
996    fn load(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg) -> Reg {
997        let into = func.new_vreg(GPR);
998        let mov = op(names, "mov_rm_64");
999        func.build(block, mov)
1000            .def(into, GPR)
1001            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1002            .finish();
1003        into
1004    }
1005
1006    /// Two-address arithmetic of that name on those two registers, in that order.
1007    fn alu(
1008        func: &mut Func,
1009        names: &mut Interner,
1010        block: mir::Block,
1011        name: &str,
1012        first: Reg,
1013        second: Reg,
1014    ) -> Reg {
1015        let answer = func.new_vreg(GPR);
1016        let opcode = op(names, name);
1017        func.build(block, opcode)
1018            .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
1019            .uses(first, GPR)
1020            .uses(second, GPR)
1021            .finish();
1022        answer
1023    }
1024
1025    /// What every instruction in a block came to, as opcodes.
1026    fn shape(func: &Func, names: &Interner, block: mir::Block) -> Vec<String> {
1027        func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
1028    }
1029
1030    /// The pass, with lists nothing is on.
1031    fn combine(func: &mut Func, names: &mut Interner) -> usize {
1032        let mut addresses = Vec::new();
1033        let mut arguments = Vec::new();
1034        let mut dynamic = Vec::new();
1035        let mut pending =
1036            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1037        loads(func, &MACHINE, names, &mut pending)
1038    }
1039
1040    /// A store of that register to sixteen off that base, which is the address `load` reads.
1041    fn store(func: &mut Func, names: &mut Interner, block: mir::Block, base: Reg, value: Reg) {
1042        let mov = op(names, "mov_mr_64");
1043        func.build(block, mov)
1044            .uses(value, GPR)
1045            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1046            .finish();
1047    }
1048
1049    /// The other walk, with lists nothing is on.
1050    fn update(func: &mut Func, names: &mut Interner) -> usize {
1051        let mut addresses = Vec::new();
1052        let mut arguments = Vec::new();
1053        let mut dynamic = Vec::new();
1054        let mut pending =
1055            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1056        stores(func, &MACHINE, names, &mut pending)
1057    }
1058
1059    /// The shape the second walk is for, which is what `*p += x` is.
1060    #[test]
1061    fn a_word_read_changed_and_written_back_becomes_one_instruction() {
1062        let (mut names, mut func, block) = empty();
1063        let base = func.new_vreg(GPR);
1064        let other = func.new_vreg(GPR);
1065        let word = load(&mut func, &mut names, block, base);
1066        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1067        store(&mut func, &mut names, block, base, sum);
1068
1069        assert_eq!(update(&mut func, &mut names), 1);
1070        assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
1071        let inst = func.insts(block).next().expect("the addition");
1072        let mem = func[inst].mem.expect("it writes memory");
1073        assert_eq!(func[mem].disp, 16, "the address came from the store");
1074        assert_eq!(func[mem].base, Some(1), "and names the operand behind the source");
1075        assert_eq!(func[func[inst].operands].len(), 2, "one source and the base of the address");
1076        assert_eq!(func[func[inst].operands][0].reg, other, "the source it kept");
1077        assert_eq!(func[func[inst].operands][1].reg, base, "the address");
1078    }
1079
1080    /// The same run with the load feeding the right source instead, which an addition does not
1081    /// mind. What `subq %rax, (%rcx)` computes is memory minus register, so the subtraction below
1082    /// is the one that has to care.
1083    #[test]
1084    fn a_word_read_into_the_right_source_of_an_addition_is_still_one_instruction() {
1085        let (mut names, mut func, block) = empty();
1086        let base = func.new_vreg(GPR);
1087        let other = func.new_vreg(GPR);
1088        let word = load(&mut func, &mut names, block, base);
1089        let sum = alu(&mut func, &mut names, block, "add_rr_64", other, word);
1090        store(&mut func, &mut names, block, base, sum);
1091
1092        assert_eq!(update(&mut func, &mut names), 1);
1093        assert_eq!(shape(&func, &names, block), ["x64.add_mr_64"]);
1094        assert_eq!(func[func[func.insts(block).next().expect("it")].operands][0].reg, other);
1095    }
1096
1097    /// A subtraction with the memory on the left, which is `*p -= x` and is what the machine
1098    /// instruction computes.
1099    #[test]
1100    fn a_subtraction_taking_a_register_away_from_memory_becomes_one_instruction() {
1101        let (mut names, mut func, block) = empty();
1102        let base = func.new_vreg(GPR);
1103        let other = func.new_vreg(GPR);
1104        let word = load(&mut func, &mut names, block, base);
1105        let left = alu(&mut func, &mut names, block, "sub_rr_64", word, other);
1106        store(&mut func, &mut names, block, base, left);
1107
1108        assert_eq!(update(&mut func, &mut names), 1);
1109        assert_eq!(shape(&func, &names, block), ["x64.sub_mr_64"]);
1110    }
1111
1112    /// And the same subtraction the other way round, which is `*p = x - *p`. The machine
1113    /// instruction would compute the other answer, so the run stays three instructions.
1114    #[test]
1115    fn a_subtraction_taking_memory_away_from_a_register_stays_three_instructions() {
1116        let (mut names, mut func, block) = empty();
1117        let base = func.new_vreg(GPR);
1118        let other = func.new_vreg(GPR);
1119        let word = load(&mut func, &mut names, block, base);
1120        let left = alu(&mut func, &mut names, block, "sub_rr_64", other, word);
1121        store(&mut func, &mut names, block, base, left);
1122
1123        assert_eq!(update(&mut func, &mut names), 0);
1124        assert_eq!(
1125            shape(&func, &names, block),
1126            ["x64.mov_rm_64", "x64.sub_rr_64", "x64.mov_mr_64"]
1127        );
1128    }
1129
1130    /// A store to somewhere else. The answer is not going back where it came from, so what is left
1131    /// is a load and an arithmetic and a store of three different addresses.
1132    #[test]
1133    fn a_store_to_another_address_stays_three_instructions() {
1134        let (mut names, mut func, block) = empty();
1135        let base = func.new_vreg(GPR);
1136        let elsewhere = func.new_vreg(GPR);
1137        let other = func.new_vreg(GPR);
1138        let word = load(&mut func, &mut names, block, base);
1139        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1140        store(&mut func, &mut names, block, elsewhere, sum);
1141
1142        assert_eq!(update(&mut func, &mut names), 0);
1143    }
1144
1145    /// The same address at a different displacement, which is the near miss the comparison has to
1146    /// catch rather than the obvious one above.
1147    #[test]
1148    fn a_store_at_another_displacement_stays_three_instructions() {
1149        let (mut names, mut func, block) = empty();
1150        let base = func.new_vreg(GPR);
1151        let other = func.new_vreg(GPR);
1152        let word = load(&mut func, &mut names, block, base);
1153        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1154        let mov = op(&mut names, "mov_mr_64");
1155        func.build(block, mov)
1156            .uses(sum, GPR)
1157            .mem(Mem { disp: 24, ..Mem::at(Operand::read(base, GPR)) })
1158            .finish();
1159
1160        assert_eq!(update(&mut func, &mut names), 0);
1161    }
1162
1163    /// The word read again by something else. The load has to stay for the second reader, so the
1164    /// run is not a run.
1165    #[test]
1166    fn a_word_two_instructions_read_stays_three_instructions() {
1167        let (mut names, mut func, block) = empty();
1168        let base = func.new_vreg(GPR);
1169        let other = func.new_vreg(GPR);
1170        let word = load(&mut func, &mut names, block, base);
1171        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1172        alu(&mut func, &mut names, block, "xor_rr_64", word, other);
1173        store(&mut func, &mut names, block, base, sum);
1174
1175        assert_eq!(update(&mut func, &mut names), 0);
1176    }
1177
1178    /// The answer read by something else as well as by the store, which is `x = *p += 1` and
1179    /// leaves the answer wanted in a register the joined instruction never writes.
1180    #[test]
1181    fn an_answer_something_else_reads_stays_three_instructions() {
1182        let (mut names, mut func, block) = empty();
1183        let base = func.new_vreg(GPR);
1184        let other = func.new_vreg(GPR);
1185        let word = load(&mut func, &mut names, block, base);
1186        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1187        store(&mut func, &mut names, block, base, sum);
1188        alu(&mut func, &mut names, block, "xor_rr_64", sum, other);
1189
1190        assert_eq!(update(&mut func, &mut names), 0);
1191    }
1192
1193    /// Another access to memory in the middle. The read the run does moves down the block to where
1194    /// the write was, so it would be moving past this one.
1195    #[test]
1196    fn a_run_with_another_access_in_the_middle_stays_three_instructions() {
1197        let (mut names, mut func, block) = empty();
1198        let base = func.new_vreg(GPR);
1199        let other = func.new_vreg(GPR);
1200        let word = load(&mut func, &mut names, block, base);
1201        load(&mut func, &mut names, block, other);
1202        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1203        store(&mut func, &mut names, block, base, sum);
1204
1205        assert_eq!(update(&mut func, &mut names), 0);
1206    }
1207
1208    /// Something writing the address register in the middle. A physical register is the only one
1209    /// this can happen to before the allocator runs, and the frame is addressed through two.
1210    #[test]
1211    fn a_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
1212        let (mut names, mut func, block) = empty();
1213        let base = Reg::physical(rucc_target::x86_64::RSP);
1214        let other = func.new_vreg(GPR);
1215        let word = load(&mut func, &mut names, block, base);
1216        let sub = op(&mut names, "sub_ri_64");
1217        func.build(block, sub)
1218            .operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
1219            .uses(base, GPR)
1220            .imm(32)
1221            .finish();
1222        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1223        store(&mut func, &mut names, block, base, sum);
1224
1225        assert_eq!(update(&mut func, &mut names), 0);
1226    }
1227
1228    /// Two locals whose displacements are both nothing so far. They are the same registers and the
1229    /// same number here and are two different places, and what says so is the list the frame layout
1230    /// has still to write an offset into.
1231    #[test]
1232    fn two_locals_the_layout_has_not_placed_yet_are_not_the_same_place() {
1233        let (mut names, mut func, block) = empty();
1234        let base = Reg::physical(rucc_target::x86_64::RSP);
1235        let other = func.new_vreg(GPR);
1236        let mov = op(&mut names, "mov_rm_64");
1237        let word = func.new_vreg(GPR);
1238        func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1239        let read = func.insts(block).next().expect("the load");
1240        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1241        let put = op(&mut names, "mov_mr_64");
1242        func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1243        let written = func.insts(block).nth(2).expect("the store");
1244
1245        let mut addresses = vec![(read, 3usize), (written, 4usize)];
1246        let mut arguments = Vec::new();
1247        let mut dynamic = Vec::new();
1248        let mut pending =
1249            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1250        assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
1251    }
1252
1253    /// The one local, which is the same place twice and folds. The entry the load was waiting on
1254    /// comes off the list, because the store is already waiting on the same one and adding the
1255    /// frame's offset twice would put the local at twice its distance.
1256    #[test]
1257    fn the_frame_entry_of_a_load_that_goes_comes_off_the_list() {
1258        let (mut names, mut func, block) = empty();
1259        let base = Reg::physical(rucc_target::x86_64::RSP);
1260        let other = func.new_vreg(GPR);
1261        let mov = op(&mut names, "mov_rm_64");
1262        let word = func.new_vreg(GPR);
1263        func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1264        let read = func.insts(block).next().expect("the load");
1265        let sum = alu(&mut func, &mut names, block, "add_rr_64", word, other);
1266        let put = op(&mut names, "mov_mr_64");
1267        func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1268        let written = func.insts(block).nth(2).expect("the store");
1269
1270        let mut addresses = vec![(read, 3usize), (written, 3usize)];
1271        let mut arguments = Vec::new();
1272        let mut dynamic = Vec::new();
1273        let mut pending =
1274            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1275        assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
1276
1277        let inst = func.insts(block).next().expect("the addition");
1278        assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
1279    }
1280
1281    /// A run of the wrong width, which is a load of four bytes under an addition of eight.
1282    #[test]
1283    fn a_run_whose_widths_disagree_stays_three_instructions() {
1284        let (mut names, mut func, block) = empty();
1285        let base = func.new_vreg(GPR);
1286        let other = func.new_vreg(GPR);
1287        let into = func.new_vreg(GPR);
1288        let narrow = op(&mut names, "mov_rm_32");
1289        func.build(block, narrow)
1290            .def(into, GPR)
1291            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1292            .finish();
1293        let sum = alu(&mut func, &mut names, block, "add_rr_64", into, other);
1294        store(&mut func, &mut names, block, base, sum);
1295
1296        assert_eq!(update(&mut func, &mut names), 0);
1297    }
1298
1299    /// Every row of the table names four instructions this target has, all of one width.
1300    #[test]
1301    fn every_row_of_the_update_table_is_four_instructions_this_target_has() {
1302        for update in UPDATES {
1303            for name in [update.from, update.into, update.load, update.store] {
1304                assert!(MACHINE.has(name), "{name} is not an instruction");
1305            }
1306            let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
1307            assert_eq!(width(update.from), width(update.into), "{} changes width", update.from);
1308            assert_eq!(
1309                width(update.from),
1310                width(update.load),
1311                "{} loads another width",
1312                update.from
1313            );
1314            assert_eq!(
1315                width(update.from),
1316                width(update.store),
1317                "{} stores another width",
1318                update.from
1319            );
1320            assert!((MACHINE.takes_mem)(update.into), "{} reaches no memory", update.into);
1321            assert!(!(MACHINE.takes_mem)(update.from), "{} already reaches memory", update.from);
1322        }
1323    }
1324
1325    /// One row per arithmetic instruction this machine can do in place, for the reason the count
1326    /// over the fold table is there.
1327    #[test]
1328    fn the_update_table_covers_the_arithmetic_this_target_can_do_in_place() {
1329        assert_eq!(UPDATES.len(), 20, "five operations at four widths, and no multiply");
1330        let commuting = UPDATES.iter().filter(|update| update.commutes).count();
1331        assert_eq!(commuting, 16, "everything but the four subtractions");
1332    }
1333
1334    /// Two-address arithmetic of that name against a constant.
1335    fn alu_imm(
1336        func: &mut Func,
1337        names: &mut Interner,
1338        block: mir::Block,
1339        name: &str,
1340        source: Reg,
1341        value: i64,
1342    ) -> Reg {
1343        let answer = func.new_vreg(GPR);
1344        let opcode = op(names, name);
1345        func.build(block, opcode)
1346            .operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
1347            .uses(source, GPR)
1348            .imm(value)
1349            .finish();
1350        answer
1351    }
1352
1353    /// The shape the constant run is for, which is what `*p += 1` is.
1354    #[test]
1355    fn a_word_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
1356        let (mut names, mut func, block) = empty();
1357        let base = func.new_vreg(GPR);
1358        let word = load(&mut func, &mut names, block, base);
1359        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1360        store(&mut func, &mut names, block, base, sum);
1361
1362        assert_eq!(update(&mut func, &mut names), 1);
1363        assert_eq!(shape(&func, &names, block), ["x64.add_mi_64"]);
1364        let inst = func.insts(block).next().expect("the addition");
1365        let mem = func[inst].mem.expect("it writes memory");
1366        assert_eq!(func[mem].disp, 16, "the address came from the store");
1367        assert_eq!(func[mem].base, Some(0), "which is now the first operand and not the second");
1368        assert_eq!(func[func[inst].operands].len(), 1, "the base of the address and nothing else");
1369        assert_eq!(func[func[inst].operands][0].reg, base, "the address");
1370        assert_eq!(func[func[inst].imm.expect("the constant")].0, 1);
1371    }
1372
1373    /// The subtraction, which needs no arrangement chosen for it. A constant cannot be the left
1374    /// source, so the run that exists is the one the instruction computes.
1375    #[test]
1376    fn a_constant_taken_away_from_a_place_becomes_one_instruction() {
1377        let (mut names, mut func, block) = empty();
1378        let base = func.new_vreg(GPR);
1379        let word = load(&mut func, &mut names, block, base);
1380        let left = alu_imm(&mut func, &mut names, block, "sub_ri_64", word, 7);
1381        store(&mut func, &mut names, block, base, left);
1382
1383        assert_eq!(update(&mut func, &mut names), 1);
1384        assert_eq!(shape(&func, &names, block), ["x64.sub_mi_64"]);
1385        assert_eq!(func[func[func.insts(block).next().expect("it")].imm.expect("it")].0, 7);
1386    }
1387
1388    /// The narrow one, so that a width that is carried through wrong is a test that fails rather
1389    /// than a program that is wrong.
1390    #[test]
1391    fn a_byte_read_changed_by_a_constant_and_written_back_becomes_one_instruction() {
1392        let (mut names, mut func, block) = empty();
1393        let base = func.new_vreg(GPR);
1394        let word = func.new_vreg(GPR);
1395        let mov = op(&mut names, "mov_rm_8");
1396        func.build(block, mov)
1397            .def(word, GPR)
1398            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1399            .finish();
1400        let sum = alu_imm(&mut func, &mut names, block, "or_ri_8", word, 4);
1401        let put = op(&mut names, "mov_mr_8");
1402        func.build(block, put)
1403            .uses(sum, GPR)
1404            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1405            .finish();
1406
1407        assert_eq!(update(&mut func, &mut names), 1);
1408        assert_eq!(shape(&func, &names, block), ["x64.or_mi_8"]);
1409    }
1410
1411    /// The word read again by something else, which is the first of the four conditions and is
1412    /// asked here the way it is asked of the register run.
1413    #[test]
1414    fn a_word_a_constant_changes_and_something_else_reads_stays_three_instructions() {
1415        let (mut names, mut func, block) = empty();
1416        let base = func.new_vreg(GPR);
1417        let other = func.new_vreg(GPR);
1418        let word = load(&mut func, &mut names, block, base);
1419        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1420        alu(&mut func, &mut names, block, "xor_rr_64", word, other);
1421        store(&mut func, &mut names, block, base, sum);
1422
1423        assert_eq!(update(&mut func, &mut names), 0);
1424    }
1425
1426    /// Something else in the middle that touches memory, which the one instruction left would be
1427    /// passing if the run were joined.
1428    #[test]
1429    fn a_constant_run_with_another_access_in_the_middle_stays_three_instructions() {
1430        let (mut names, mut func, block) = empty();
1431        let base = func.new_vreg(GPR);
1432        let elsewhere = func.new_vreg(GPR);
1433        let word = load(&mut func, &mut names, block, base);
1434        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1435        load(&mut func, &mut names, block, elsewhere);
1436        store(&mut func, &mut names, block, base, sum);
1437
1438        assert_eq!(update(&mut func, &mut names), 0);
1439    }
1440
1441    /// The address register written between the load and the store, which would leave the one
1442    /// instruction naming a different place from the one the run read.
1443    #[test]
1444    fn a_constant_run_whose_address_register_is_written_in_the_middle_stays_three_instructions() {
1445        let (mut names, mut func, block) = empty();
1446        let base = Reg::physical(rucc_target::x86_64::RAX);
1447        let word = load(&mut func, &mut names, block, base);
1448        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1449        let mov = op(&mut names, "mov_ri_64");
1450        func.build(block, mov).def(base, GPR).imm(0).finish();
1451        store(&mut func, &mut names, block, base, sum);
1452
1453        assert_eq!(update(&mut func, &mut names), 0);
1454    }
1455
1456    /// A store somewhere else, which is the run that is not a run.
1457    #[test]
1458    fn a_constant_written_to_another_address_stays_three_instructions() {
1459        let (mut names, mut func, block) = empty();
1460        let base = func.new_vreg(GPR);
1461        let elsewhere = func.new_vreg(GPR);
1462        let word = load(&mut func, &mut names, block, base);
1463        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1464        store(&mut func, &mut names, block, elsewhere, sum);
1465
1466        assert_eq!(update(&mut func, &mut names), 0);
1467    }
1468
1469    /// A run of the wrong width, which is a load of four bytes under an addition of eight.
1470    #[test]
1471    fn a_constant_run_whose_widths_disagree_stays_three_instructions() {
1472        let (mut names, mut func, block) = empty();
1473        let base = func.new_vreg(GPR);
1474        let into = func.new_vreg(GPR);
1475        let narrow = op(&mut names, "mov_rm_32");
1476        func.build(block, narrow)
1477            .def(into, GPR)
1478            .mem(Mem { disp: 16, ..Mem::at(Operand::read(base, GPR)) })
1479            .finish();
1480        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", into, 1);
1481        store(&mut func, &mut names, block, base, sum);
1482
1483        assert_eq!(update(&mut func, &mut names), 0);
1484    }
1485
1486    /// The multiply, which has a two-address form against a constant and no form that leaves the
1487    /// product in memory, so the run stays three instructions.
1488    #[test]
1489    fn a_place_multiplied_by_a_constant_stays_three_instructions() {
1490        let (mut names, mut func, block) = empty();
1491        let base = func.new_vreg(GPR);
1492        let word = load(&mut func, &mut names, block, base);
1493        let product = alu_imm(&mut func, &mut names, block, "imul_ri_64", word, 3);
1494        store(&mut func, &mut names, block, base, product);
1495
1496        assert_eq!(update(&mut func, &mut names), 0);
1497    }
1498
1499    /// The local, which is the same place twice and folds, and whose frame entry comes off the
1500    /// list for the reason the register run's does.
1501    #[test]
1502    fn the_frame_entry_of_a_load_a_constant_run_takes_comes_off_the_list() {
1503        let (mut names, mut func, block) = empty();
1504        let base = Reg::physical(rucc_target::x86_64::RSP);
1505        let mov = op(&mut names, "mov_rm_64");
1506        let word = func.new_vreg(GPR);
1507        func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1508        let read = func.insts(block).next().expect("the load");
1509        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1510        let put = op(&mut names, "mov_mr_64");
1511        func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1512        let written = func.insts(block).nth(2).expect("the store");
1513
1514        let mut addresses = vec![(read, 3usize), (written, 3usize)];
1515        let mut arguments = Vec::new();
1516        let mut dynamic = Vec::new();
1517        let mut pending =
1518            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1519        assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 1);
1520
1521        let inst = func.insts(block).next().expect("the addition");
1522        assert_eq!(addresses, [(inst, 3usize)], "one entry, on the instruction that is left");
1523    }
1524
1525    /// Two locals the layout has not placed yet, which are the same addressing mode and not the
1526    /// same place, the way they are for the register run.
1527    #[test]
1528    fn two_locals_a_constant_run_would_join_are_not_the_same_place() {
1529        let (mut names, mut func, block) = empty();
1530        let base = Reg::physical(rucc_target::x86_64::RSP);
1531        let mov = op(&mut names, "mov_rm_64");
1532        let word = func.new_vreg(GPR);
1533        func.build(block, mov).def(word, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1534        let read = func.insts(block).next().expect("the load");
1535        let sum = alu_imm(&mut func, &mut names, block, "add_ri_64", word, 1);
1536        let put = op(&mut names, "mov_mr_64");
1537        func.build(block, put).uses(sum, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1538        let written = func.insts(block).nth(2).expect("the store");
1539
1540        let mut addresses = vec![(read, 3usize), (written, 4usize)];
1541        let mut arguments = Vec::new();
1542        let mut dynamic = Vec::new();
1543        let mut pending =
1544            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1545        assert_eq!(stores(&mut func, &MACHINE, &mut names, &mut pending), 0);
1546    }
1547
1548    /// Every row of the constant table names four instructions this target has, all of one width.
1549    #[test]
1550    fn every_row_of_the_bump_table_is_four_instructions_this_target_has() {
1551        for bump in BUMPS {
1552            for name in [bump.from, bump.into, bump.load, bump.store] {
1553                assert!(MACHINE.has(name), "{name} is not an instruction");
1554            }
1555            let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
1556            assert_eq!(width(bump.from), width(bump.into), "{} changes width", bump.from);
1557            assert_eq!(width(bump.from), width(bump.load), "{} loads another width", bump.from);
1558            assert_eq!(width(bump.from), width(bump.store), "{} stores another width", bump.from);
1559            assert!((MACHINE.takes_mem)(bump.into), "{} reaches no memory", bump.into);
1560            assert!(!(MACHINE.takes_mem)(bump.from), "{} already reaches memory", bump.from);
1561            assert!((MACHINE.takes_imm)(bump.into), "{} carries no constant", bump.into);
1562        }
1563    }
1564
1565    /// One row per arithmetic instruction this machine can do in place against a constant, which is
1566    /// the same five operations at the same four widths the register table has.
1567    #[test]
1568    fn the_bump_table_covers_the_arithmetic_this_target_can_do_in_place_against_a_constant() {
1569        assert_eq!(BUMPS.len(), 20, "five operations at four widths, and no multiply");
1570        let register: Vec<&str> = UPDATES.iter().map(|update| update.from).collect();
1571        for bump in BUMPS {
1572            let same = bump.from.replace("_ri_", "_rr_");
1573            assert!(register.contains(&same.as_str()), "{} has no register row", bump.from);
1574        }
1575    }
1576
1577    /// No instruction is in both tables, which is what lets the two walks be tried one after the
1578    /// other without either having to know what the other took.
1579    #[test]
1580    fn nothing_is_both_a_register_run_and_a_constant_run() {
1581        for bump in BUMPS {
1582            assert!(
1583                !UPDATES.iter().any(|update| update.from == bump.from),
1584                "{} starts both kinds of run",
1585                bump.from
1586            );
1587        }
1588    }
1589
1590    /// The shape the whole pass is for.
1591    #[test]
1592    fn a_load_read_once_by_an_addition_becomes_its_memory_operand() {
1593        let (mut names, mut func, block) = empty();
1594        let base = func.new_vreg(GPR);
1595        let other = func.new_vreg(GPR);
1596        let word = load(&mut func, &mut names, block, base);
1597        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1598
1599        assert_eq!(combine(&mut func, &mut names), 1);
1600        assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
1601        let inst = func.insts(block).next().expect("the addition");
1602        let mem = func[inst].mem.expect("the addition reads memory now");
1603        assert_eq!(func[mem].disp, 16, "the load's displacement came with it");
1604        assert_eq!(func[mem].base, Some(2), "and names the operand behind the source it kept");
1605        assert_eq!(func[func[inst].operands][1].reg, other, "the source it kept");
1606        assert_eq!(func[func[inst].operands][2].reg, base, "the address it took on");
1607    }
1608
1609    /// The same load feeding the source the answer is tied to. The two sources are swapped, which
1610    /// an addition does not mind and is what lets this fold at all.
1611    #[test]
1612    fn a_load_feeding_the_first_source_of_an_addition_is_swapped_and_folded() {
1613        let (mut names, mut func, block) = empty();
1614        let base = func.new_vreg(GPR);
1615        let other = func.new_vreg(GPR);
1616        let word = load(&mut func, &mut names, block, base);
1617        alu(&mut func, &mut names, block, "add_rr_64", word, other);
1618
1619        assert_eq!(combine(&mut func, &mut names), 1);
1620        assert_eq!(shape(&func, &names, block), ["x64.add_rm_64"]);
1621        let inst = func.insts(block).next().expect("the addition");
1622        assert_eq!(func[func[inst].operands][1].reg, other);
1623    }
1624
1625    /// A subtraction with the load on the left, which is the one place the swap above would change
1626    /// the answer.
1627    #[test]
1628    fn a_load_feeding_the_left_of_a_subtraction_stays_a_load() {
1629        let (mut names, mut func, block) = empty();
1630        let base = func.new_vreg(GPR);
1631        let other = func.new_vreg(GPR);
1632        let word = load(&mut func, &mut names, block, base);
1633        alu(&mut func, &mut names, block, "sub_rr_64", word, other);
1634
1635        assert_eq!(combine(&mut func, &mut names), 0);
1636        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.sub_rr_64"]);
1637    }
1638
1639    /// And the same subtraction the other way round, which is the one that folds.
1640    #[test]
1641    fn a_load_feeding_the_right_of_a_subtraction_folds() {
1642        let (mut names, mut func, block) = empty();
1643        let base = func.new_vreg(GPR);
1644        let other = func.new_vreg(GPR);
1645        let word = load(&mut func, &mut names, block, base);
1646        alu(&mut func, &mut names, block, "sub_rr_64", other, word);
1647
1648        assert_eq!(combine(&mut func, &mut names), 1);
1649        assert_eq!(shape(&func, &names, block), ["x64.sub_rm_64"]);
1650    }
1651
1652    /// Two readers. The load has to stay where it is for the second of them, so putting it into the
1653    /// first buys nothing and reads the memory twice.
1654    #[test]
1655    fn a_load_two_instructions_read_stays_a_load() {
1656        let (mut names, mut func, block) = empty();
1657        let base = func.new_vreg(GPR);
1658        let other = func.new_vreg(GPR);
1659        let word = load(&mut func, &mut names, block, base);
1660        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1661        alu(&mut func, &mut names, block, "xor_rr_64", other, word);
1662
1663        assert_eq!(combine(&mut func, &mut names), 0);
1664        assert_eq!(
1665            shape(&func, &names, block),
1666            ["x64.mov_rm_64", "x64.add_rr_64", "x64.xor_rr_64"]
1667        );
1668    }
1669
1670    /// A store between the two. Whether it writes what the load reads is a question about two
1671    /// addresses, and the answer to not being able to tell is to leave the load where it is.
1672    #[test]
1673    fn a_load_with_a_store_between_it_and_its_reader_stays_a_load() {
1674        let (mut names, mut func, block) = empty();
1675        let base = func.new_vreg(GPR);
1676        let other = func.new_vreg(GPR);
1677        let word = load(&mut func, &mut names, block, base);
1678        let store = op(&mut names, "mov_mr_64");
1679        func.build(block, store).uses(other, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1680        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1681
1682        assert_eq!(combine(&mut func, &mut names), 0);
1683        assert_eq!(
1684            shape(&func, &names, block),
1685            ["x64.mov_rm_64", "x64.mov_mr_64", "x64.add_rr_64"]
1686        );
1687    }
1688
1689    /// Another load between the two, which writes nothing and is still not passed.
1690    ///
1691    /// This is the one that would be wrong if the walk asked only about writes. Where both reads
1692    /// are `volatile` the program said which of them happens first, and nothing here can tell that
1693    /// program from the one that did not say it, so neither may be reordered.
1694    #[test]
1695    fn a_load_with_another_load_between_it_and_its_reader_stays_a_load() {
1696        let (mut names, mut func, block) = empty();
1697        let base = func.new_vreg(GPR);
1698        let other = func.new_vreg(GPR);
1699        let word = load(&mut func, &mut names, block, base);
1700        load(&mut func, &mut names, block, other);
1701        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1702
1703        assert_eq!(combine(&mut func, &mut names), 0);
1704        assert_eq!(
1705            shape(&func, &names, block),
1706            ["x64.mov_rm_64", "x64.mov_rm_64", "x64.add_rr_64"]
1707        );
1708    }
1709
1710    /// The second of two loads, read by arithmetic that reads the first as well. Nothing moves past
1711    /// anything, which is what makes this one the shape the pass is allowed to take.
1712    #[test]
1713    fn the_later_of_two_loads_is_the_one_that_folds() {
1714        let (mut names, mut func, block) = empty();
1715        let base = func.new_vreg(GPR);
1716        let other = func.new_vreg(GPR);
1717        let first = load(&mut func, &mut names, block, base);
1718        let second = load(&mut func, &mut names, block, other);
1719        alu(&mut func, &mut names, block, "add_rr_64", first, second);
1720
1721        assert_eq!(combine(&mut func, &mut names), 1);
1722        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rm_64"]);
1723        let addition = func.insts(block).nth(1).expect("the addition");
1724        assert_eq!(func[func[addition].operands][1].reg, first, "the earlier load is still read");
1725        assert_eq!(func[func[addition].operands][2].reg, other, "and the later one is the address");
1726    }
1727
1728    /// A call between the two. What a call does to memory is not in the instruction, so it is the
1729    /// same answer as the store and reached without asking about the address.
1730    #[test]
1731    fn a_load_with_a_call_between_it_and_its_reader_stays_a_load() {
1732        let (mut names, mut func, block) = empty();
1733        let base = func.new_vreg(GPR);
1734        let other = func.new_vreg(GPR);
1735        let word = load(&mut func, &mut names, block, base);
1736        let call = op(&mut names, "call");
1737        func.build(block, call).finish();
1738        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1739
1740        assert_eq!(combine(&mut func, &mut names), 0);
1741        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.call", "x64.add_rr_64"]);
1742    }
1743
1744    /// Something writing the register the address reads. A physical register is the only one this
1745    /// can happen to while the IR is in SSA form, and the frame is addressed through two of them.
1746    #[test]
1747    fn a_load_whose_address_register_is_written_between_the_two_stays_a_load() {
1748        let (mut names, mut func, block) = empty();
1749        let base = Reg::physical(rucc_target::x86_64::RSP);
1750        let other = func.new_vreg(GPR);
1751        let word = load(&mut func, &mut names, block, base);
1752        let sub = op(&mut names, "sub_ri_64");
1753        func.build(block, sub)
1754            .operand(Operand::write(base, GPR).with(Constraint::Reuse(1)))
1755            .uses(base, GPR)
1756            .imm(32)
1757            .finish();
1758        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1759
1760        assert_eq!(combine(&mut func, &mut names), 0);
1761    }
1762
1763    /// A load of four bytes under an addition of eight. The register held what the load put in it
1764    /// and a memory operand holds what is at the address, which is a different number of bytes.
1765    #[test]
1766    fn a_load_of_the_wrong_width_stays_a_load() {
1767        let (mut names, mut func, block) = empty();
1768        let base = func.new_vreg(GPR);
1769        let other = func.new_vreg(GPR);
1770        let into = func.new_vreg(GPR);
1771        let narrow = op(&mut names, "mov_rm_32");
1772        func.build(block, narrow).def(into, GPR).mem(Mem::at(Operand::read(base, GPR))).finish();
1773        alu(&mut func, &mut names, block, "add_rr_64", other, into);
1774
1775        assert_eq!(combine(&mut func, &mut names), 0);
1776        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_32", "x64.add_rr_64"]);
1777    }
1778
1779    /// A load whose value leaves the block on an edge. It is read by nothing in any operand vector
1780    /// and is read all the same, which is the count that is easy to get wrong.
1781    #[test]
1782    fn a_load_whose_value_an_edge_carries_stays_a_load() {
1783        let (mut names, mut func, block) = empty();
1784        let next = func.create_block();
1785        let base = func.new_vreg(GPR);
1786        let other = func.new_vreg(GPR);
1787        let word = load(&mut func, &mut names, block, base);
1788        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1789        let arrived = func.new_vreg(GPR);
1790        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
1791        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![word])];
1792
1793        assert_eq!(combine(&mut func, &mut names), 0);
1794        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64", "x64.add_rr_64"]);
1795    }
1796
1797    /// A reader in another block, which is the whole of what block local means here.
1798    #[test]
1799    fn a_reader_in_another_block_stays_where_it_is() {
1800        let (mut names, mut func, block) = empty();
1801        let next = func.create_block();
1802        let base = func.new_vreg(GPR);
1803        let other = func.new_vreg(GPR);
1804        let word = load(&mut func, &mut names, block, base);
1805        alu(&mut func, &mut names, next, "add_rr_64", other, word);
1806
1807        assert_eq!(combine(&mut func, &mut names), 0);
1808        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
1809        assert_eq!(shape(&func, &names, next), ["x64.add_rr_64"]);
1810    }
1811
1812    /// A reader further down the block than the window reaches.
1813    #[test]
1814    fn a_reader_past_the_window_stays_where_it_is() {
1815        let (mut names, mut func, block) = empty();
1816        let base = func.new_vreg(GPR);
1817        let other = func.new_vreg(GPR);
1818        let word = load(&mut func, &mut names, block, base);
1819        let nop = op(&mut names, "nop");
1820        for _ in 0..WINDOW {
1821            func.build(block, nop).finish();
1822        }
1823        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1824
1825        assert_eq!(combine(&mut func, &mut names), 0);
1826    }
1827
1828    /// And one instruction closer, which is the last place it still folds.
1829    #[test]
1830    fn a_reader_at_the_edge_of_the_window_folds() {
1831        let (mut names, mut func, block) = empty();
1832        let base = func.new_vreg(GPR);
1833        let other = func.new_vreg(GPR);
1834        let word = load(&mut func, &mut names, block, base);
1835        let nop = op(&mut names, "nop");
1836        for _ in 0..WINDOW - 1 {
1837            func.build(block, nop).finish();
1838        }
1839        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1840
1841        assert_eq!(combine(&mut func, &mut names), 1);
1842    }
1843
1844    /// The entry a frame layout is waiting on moves with the load. Without this the displacement
1845    /// of a local would be written into an instruction that has gone.
1846    #[test]
1847    fn the_frame_entry_of_a_load_that_moves_goes_with_it() {
1848        let (mut names, mut func, block) = empty();
1849        let base = Reg::physical(rucc_target::x86_64::RSP);
1850        let other = func.new_vreg(GPR);
1851        let word = load(&mut func, &mut names, block, base);
1852        let reader = func.insts(block).nth(1);
1853        assert!(reader.is_none(), "the block holds the load alone so far");
1854        alu(&mut func, &mut names, block, "add_rr_64", other, word);
1855        let held = func.insts(block).next().expect("the load");
1856
1857        let mut addresses = vec![(held, 3usize)];
1858        let mut arguments = Vec::new();
1859        let mut dynamic = Vec::new();
1860        let mut pending =
1861            Pending { addresses: &mut addresses, arguments: &mut arguments, dynamic: &mut dynamic };
1862        assert_eq!(loads(&mut func, &MACHINE, &mut names, &mut pending), 1);
1863
1864        let inst = func.insts(block).next().expect("the addition");
1865        assert_eq!(addresses, [(inst, 3usize)], "the entry names the instruction that took it");
1866    }
1867
1868    /// Every row of the table names instructions this target has, and names a load and an
1869    /// arithmetic whose widths agree. A row that got one of the three wrong would propose an
1870    /// instruction the change framework turns down, which is a fold that silently never happens.
1871    #[test]
1872    fn every_row_of_the_table_is_three_instructions_this_target_has() {
1873        for fold in FOLDS {
1874            assert!(MACHINE.has(fold.from), "{} is not an instruction", fold.from);
1875            assert!(MACHINE.has(fold.into), "{} is not an instruction", fold.into);
1876            assert!(MACHINE.has(fold.load), "{} is not an instruction", fold.load);
1877            let width = |name: &str| name.rsplit_once('_').map(|(_, width)| width.to_owned());
1878            assert_eq!(width(fold.from), width(fold.into), "{} changes width", fold.from);
1879            assert_eq!(width(fold.from), width(fold.load), "{} loads another width", fold.from);
1880            assert!((MACHINE.takes_mem)(fold.into), "{} reads no memory", fold.into);
1881            assert!(!(MACHINE.takes_mem)(fold.from), "{} already reads memory", fold.from);
1882        }
1883    }
1884
1885    /// One row per arithmetic instruction the target has that could take one. The count is here so
1886    /// that an instruction added to the target without a row shows up as a number rather than as a
1887    /// fold nobody noticed was missing.
1888    #[test]
1889    fn the_table_covers_the_arithmetic_this_target_has() {
1890        assert_eq!(FOLDS.len(), 23, "six operations at four widths, less the eight bit multiply");
1891        let commuting = FOLDS.iter().filter(|fold| fold.commutes).count();
1892        assert_eq!(commuting, 19, "everything but the four subtractions");
1893    }
1894}