Skip to main content

rucc_codegen/
layout.rs

1//! Putting the blocks in an order, and turning the edges between them into jumps.
2//!
3//! Design: `spec/10-backend.md` section 10.6.
4//!
5//! Up to here a function is a set of blocks and a set of edges, and nothing has said which block
6//! comes first in memory. A machine has no such thing: it runs the instruction after the one it
7//! just ran, so an order is not a presentation detail but the last piece of what the function
8//! means. This is what chooses one, and then writes the jumps that make the edges the order did
9//! not put next to each other still go where they went.
10//!
11//! # What the order is
12//!
13//! Two orders, and which one is used is what `-freorder-blocks` asks about.
14//!
15//! At `-O0`, reverse postorder over the CFG, with each block's successors walked in reverse, and
16//! anything unreachable put at the end in block order. That is the order `spec/10-backend.md`
17//! section 10.3 asks for, and it is not arbitrary. Walking the successors in reverse is what
18//! makes the first arm of a branch come out first, because a depth-first walk finishes its last
19//! child first and reverse postorder then puts that child last. So an `if` with no `else` falls
20//! through into its body, and a loop comes out as its header, its body and then whatever follows
21//! it, which is the shape where the back edge is the only jump in it.
22//!
23//! Above it, traces: the software trace cache construction of
24//! `spec/optimizer/38-scheduling-and-layout.md` section 38.4, which is `traces` below.
25//!
26//! Unreachable blocks are laid out rather than deleted. Deleting one is a decision about what the
27//! program does and this pass has no business making it, and a block nothing reaches costs the
28//! bytes it occupies and nothing else.
29//!
30//! # What a block looks like afterwards
31//!
32//! A block still holds where it goes, and it still holds every arm, which is what keeps the
33//! control flow graph readable after this has run. What changes is that the order the arms are in
34//! now means something it did not mean before:
35//!
36//! ```text
37//!   no arms      it returns
38//!   one arm      it falls into that block if that block is next, and jumps to it if not
39//!   two arms     a test and a conditional jump to the first, and the second is always next
40//! ```
41//!
42//! So a jump target is a block without an instruction growing a field for one.
43//! `rucc_mir::InstData` is twenty eight bytes by assertion and a block reference does not fit in
44//! it, and every pass over the graph already reads the arms, so putting the target where the
45//! graph already is costs nothing and keeps the two from disagreeing.
46//!
47//! Which arm is which is no longer which way the condition went, because a block that falls into
48//! the arm the condition is true for is a block whose jump has to be taken when it is false. That
49//! is what the two conditional jumps in [`BranchInsts`] are for, and it is why the arms may come
50//! out swapped: what the condition meant is in the opcode afterwards, and what the arms mean is
51//! where the jump goes and what comes next.
52//!
53//! # The one block none of that is true of
54//!
55//! A block that ends in the jump through a register, which is what a computed `goto` is selected
56//! as. Where it goes is in the register, so the arms are the whole list of places it might arrive
57//! at and there may be any number of them. Nothing is written here for such a block: the jump is
58//! already in it, none of its arms is fallen into and none is jumped to from here, and a jump
59//! written behind that one would be a jump nothing reaches. The arms stay on the block for the
60//! reason they stay on every other one, which is that the liveness and this pass both read them.
61//!
62//! # The block a branch sometimes needs
63//!
64//! A branch whose second arm cannot be laid out next, because both its arms are blocks the walk
65//! has already been to, would need two jumps in one block. Rather than write one, this makes the
66//! block it needs: an empty one on the second edge, laid out immediately after the branch, that
67//! jumps where the edge went. That is exactly the critical edge splitting in [`crate::split`],
68//! done for a different reason, and it costs the same jump the second jump would have cost while
69//! leaving every block with at most one.
70//!
71//! # The test a comparison makes unnecessary
72//!
73//! Almost every branch a C program writes is on a comparison, and a comparison has already set
74//! the flags by the time the byte it wrote is tested against itself. So where the instruction in
75//! front of the branch is that comparison, and the branch is the whole of what reads its byte,
76//! the byte and the test both go and the jump names the condition the comparison was asked about
77//! instead of naming zero. Three instructions become two, and the two are what the machine has a
78//! comparison and a conditional jump for.
79//!
80//! This is where it happens rather than anywhere earlier because of what the flags are. Between
81//! the comparison and the jump they are live and they are not a register: no pass could be told
82//! about them, so no pass may put an instruction between the two. After this one there is no pass
83//! left, which is the whole of the argument, and it is the same argument
84//! `rucc_target::x86_64::Form::CmpSet` is one form rather than two under.
85//!
86//! What this cannot work out for itself is whether the byte has another reader. Every register is
87//! physical by the time this runs and a physical register is written many times in a function, so
88//! the question has to be asked while they are still virtual and written once. [`fusable`] is that
89//! question, asked before allocation, and its answer is one of the arguments to [`blocks`]. The
90//! same arrangement, and for the same reason, as the addresses [`crate::finish`] has still to
91//! write and [`crate::fold`] is handed.
92//!
93//! # Why it runs last
94//!
95//! [`crate::finish`] finds the blocks a function returns from by looking for the ones that go
96//! nowhere. Nothing here creates one of those, but everything here reads and writes the arms, and
97//! a pass that reorders them is one nothing before it should be looking at. Running the layout
98//! after the prologue and the epilogue are in is also what makes the epilogue something it can
99//! lay out around rather than something it has to leave room for.
100
101use std::cmp::Reverse;
102use std::collections::BinaryHeap;
103
104use rucc_base::Interner;
105use rucc_base::hash::{Map, Set};
106use rucc_mir as mir;
107use rucc_target::{BranchInsts, Fusion, Role};
108
109/// The scale a weight is in, which is what a share of a block is worked out against.
110const SCALE: u128 = mir::Weight::SCALE as u128;
111
112/// Puts a function's blocks in an order and writes the jumps that order needs.
113///
114/// Run last, after [`crate::finish`].
115///
116/// # Panics
117///
118/// Panics on a block with more than two successors that does not end in the jump through a
119/// register, which is the only thing that lowers to one, and on a block with two whose last
120/// instruction is not the conditional branch the target named. Both are a function that was built
121/// wrongly somewhere earlier, and both are worth finding here rather than as a jump to the wrong
122/// place.
123pub fn blocks(
124    func: &mut mir::Func,
125    insts: &BranchInsts,
126    names: &mut Interner,
127    fusable: &Set<mir::Inst>,
128    reorder: bool,
129) {
130    let table = table(insts, names);
131    let mut order = if reorder { traces(func) } else { order(func) };
132    let mut writer = Writer { func, insts, names, table, fusable };
133    let mut at = 0;
134    while at < order.len() {
135        // A branch that can fall into neither arm asks for a block to put the second jump in, and
136        // that block goes immediately after it, which is where the loop reaches it next.
137        if let Some(bridge) = writer.edges(order[at], order.get(at + 1).copied()) {
138            order.insert(at + 1, bridge);
139        }
140        at += 1;
141    }
142    func.set_block_order(&order);
143}
144
145/// The heads of the loops, which are the blocks a jump inside a loop runs backwards to, in the order
146/// they are laid out.
147///
148/// Read off the layout rather than off a loop tree, because what the padding is for is where the
149/// jump lands and the layout is what says that. A loop the layout rotated has its test at the
150/// bottom and its body at the top, and the top of the body is the head here, since it is where the
151/// back edge goes every time round. A block that jumps to itself is its own head.
152///
153/// A jump that runs backwards is not always a loop. The trace can lay a cold arm out after the
154/// block it rejoins, and the jump back from it runs once. What makes it a loop is that the block
155/// it lands on can get back to the jump, which is both ends being on one cycle of the graph.
156///
157/// Never the first block. The front of a function is already on the boundary a function is given,
158/// and anything put between the function's name and its first instruction would be in the room a
159/// patcher was promised or ahead of the landing pad an indirect call has to find first.
160///
161/// Nor a loop that hardly runs. `spec/optimizer/38-scheduling-and-layout.md` section 38.5 takes
162/// gcc's `align-threshold`: padding is size, so it goes in front of a head that runs at least a
163/// hundredth as often as the hottest block of the function and nowhere else. A function with no
164/// weights has every block at the same one, and then every loop is hot enough.
165///
166/// Run after [`blocks`], and after anything else that adds or takes out a block.
167#[must_use]
168pub fn heads(func: &mir::Func) -> Vec<mir::Block> {
169    let mut at = vec![usize::MAX; func.block_count()];
170    for (place, block) in func.blocks().enumerate() {
171        at[block.index()] = place;
172    }
173    let piece = cycles(func);
174    let mut back = vec![false; func.block_count()];
175    for block in func.blocks() {
176        for succ in &func[block].succs {
177            let to = succ.block.index();
178            if at[to] <= at[block.index()] && piece[to] == piece[block.index()] {
179                back[to] = true;
180            }
181        }
182    }
183    let hottest = func.blocks().map(|block| func[block].weight.raw()).max().unwrap_or(0);
184    let floor = hottest / ALIGN_THRESHOLD;
185    func.blocks()
186        .skip(1)
187        .filter(|block| back[block.index()] && func[*block].weight.raw() >= floor)
188        .collect()
189}
190
191/// How many times less often than the hottest block a loop may run and still be padded, which is
192/// gcc's `align-threshold` (`gcc/params.opt:29`). See [`heads`].
193const ALIGN_THRESHOLD: u64 = 100;
194
195/// Which piece of the graph each block is in, indexed by the block's own number, where two blocks
196/// are in the same piece when each can reach the other.
197///
198/// Kosaraju's two walks, both with a stack of their own rather than recursion, for the reason
199/// [`order`] gives: the first down the edges to find the order the blocks finish in, and the second
200/// up them from the last to finish, where everything one walk reaches is one piece.
201fn cycles(func: &mir::Func) -> Vec<usize> {
202    let count = func.block_count();
203    let mut preds = vec![Vec::new(); count];
204    for block in func.blocks() {
205        for succ in &func[block].succs {
206            preds[succ.block.index()].push(block.index());
207        }
208    }
209    let mut finished = Vec::with_capacity(count);
210    let mut seen = vec![false; count];
211    for block in func.blocks() {
212        if std::mem::replace(&mut seen[block.index()], true) {
213            continue;
214        }
215        let mut stack = vec![(block, 0usize)];
216        while let Some((block, next)) = stack.pop() {
217            let Some(succ) = func[block].succs.get(next) else {
218                finished.push(block.index());
219                continue;
220            };
221            stack.push((block, next + 1));
222            if !std::mem::replace(&mut seen[succ.block.index()], true) {
223                stack.push((succ.block, 0));
224            }
225        }
226    }
227    let mut piece = vec![usize::MAX; count];
228    for (number, &root) in finished.iter().rev().enumerate() {
229        if piece[root] != usize::MAX {
230            continue;
231        }
232        piece[root] = number;
233        let mut stack = vec![root];
234        while let Some(block) = stack.pop() {
235            for &pred in &preds[block] {
236                if piece[pred] == usize::MAX {
237                    piece[pred] = number;
238                    stack.push(pred);
239                }
240            }
241        }
242    }
243    piece
244}
245
246/// The order the blocks are laid out in, which is every block the function has exactly once.
247fn order(func: &mir::Func) -> Vec<mir::Block> {
248    let mut order = Vec::with_capacity(func.block_count());
249    let mut seen = vec![false; func.block_count()];
250    if let Some(entry) = func.entry() {
251        seen[entry.index()] = true;
252        // The walk is explicit rather than recursive because a function with a hundred thousand
253        // blocks in it is a function somebody generated, and it should compile rather than run out
254        // of stack. Each entry is a block and how many of its arms have been started.
255        let mut stack = vec![(entry, 0usize)];
256        while let Some((block, next)) = stack.pop() {
257            let succs = &func[block].succs;
258            let Some(arm) = succs.len().checked_sub(next + 1) else {
259                order.push(block);
260                continue;
261            };
262            stack.push((block, next + 1));
263            let to = succs[arm].block;
264            if !std::mem::replace(&mut seen[to.index()], true) {
265                stack.push((to, 0));
266            }
267        }
268        order.reverse();
269    }
270    // Whatever the walk did not reach, in the order the blocks were made, which is the only order
271    // there is anything to be said for when nothing goes to any of them.
272    order.extend(func.blocks().filter(|block| !seen[block.index()]));
273    order
274}
275
276/// The rounds the traces are built in, each asking for less than the one before it.
277///
278/// Design: `spec/optimizer/38-scheduling-and-layout.md` section 38.4, which quotes
279/// `gcc/bb-reorder.cc:32` on why there is more than one round: a first round that only follows
280/// the arms almost always taken builds the trunk of the function, and the rounds below it pick up
281/// what is left without being able to break the trunk apart. It costs one more pass over the
282/// blocks per round and it is the difference between "stc" and "simple".
283///
284/// A round is a pair. The first number is how likely an arm has to be for the trace to follow it,
285/// in parts of [`mir::Weight::SCALE`], which is GCC's branch threshold. The second is how often
286/// the block at the end of that arm has to run, in the same parts of how often the function is
287/// entered, which is GCC's exec threshold. The last round asks for nothing, which is what makes
288/// every block end up somewhere.
289///
290/// The eight numbers are GCC's own, out of `branch_threshold` and `exec_threshold` in
291/// `gcc/bb-reorder.cc`, in ten thousandths where GCC writes thousandths. Two things about them
292/// are worth saying out loud because both were got wrong here first.
293///
294/// The branch threshold is low. Two fifths, not nine tenths: an arm taken half the time is an arm
295/// the first round follows, and since one arm of a two way branch always is, the first round walks
296/// straight through an unpredicted function the way a depth first walk would. A high threshold
297/// stops the trace at every branch nothing predicted, which is most of them, and hands both arms
298/// back to the seed list to be laid out by weight, and weight is exactly what has nothing to say
299/// about them.
300///
301/// The exec threshold is against the entry and not against the hottest block. A block that runs
302/// once per call is a block in the trunk of the function, and measuring it against a loop that
303/// runs twenty times a call makes the whole trunk cold: the preheader of every loop lands at the
304/// end of the function behind a jump, which is the opposite of what this is for.
305const ROUNDS: [(u64, u64); 4] = [(4_000, 5_000), (2_000, 2_000), (1_000, 500), (0, 0)];
306
307/// The order the blocks are laid out in above `-O0`, which is traces grown from the hottest
308/// blocks outwards.
309///
310/// Design: `spec/optimizer/38-scheduling-and-layout.md` section 38.4.
311///
312/// A trace is a run of blocks that control is expected to walk straight through. It is grown from
313/// a seed by repeatedly taking the arm most likely to be the one taken, stopping when no arm is
314/// likely enough for the round or when the likeliest one leads somewhere the layout has already
315/// been. Every block is a seed in some round, the hotter ones first, and the traces come out in
316/// the order they were grown. So the function's trunk is laid out first and contiguously, its
317/// error paths end up behind it, and the branch that leaves the trunk is the one that costs a
318/// jump.
319///
320/// The entry is the first seed whatever its weight, because on this machine a function is entered
321/// at its first byte and the block laid out first is the block that runs first. A hotter block
322/// inside a loop would otherwise take the seat.
323///
324/// The traces are then run together by [`connect`], which is what keeps a run of blocks the rounds
325/// cut in half from coming out in two places.
326///
327/// # Which block the next trace starts at
328///
329/// Not simply the hottest one left. A block something already laid out goes to comes first, and
330/// among those the one with the hottest edge into it, which is [`Seed`] and which is GCC's
331/// `bb_to_key` in `gcc/bb-reorder.cc`. The reason is the whole of what a layout costs: a block laid
332/// out in front of everything that reaches it pays a jump on every one of those paths and saves
333/// nothing, and a block laid out behind the trace that reaches it pays nothing on the path that
334/// falls into it. Seeding by weight alone gets this wrong on the commonest shape in C, which is two
335/// arms that both end at one block: the block both arms join at is the hottest of the three and
336/// goes first, and then both arms jump to it.
337///
338/// # Loop rotation, and where it comes from
339///
340/// Section 38.4 asks for the loop to be rotated so that its exit is the last block of the trace,
341/// and there is no step here that does it. It falls out of the walk instead: a trace that enters
342/// a loop header follows the body, reaches the latch, finds that the latch's likeliest arm is the
343/// header it has already laid out, and stops. The exit is then a seed of its own and comes next.
344/// That is the rotated order, back edge running backwards and exit falling through, arrived at
345/// from the greedy rule rather than from a rule about loops.
346///
347/// What that does not cover is a loop whose header is its exit test and whose body is cold, where
348/// GCC would duplicate the header. Section 38.4 says the first version should not copy code and
349/// this does not.
350fn traces(func: &mir::Func) -> Vec<mir::Block> {
351    // Where the shape of the graph would have put each block, which is what decides between two
352    // blocks that run equally often. Most branches in most functions have nothing to predict them
353    // by and come out even, so without this the seed order between them would be the order the
354    // blocks happen to have been made in, and a block that falls into the one after it under
355    // [`order`] would be laid out somewhere else for no reason and pay a jump for it.
356    let mut place = vec![usize::MAX; func.block_count()];
357    for (at, &block) in order(func).iter().enumerate() {
358        place[block.index()] = at;
359    }
360
361    let mut found: Vec<Vec<mir::Block>> = Vec::new();
362    let mut seen = vec![false; func.block_count()];
363    // How often the function is entered, which every exec threshold is a share of. A function
364    // whose entry says nothing is one nobody wrote a weight on, and then once is the right answer
365    // for every block in it and every round behaves the same.
366    let entered = func.entry().map_or(mir::Weight::ONCE, |entry| func[entry].weight).raw();
367    // The hottest edge into each block out of a block already laid out, which is what the queue is
368    // ordered by and what says whether an entry popped off it is out of date. It outlives the
369    // round it was written in on purpose: a trace that stops because the next block is below this
370    // round's exec threshold leaves that block remembered as reached, and the round that does take
371    // it starts its first trace there rather than wherever the weights happen to point. That is
372    // how a chain of comparisons whose tail cools off below the threshold stays a straight line.
373    let mut reached = vec![0; func.block_count()];
374
375    for (likely, often) in ROUNDS {
376        // The exec threshold as a number rather than a fraction. In a hundred and twenty eight
377        // bits because a weight saturates at the top of a sixty four bit one and a nest of loops
378        // gets there.
379        let floor =
380            u64::try_from(u128::from(entered) * u128::from(often) / SCALE).unwrap_or(u64::MAX);
381        // A round does not start a trace in a block colder than its exec threshold, which is what
382        // keeps an error path out of the middle of the trunk: it waits for a round that asks for
383        // less. The entry is the exception below, because the block laid out first is the block
384        // that runs first and that has to be the entry whatever it weighs.
385        let mut queue: BinaryHeap<Seed> = func
386            .blocks()
387            .filter(|&block| !seen[block.index()] && func[block].weight.raw() >= floor)
388            .map(|block| Seed {
389                reached: reached[block.index()],
390                weight: func[block].weight,
391                place: Reverse(place[block.index()]),
392                block,
393            })
394            .collect();
395        let mut start = func.entry().filter(|entry| !seen[entry.index()]);
396
397        while let Some(from) = start.take().or_else(|| next_seed(&mut queue, &seen, &reached)) {
398            let mut trace = Vec::new();
399            let mut block = from;
400            loop {
401                seen[block.index()] = true;
402                trace.push(block);
403                let next = along(func, block, &seen, likely, floor);
404                // Everything this block goes to and the trace does not, so that the next trace can
405                // start at one of them rather than wherever the weights point. A block too cold
406                // for this round is still written down as reached, because the round that is cold
407                // enough to take it wants to know it hangs off something already laid out.
408                for call in &func[block].succs {
409                    let to = call.block;
410                    if seen[to.index()]
411                        || Some(to) == next
412                        || call.weight.raw() <= reached[to.index()]
413                    {
414                        continue;
415                    }
416                    reached[to.index()] = call.weight.raw();
417                    if func[to].weight.raw() >= floor {
418                        queue.push(Seed {
419                            reached: call.weight.raw(),
420                            weight: func[to].weight,
421                            place: Reverse(place[to.index()]),
422                            block: to,
423                        });
424                    }
425                }
426                let Some(next) = next else { break };
427                block = next;
428            }
429            found.push(trace);
430        }
431    }
432    connect(func, found)
433}
434
435/// The traces run together into one order, each one followed where possible by the trace control
436/// leaves it for.
437///
438/// Design: `gcc/bb-reorder.cc`, `connect_traces`.
439///
440/// The rounds cut a straight run of blocks into pieces whenever the run cools below the round's
441/// exec threshold, and a chain of comparisons against a constant is exactly that: each comparison
442/// is reached only when every one before it failed, so the chain halves in weight at every step and
443/// the round that laid the head of it down will not touch the tail. Left alone, the pieces come out
444/// in round order with other traces between them, and every piece pays a jump to reach the next.
445///
446/// So the pieces are put back together. Each trace is followed by the unplaced trace its last block
447/// most often goes to, and that one by the trace its last block most often goes to, until there is
448/// none, and only then does the next trace in round order start a new run. The rounds still decide
449/// which trace is hot and comes first, and this decides what falls in behind it.
450fn connect(func: &mir::Func, traces: Vec<Vec<mir::Block>>) -> Vec<mir::Block> {
451    // Which trace each block starts, for the blocks that start one. A trace may only be joined at
452    // its first block, because joining it anywhere else would mean cutting it in half and the
453    // rounds put it together for a reason.
454    let mut head = vec![usize::MAX; func.block_count()];
455    for (at, trace) in traces.iter().enumerate() {
456        if let Some(&first) = trace.first() {
457            head[first.index()] = at;
458        }
459    }
460
461    let mut order = Vec::with_capacity(func.block_count());
462    let mut used = vec![false; traces.len()];
463    for from in 0..traces.len() {
464        if used[from] {
465            continue;
466        }
467        let mut at = from;
468        loop {
469            used[at] = true;
470            order.extend_from_slice(&traces[at]);
471            let Some(&last) = traces[at].last() else { break };
472            let mut best: Option<(u64, usize)> = None;
473            for call in &func[last].succs {
474                let to = head[call.block.index()];
475                if to == usize::MAX || used[to] {
476                    continue;
477                }
478                let weight = call.weight.raw();
479                // Ties go to the trace found first, which is the hotter of the two, because the
480                // rounds laid the traces down hottest first.
481                if best.is_none_or(|(found, over)| weight > found || (weight == found && to < over))
482                {
483                    best = Some((weight, to));
484                }
485            }
486            let Some((_, next)) = best else { break };
487            at = next;
488        }
489    }
490    order
491}
492
493/// A block a trace could start at, ordered so that the greatest is the one to start at next.
494///
495/// Design: `gcc/bb-reorder.cc`, `bb_to_key`, of which this is the same three answers in the order
496/// GCC asks them.
497#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
498struct Seed {
499    /// How often the hottest edge into this block out of a block already laid out is taken, and
500    /// zero while nothing laid out goes here. First, so that a block something reaches beats a
501    /// block nothing reaches however hot the second one is.
502    reached: u64,
503    /// How often the block runs, which decides between two blocks nothing laid out reaches.
504    weight: mir::Weight,
505    /// Where reverse postorder would have put it, which decides between two blocks that are equal
506    /// on both of the above, so that a function with no weights on it comes out in the order the
507    /// shape of its graph gives rather than in whatever order the queue settles.
508    place: Reverse<usize>,
509    /// The block, last, so that two blocks equal on everything else still come out in one order.
510    block: mir::Block,
511}
512
513/// The next block to start a trace at, out of the queue, or nothing when there is none left.
514///
515/// An entry whose block has been laid out since it was queued, or which was queued before a hotter
516/// edge into the same block was found, is thrown away here rather than found and updated in place
517/// when that happens. The queue is a heap and an entry in the middle of one cannot be reached, so
518/// the choice is between this and an index beside it, and a stale entry costs one pop.
519fn next_seed(queue: &mut BinaryHeap<Seed>, seen: &[bool], reached: &[u64]) -> Option<mir::Block> {
520    while let Some(seed) = queue.pop() {
521        if !seen[seed.block.index()] && seed.reached >= reached[seed.block.index()] {
522            return Some(seed.block);
523        }
524    }
525    None
526}
527
528/// The arm the trace follows out of a block, or nothing when no arm is worth following.
529///
530/// The likeliest arm that has not been laid out already, is taken at least as often as the
531/// round's floor, and takes at least the round's share of the times the block runs. Ties go to
532/// the arm written first, which is the arm a conditional branch takes when its condition holds,
533/// so a function with no weights on it at all comes out following the true arm.
534fn along(
535    func: &mir::Func,
536    block: mir::Block,
537    seen: &[bool],
538    likely: u64,
539    floor: u64,
540) -> Option<mir::Block> {
541    let whole = func[block].weight;
542    let mut best: Option<&mir::BlockCall> = None;
543    for call in &func[block].succs {
544        if seen[call.block.index()]
545            || call.weight.raw() < floor
546            || call.weight.out_of(whole) < likely
547        {
548            continue;
549        }
550        if best.is_none_or(|found| call.weight > found.weight) {
551            best = Some(call);
552        }
553    }
554    best.map(|call| call.block)
555}
556
557/// The comparisons a branch may be folded into, which [`blocks`] can then find by opcode.
558///
559/// One entry per name the target's table holds, interned once for the function rather than once
560/// per block, since a block that ends in a branch is most of the blocks there are.
561fn table(insts: &BranchInsts, names: &mut Interner) -> Map<mir::Opcode, &'static Fusion> {
562    insts
563        .fused
564        .iter()
565        .map(|fusion| {
566            (mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, fusion.set))), fusion)
567        })
568        .collect()
569}
570
571/// The comparisons a branch on their answer is the whole of what reads, which [`blocks`] may fold
572/// the test out of.
573///
574/// Run before allocation, on the same function [`blocks`] is later given. What it answers is
575/// whether anything but the branch reads the byte a comparison wrote, and that is a question about
576/// a virtual register: a physical one is written many times in a function and counting its readers
577/// would mean asking which of the writes each reader belongs to. So it is asked here, where a
578/// register is written once, and the answer is carried to the pass that can use it.
579///
580/// Being on this list is necessary and not sufficient. Allocation may put a reload between the
581/// comparison and the branch, and a comparison that is no longer the instruction in front of the
582/// branch is not one the flags survive to, so [`blocks`] checks that again on what it finds.
583#[must_use]
584pub fn fusable(func: &mir::Func, insts: &BranchInsts, names: &mut Interner) -> Set<mir::Inst> {
585    let table = table(insts, names);
586    let branch = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.cond)));
587    let reads = crate::changes::Reads::of(func);
588    let mut found = Set::default();
589    for block in func.blocks() {
590        let insts: Vec<mir::Inst> = func.insts(block).collect();
591        let [.., compare, last] = insts[..] else { continue };
592        if func[last].opcode != branch || !table.contains_key(&func[compare].opcode) {
593            continue;
594        }
595        let operands = &func[func[compare].operands];
596        let Some(byte) = operands.first().filter(|operand| operand.role != Role::Use) else {
597            continue;
598        };
599        if !byte.reg.is_virtual() || reads.count(byte.reg) != 1 {
600            continue;
601        }
602        // And it is this branch that reads it rather than one in some other block, which the
603        // count alone does not say.
604        if func[func[last].operands].first().map(|operand| operand.reg) == Some(byte.reg) {
605            found.insert(compare);
606        }
607    }
608    found
609}
610
611/// The one thing that writes an instruction here, over the function it writes into.
612struct Writer<'a> {
613    func: &'a mut mir::Func,
614    insts: &'a BranchInsts,
615    names: &'a mut Interner,
616    table: Map<mir::Opcode, &'static Fusion>,
617    fusable: &'a Set<mir::Inst>,
618}
619
620impl Writer<'_> {
621    /// Writes the jumps one block needs, given the block laid out after it, and gives back the
622    /// block that has to go between the two when the branch needed one.
623    fn edges(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
624        // A block that already ends in the jump through a register wants nothing written, whatever
625        // its arms are. Where it goes is in the register, so none of its arms is fallen into and
626        // none of them is jumped to from here, and a jump written behind that one would be a jump
627        // nothing reaches.
628        if self.leaves_indirectly(block) {
629            return None;
630        }
631        match self.func[block].succs.len() {
632            0 => None,
633            1 => {
634                self.one(block, next);
635                None
636            }
637            2 => self.two(block, next),
638            arms => panic!("a block with {arms} arms, and nothing lowers to one"),
639        }
640    }
641
642    /// Whether the block ends in the jump through a register a computed `goto` is selected as.
643    fn leaves_indirectly(&mut self, block: mir::Block) -> bool {
644        let Some(last) = self.func.terminator(block) else { return false };
645        let indirect = self.opcode(self.insts.indirect);
646        self.func[last].opcode == indirect
647    }
648
649    /// Whether the block already ends in a jump on the condition state, which an `asm` template
650    /// wrote and this pass did not.
651    fn jumps_already(&mut self, block: mir::Block) -> bool {
652        let Some(last) = self.func.terminator(block) else { return false };
653        let opcode = self.func[last].opcode;
654        let conditional = self.insts.conditional;
655        conditional.iter().any(|name| self.opcode(name) == opcode)
656    }
657
658    /// A block that goes to one place, which either follows it or has to be jumped to.
659    fn one(&mut self, block: mir::Block, next: Option<mir::Block>) {
660        if Some(self.func[block].succs[0].block) == next {
661            return;
662        }
663        let opcode = self.opcode(self.insts.jump);
664        self.func.build(block, opcode).finish();
665    }
666
667    /// A block that goes to two places, which is a test and a jump to one of them.
668    ///
669    /// The condition is read off the branch the rules selected and the branch is taken out, so the
670    /// register the test reads is the one the branch read and no new value is made. That is what
671    /// makes this safe to run after allocation: it writes no register that was not already
672    /// written and it asks for none that was not already asked for.
673    fn two(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
674        // A block whose jump is already there, which is one an `asm` template wrote itself. Its
675        // arms are in the order the jump means, so all that is left is the block the second arm
676        // needs when it is not the one laid out next.
677        if self.jumps_already(block) {
678            let second = self.func[block].succs[1].block;
679            return (next != Some(second)).then(|| self.bridge(block));
680        }
681
682        // Asked before the branch is taken out, because what it looks at is the instruction in
683        // front of the branch and taking the branch out would make that the last one.
684        let fused = self.fused(block);
685        let condition = self.take(block);
686
687        // Whichever arm is laid out next is the one the block falls into, and the jump is then
688        // the one taken when the condition sends it the other way. Falling into the arm the
689        // condition is false for leaves the jump taken when it holds, and falling into the arm it
690        // is true for leaves the other jump and the arms the other way round.
691        let (if_true, if_false) = match fused {
692            Some((_, fusion)) => (fusion.if_true, fusion.if_false),
693            None => (self.insts.if_true, self.insts.if_false),
694        };
695        let arms: Vec<mir::Block> = self.func[block].succs.iter().map(|arm| arm.block).collect();
696        let (name, bridge) = if next == Some(arms[1]) {
697            (if_true, None)
698        } else if next == Some(arms[0]) {
699            self.func.succs_mut(block).swap(0, 1);
700            (if_false, None)
701        } else {
702            (if_true, Some(self.bridge(block)))
703        };
704
705        match fused {
706            Some((compare, fusion)) => self.keep_only_the_flags(compare, fusion),
707            None => {
708                let opcode = self.opcode(self.insts.test);
709                self.func.build(block, opcode).operand(condition).finish();
710            }
711        }
712        let opcode = self.opcode(name);
713        self.func.build(block, opcode).finish();
714        bridge
715    }
716
717    /// The comparison the block's branch can be folded into, when there is one.
718    ///
719    /// Three things have to hold and [`fusable`] has already answered the one that cannot be
720    /// answered here. What is left is that the comparison is still the instruction in front of the
721    /// branch, since allocation may have put a reload between them and the flags do not survive
722    /// one, and that the byte the branch reads is the byte that comparison wrote, since the
723    /// allocator has since given both of them a physical register and two registers that were
724    /// different could have become the same one.
725    fn fused(&self, block: mir::Block) -> Option<(mir::Inst, &'static Fusion)> {
726        let insts: Vec<mir::Inst> = self.func.insts(block).collect();
727        let [.., compare, last] = insts[..] else { return None };
728        if !self.fusable.contains(&compare) {
729            return None;
730        }
731        let fusion = *self.table.get(&self.func[compare].opcode)?;
732        let byte = self.func[self.func[compare].operands].first()?.reg;
733        (self.func[self.func[last].operands].first()?.reg == byte).then_some((compare, fusion))
734    }
735
736    /// Turns a comparison that wrote a byte into the same comparison that writes nothing.
737    ///
738    /// The instruction stays where it is and keeps its immediate, which is the point: what it does
739    /// to the flags is what it already did, and the jump written behind it reads those. Only the
740    /// operand at the front goes, which is the byte, and the opcode changes to the one that has no
741    /// operand there.
742    ///
743    /// An addressing mode comes with the rest of it and does not survive the move on its own. What
744    /// a mode holds is where in the operand vector its base and its index are, and every operand
745    /// has just come down one place, so the two positions come down with them. A comparison
746    /// against a register or a constant has no mode and nothing to do here, and a comparison
747    /// against memory is the one that does.
748    fn keep_only_the_flags(&mut self, compare: mir::Inst, fusion: &Fusion) {
749        let read: Vec<mir::Operand> =
750            self.func[self.func[compare].operands].iter().skip(1).copied().collect();
751        let operands = self.func.push_operands(&read);
752        self.func[compare].opcode = self.opcode(fusion.cmp);
753        self.func[compare].operands = operands;
754        if let Some(at) = self.func[compare].mem {
755            let mut amode = self.func[at];
756            amode.base = amode.base.map(|position| position - 1);
757            amode.index = amode.index.map(|position| position - 1);
758            self.func[compare].mem = Some(self.func.add_amode(amode));
759        }
760    }
761
762    /// Takes the conditional branch off the end of a block and gives back what it read.
763    fn take(&mut self, block: mir::Block) -> mir::Operand {
764        let branch = self.func.terminator(block).expect("a block with two arms has a branch");
765        let cond = self.opcode(self.insts.cond);
766        assert_eq!(
767            self.func[branch].opcode, cond,
768            "a block with two arms whose last instruction is not the branch"
769        );
770        let operands = self.func[branch].operands;
771        let condition = self.func[operands][0];
772        self.func.remove_inst(branch);
773        condition
774    }
775
776    /// Puts an empty block on a branch's second edge, so that the branch has something to fall
777    /// into and the jump the edge really needs is in a block of its own.
778    fn bridge(&mut self, block: mir::Block) -> mir::Block {
779        let bridge = self.func.create_block();
780        let edge = self.func[block].succs[1].clone();
781        let weight = edge.weight;
782        self.func.set_weight(bridge, weight);
783        *self.func.succs_mut(bridge) = vec![edge];
784        self.func.succs_mut(block)[1] = mir::BlockCall::to(bridge).taken(weight);
785        bridge
786    }
787
788    /// The opcode of that name on this target, which is the name with the target's prefix in
789    /// front of it.
790    fn opcode(&mut self, name: &str) -> mir::Opcode {
791        mir::Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
792    }
793}
794
795#[cfg(test)]
796mod tests {
797    use rucc_mir::{BlockCall, Mem, Opcode, Operand, Reg};
798    use rucc_target::x86_64::{BRANCH, GPR, RAX, RCX, REGS};
799
800    use super::*;
801
802    /// A function with that many blocks, none of which goes anywhere yet.
803    fn blank(count: usize) -> (Interner, mir::Func, Vec<mir::Block>) {
804        let mut names = Interner::new();
805        let mut func = mir::Func::new(names.intern("f"));
806        let blocks = (0..count).map(|_| func.create_block()).collect();
807        (names, func, blocks)
808    }
809
810    /// Puts a conditional branch at the end of a block, on a register that is already physical
811    /// the way one is by the time this pass runs.
812    fn branch(func: &mut mir::Func, names: &mut Interner, block: mir::Block, arms: &[mir::Block]) {
813        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
814        func.build(block, opcode).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
815        *func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
816    }
817
818    /// Laying the blocks out for the one machine this crate has, and the dump of what came out.
819    ///
820    /// The dump rather than the function, because where a jump goes is on the block and the dump
821    /// is the one place the instruction and the arm are put back together. A test that read the
822    /// two separately would pass on a function whose jump and whose edge disagreed, which is the
823    /// mistake this pass is most able to make.
824    ///
825    /// A block is named in the dump by where it is in the layout rather than by the number it was
826    /// made with, which is why every expectation below reads that way and why the order is worth
827    /// asserting on its own.
828    fn laid_out(func: &mut mir::Func, names: &mut Interner) -> Vec<String> {
829        // Both halves, in the order the pipeline runs them, so that a test which builds a
830        // comparison in front of its branch sees what a compiled function would see.
831        let fusable = fusable(func, &BRANCH, names);
832        blocks(func, &BRANCH, names, &fusable, false);
833        mir::print_func(func, names, &REGS)
834            .lines()
835            .filter(|line| !line.trim().is_empty() && !line.starts_with("mfunc") && *line != "}")
836            .map(|line| line.trim().to_string())
837            .collect()
838    }
839
840    /// The blocks in layout order, by the number each was made with.
841    fn order_of(func: &mir::Func) -> Vec<usize> {
842        func.blocks().map(mir::Block::index).collect()
843    }
844
845    #[test]
846    fn a_block_that_falls_into_the_next_one_gets_no_jump_at_all() {
847        let (mut names, mut func, made) = blank(2);
848        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
849
850        let text = laid_out(&mut func, &mut names);
851
852        // The arm is still on the block, because the graph is still worth reading, and there is
853        // no instruction on it because the block it goes to is the one that runs next anyway.
854        assert_eq!(text, ["block0:", "block1", "block1:"]);
855    }
856
857    #[test]
858    fn a_block_that_goes_somewhere_that_is_not_next_gets_a_jump() {
859        let (mut names, mut func, made) = blank(2);
860        // A loop with nothing in it and no way out, which is the smallest function there is with
861        // an edge that runs backwards. Every layout puts the two blocks in this order, so the
862        // second one has nothing after it and its edge has to be a jump.
863        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
864        *func.succs_mut(made[1]) = vec![BlockCall::to(made[0])];
865
866        let text = laid_out(&mut func, &mut names);
867
868        assert_eq!(text, ["block0:", "block1", "block1:", "x64.jmp block0"]);
869    }
870
871    #[test]
872    fn a_branch_that_falls_into_its_false_arm_jumps_when_the_condition_holds() {
873        let (mut names, mut func, made) = blank(3);
874        // A loop whose body is the block it came from: the arm taken when the condition holds is
875        // a block the walk has already been to, so the other arm is what comes next.
876        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
877        branch(&mut func, &mut names, made[1], &[made[0], made[2]]);
878
879        let text = laid_out(&mut func, &mut names);
880
881        assert_eq!(order_of(&func), [0, 1, 2]);
882        assert_eq!(
883            text,
884            [
885                "block0:",
886                "block1",
887                "block1:",
888                "x64.test_rr_8 $rax",
889                "x64.jcc_ne block0, block2",
890                "block2:",
891            ]
892        );
893    }
894
895    #[test]
896    fn a_branch_that_falls_into_its_true_arm_jumps_when_the_condition_does_not_hold() {
897        let (mut names, mut func, made) = blank(3);
898        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
899
900        let text = laid_out(&mut func, &mut names);
901
902        // The arms come out swapped, because after this the first is where the jump goes and the
903        // second is what runs next, and the jump is the one taken when the condition failed.
904        assert_eq!(order_of(&func), [0, 1, 2]);
905        assert_eq!(
906            text,
907            ["block0:", "x64.test_rr_8 $rax", "x64.jcc_e block2, block1", "block1:", "block2:"]
908        );
909    }
910
911    #[test]
912    fn a_block_that_leaves_through_a_register_is_given_no_jump_and_keeps_every_arm() {
913        let (mut names, mut func, made) = blank(4);
914        let jump = Opcode::new(names.intern("x64.jmp_reg"));
915        func.build(made[0], jump).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
916        *func.succs_mut(made[0]) = made[1..].iter().map(|&arm| BlockCall::to(arm)).collect();
917
918        let text = laid_out(&mut func, &mut names);
919
920        // Nothing written behind the jump that is already there, whatever the first arm is, since
921        // where this block goes is in the register. The arms stay on the block because they are
922        // how everything downstream finds out where control can go.
923        assert_eq!(
924            text,
925            [
926                "block0:",
927                "x64.jmp_reg $rax, block1, block2, block3",
928                "block1:",
929                "block2:",
930                "block3:"
931            ]
932        );
933    }
934
935    #[test]
936    fn a_branch_that_can_fall_into_neither_arm_is_given_a_block_to_jump_from() {
937        let (mut names, mut func, made) = blank(2);
938        // A loop that goes back to the top or round again, so both arms are blocks the walk has
939        // already been to and nothing is left to lay out after it.
940        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
941        branch(&mut func, &mut names, made[1], &[made[0], made[1]]);
942
943        let text = laid_out(&mut func, &mut names);
944
945        // Block two is the one this made. It is empty, it is laid out where the branch falls into
946        // it, and the jump the second arm needed is in it rather than being a second jump in the
947        // block above.
948        assert_eq!(order_of(&func), [0, 1, 2]);
949        assert_eq!(
950            text,
951            [
952                "block0:",
953                "block1",
954                "block1:",
955                "x64.test_rr_8 $rax",
956                "x64.jcc_ne block0, block2",
957                "block2:",
958                "x64.jmp block1",
959            ]
960        );
961    }
962
963    #[test]
964    fn the_test_reads_the_register_the_branch_read() {
965        let (mut names, mut func, made) = blank(3);
966        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
967
968        let fusable = fusable(&func, &BRANCH, &mut names);
969        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
970
971        let test = func.insts(made[0]).next().expect("a test");
972        let operands = func[test].operands;
973        assert_eq!(func[operands], [Operand::read(Reg::physical(RAX), GPR)]);
974    }
975
976    #[test]
977    fn a_block_nothing_reaches_is_laid_out_at_the_end_rather_than_deleted() {
978        let (mut names, mut func, made) = blank(4);
979        *func.succs_mut(made[0]) = vec![BlockCall::to(made[3])];
980
981        let fusable = fusable(&func, &BRANCH, &mut names);
982        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
983
984        // Blocks one and two are reached by nothing, so they go last, in the order they were
985        // made. Deleting one would be a decision about what the program does, and this pass has
986        // no business making it.
987        assert_eq!(order_of(&func), [0, 3, 1, 2]);
988    }
989
990    #[test]
991    fn a_function_with_no_blocks_is_left_alone() {
992        let mut names = Interner::new();
993        let mut func = mir::Func::new(names.intern("f"));
994
995        let fusable = fusable(&func, &BRANCH, &mut names);
996        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
997
998        assert_eq!(func.block_count(), 0);
999    }
1000
1001    #[test]
1002    #[should_panic(expected = "a block with 3 arms")]
1003    fn a_block_with_three_arms_is_refused_rather_than_laid_out_wrongly() {
1004        let (mut names, mut func, made) = blank(4);
1005        branch(&mut func, &mut names, made[0], &[made[1], made[2], made[3]]);
1006
1007        let fusable = fusable(&func, &BRANCH, &mut names);
1008        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
1009    }
1010
1011    #[test]
1012    #[should_panic(expected = "whose last instruction is not the branch")]
1013    fn a_block_with_two_arms_and_no_branch_in_it_is_refused() {
1014        let (mut names, mut func, made) = blank(3);
1015        let opcode = Opcode::new(names.intern("x64.nop"));
1016        func.build(made[0], opcode).finish();
1017        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1]), BlockCall::to(made[2])];
1018
1019        let fusable = fusable(&func, &BRANCH, &mut names);
1020        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
1021    }
1022
1023    /// Puts a comparison and a branch on its answer at the end of a block.
1024    ///
1025    /// The byte is a virtual register, which is what it is when [`fusable`] is asked and is not
1026    /// what it is when [`blocks`] runs. Nothing in either half cares which it is except the
1027    /// counting, so a test that runs both over one function has to use the register the counting
1028    /// wants, and what it costs is that this is one thing the unit tests cannot check about the
1029    /// two halves running at different times. `crate::pipeline` runs them the real way round.
1030    fn compare(
1031        func: &mut mir::Func,
1032        names: &mut Interner,
1033        block: mir::Block,
1034        arms: &[mir::Block],
1035    ) -> Reg {
1036        let byte = func.new_vreg(GPR);
1037        let opcode = Opcode::new(names.intern("x64.cmp_set_l_32"));
1038        func.build(block, opcode)
1039            .def(byte, GPR)
1040            .operand(Operand::read(Reg::physical(RAX), GPR))
1041            .operand(Operand::read(Reg::physical(RCX), GPR))
1042            .finish();
1043        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
1044        func.build(block, opcode).operand(Operand::read(byte, GPR)).finish();
1045        *func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
1046        byte
1047    }
1048
1049    /// A branch on a comparison is the comparison and a jump on what it found.
1050    ///
1051    /// Three instructions go in and two come out. The byte goes because nothing reads it, the test
1052    /// goes because the comparison set the flags the test was going to set, and the jump names the
1053    /// condition rather than naming zero. Which condition it names is the opposite of the one the
1054    /// comparison asked about, since the block falls into the arm the comparison is true for.
1055    #[test]
1056    fn a_branch_on_a_comparison_is_the_comparison_and_a_jump_on_what_it_found() {
1057        let (mut names, mut func, made) = blank(3);
1058        compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
1059
1060        let text = laid_out(&mut func, &mut names);
1061
1062        assert_eq!(
1063            text,
1064            [
1065                "block0:",
1066                "x64.cmp_rr_32 $rax, $rcx",
1067                "x64.jcc_ge block2, block1",
1068                "block1:",
1069                "block2:",
1070            ]
1071        );
1072    }
1073
1074    /// The same thing for a comparison that reads memory, where the address has to come down with
1075    /// the operands.
1076    ///
1077    /// What an addressing mode holds is where its base register is in the operand vector, and
1078    /// taking the byte off the front moves every operand one place. A mode left pointing at where
1079    /// the base used to be would name the operand in front of it, which here is the value being
1080    /// compared, so the instruction would read an address it was never given. The count of the
1081    /// operands is checked as well as the position, since a mode that points past the end is the
1082    /// other way this goes wrong.
1083    #[test]
1084    fn a_folded_comparison_keeps_its_address_when_the_byte_comes_off_the_front() {
1085        let (mut names, mut func, made) = blank(3);
1086        let byte = func.new_vreg(GPR);
1087        let opcode = Opcode::new(names.intern("x64.cmp_set_l_rm_32"));
1088        func.build(made[0], opcode)
1089            .def(byte, GPR)
1090            .operand(Operand::read(Reg::physical(RAX), GPR))
1091            .mem(Mem { disp: 24, ..Mem::at(Operand::read(Reg::physical(RCX), GPR)) })
1092            .finish();
1093        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
1094        func.build(made[0], opcode).operand(Operand::read(byte, GPR)).finish();
1095        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1]), BlockCall::to(made[2])];
1096
1097        let text = laid_out(&mut func, &mut names);
1098
1099        assert_eq!(
1100            text,
1101            [
1102                "block0:",
1103                "x64.cmp_rm_32 $rax, [$rcx + 24]",
1104                "x64.jcc_ge block2, block1",
1105                "block1:",
1106                "block2:",
1107            ]
1108        );
1109        let compare = func.insts(made[0]).next().expect("the comparison");
1110        let mem = func[compare].mem.expect("it reads memory");
1111        assert_eq!(func[mem].base, Some(1), "the base came down with the operands");
1112        assert_eq!(func[func[compare].operands].len(), 2, "the value and the base of the address");
1113    }
1114
1115    /// The same thing again for a comparison of memory against a constant, which is the shape with
1116    /// the fewest operands there is.
1117    ///
1118    /// The byte is the only operand in front of the address here, so taking it off leaves the base
1119    /// at the very front and the instruction reading nothing but the address it was given. A mode
1120    /// that had not come down would be pointing one past the end of a vector with a single operand
1121    /// in it, which is the way this goes wrong on the narrowest shape rather than on the widest.
1122    #[test]
1123    fn a_comparison_of_memory_against_a_constant_keeps_its_address_when_the_byte_comes_off() {
1124        let (mut names, mut func, made) = blank(3);
1125        let byte = func.new_vreg(GPR);
1126        let opcode = Opcode::new(names.intern("x64.cmp_set_l_mi_32"));
1127        func.build(made[0], opcode)
1128            .def(byte, GPR)
1129            .mem(Mem { disp: 24, ..Mem::at(Operand::read(Reg::physical(RCX), GPR)) })
1130            .imm(7)
1131            .finish();
1132        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
1133        func.build(made[0], opcode).operand(Operand::read(byte, GPR)).finish();
1134        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1]), BlockCall::to(made[2])];
1135
1136        let text = laid_out(&mut func, &mut names);
1137
1138        assert_eq!(
1139            text,
1140            [
1141                "block0:",
1142                "x64.cmp_mi_32 [$rcx + 24], 7",
1143                "x64.jcc_ge block2, block1",
1144                "block1:",
1145                "block2:",
1146            ]
1147        );
1148        let compare = func.insts(made[0]).next().expect("the comparison");
1149        let mem = func[compare].mem.expect("it reads memory");
1150        assert_eq!(func[mem].base, Some(0), "the base came down to the front");
1151        assert_eq!(func[func[compare].operands].len(), 1, "the base of the address on its own");
1152    }
1153
1154    /// The same comparison with something else reading its answer, which keeps everything.
1155    ///
1156    /// Folding the byte away when a second instruction wants it would be deleting a value the
1157    /// program computes. This is the whole of what [`fusable`] is asked before allocation, and the
1158    /// second reader here is in another block so that it is a question about the function rather
1159    /// than about the block the branch is in.
1160    #[test]
1161    fn a_comparison_whose_answer_something_else_reads_keeps_its_byte_and_its_test() {
1162        let (mut names, mut func, made) = blank(3);
1163        let byte = compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
1164        let opcode = Opcode::new(names.intern("x64.mov_rr_64"));
1165        func.build(made[1], opcode)
1166            .def(Reg::physical(RAX), GPR)
1167            .operand(Operand::read(byte, GPR))
1168            .finish();
1169
1170        let text = laid_out(&mut func, &mut names);
1171
1172        assert!(text.contains(&"x64.test_rr_8 %0".to_owned()), "{text:?}");
1173        assert!(text.contains(&"x64.jcc_e block2, block1".to_owned()), "{text:?}");
1174    }
1175
1176    /// A comparison allocation moved away from its branch, which keeps its test.
1177    ///
1178    /// [`fusable`] says the byte has one reader and says nothing about where the two instructions
1179    /// end up, because allocation runs between the two halves and may put a reload in front of the
1180    /// branch. The flags do not survive one, so the second half looks again, and this is the case
1181    /// where it finds something and refuses. The instruction is put in between the two calls
1182    /// because that is when allocation would have put it there.
1183    #[test]
1184    fn a_comparison_that_is_no_longer_in_front_of_its_branch_keeps_its_test() {
1185        let (mut names, mut func, made) = blank(3);
1186        compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
1187        let fusable = fusable(&func, &BRANCH, &mut names);
1188        assert_eq!(fusable.len(), 1, "the comparison is one the byte's count allows");
1189
1190        let branch = func.terminator(made[0]).expect("a block with two arms has a branch");
1191        let opcode = Opcode::new(names.intern("x64.mov_rr_64"));
1192        let reload = func
1193            .build_loose(opcode)
1194            .def(Reg::physical(RCX), GPR)
1195            .operand(Operand::read(Reg::physical(RAX), GPR))
1196            .finish();
1197        func.insert_before(branch, reload);
1198        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
1199        let text = mir::print_func(&func, &names, &REGS);
1200
1201        assert!(text.contains("x64.cmp_set_l_32"), "{text}");
1202        assert!(text.contains("x64.test_rr_8"), "{text}");
1203        assert!(!text.contains("x64.cmp_rr_32"), "{text}");
1204    }
1205
1206    /// Laying the blocks out along the traces the weights say, which is what every level above
1207    /// `-O0` asks for.
1208    fn traced(func: &mut mir::Func, names: &mut Interner) -> Vec<String> {
1209        let fusable = fusable(func, &BRANCH, names);
1210        blocks(func, &BRANCH, names, &fusable, true);
1211        mir::print_func(func, names, &REGS)
1212            .lines()
1213            .filter(|line| !line.trim().is_empty() && !line.starts_with("mfunc") && *line != "}")
1214            .map(|line| line.trim().to_string())
1215            .collect()
1216    }
1217
1218    /// Says how often a block runs and how often each of its arms is taken, in parts of ten
1219    /// thousand, the way `crate::weights` would have.
1220    fn runs(func: &mut mir::Func, block: mir::Block, weight: u64, arms: &[u64]) {
1221        func.set_weight(block, mir::Weight::parts(weight));
1222        for (index, &taken) in arms.iter().enumerate() {
1223            func.succs_mut(block)[index].weight = mir::Weight::parts(taken);
1224        }
1225    }
1226
1227    /// The arm almost always taken is the one laid out next, whichever of the two it is.
1228    ///
1229    /// Same function twice, with the two arms weighted the two ways round. At `-O0` the order is
1230    /// the shape of the graph and the first arm always comes next; here it is the weights, so the
1231    /// block that hardly ever runs goes behind the one that nearly always does and the jump is
1232    /// spent on it rather than on the common path.
1233    #[test]
1234    fn the_arm_that_is_nearly_always_taken_is_the_one_laid_out_next() {
1235        let (mut names, mut func, made) = blank(3);
1236        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
1237        runs(&mut func, made[0], 10_000, &[200, 9_800]);
1238        runs(&mut func, made[1], 200, &[]);
1239        runs(&mut func, made[2], 9_800, &[]);
1240
1241        traced(&mut func, &mut names);
1242
1243        assert_eq!(order_of(&func), [0, 2, 1]);
1244
1245        let (mut names, mut func, made) = blank(3);
1246        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
1247        runs(&mut func, made[0], 10_000, &[9_800, 200]);
1248        runs(&mut func, made[1], 9_800, &[]);
1249        runs(&mut func, made[2], 200, &[]);
1250
1251        traced(&mut func, &mut names);
1252
1253        assert_eq!(order_of(&func), [0, 1, 2]);
1254    }
1255
1256    /// A loop comes out as its header, its body and then its exit, with the back edge backwards.
1257    ///
1258    /// Nothing here rotates anything. The trace walks out of the header into the body because the
1259    /// body is where the header nearly always goes, stops at the latch because the header it
1260    /// wants next is already laid out, and the exit is picked up as the next seed. That is the
1261    /// order a branch predictor's static guess expects and it is what the greedy rule gives.
1262    #[test]
1263    fn a_loop_is_laid_out_with_its_exit_behind_it_and_its_back_edge_running_backwards() {
1264        let (mut names, mut func, made) = blank(4);
1265        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
1266        branch(&mut func, &mut names, made[1], &[made[2], made[3]]);
1267        *func.succs_mut(made[2]) = vec![BlockCall::to(made[1])];
1268        runs(&mut func, made[0], 10_000, &[10_000]);
1269        runs(&mut func, made[1], 100_000, &[90_000, 10_000]);
1270        runs(&mut func, made[2], 90_000, &[90_000]);
1271        runs(&mut func, made[3], 10_000, &[]);
1272
1273        let text = traced(&mut func, &mut names);
1274
1275        assert_eq!(order_of(&func), [0, 1, 2, 3]);
1276        assert_eq!(
1277            text,
1278            [
1279                "block0:",
1280                "block1",
1281                "block1:",
1282                "x64.test_rr_8 $rax",
1283                "x64.jcc_e block3, block2",
1284                "block2:",
1285                "x64.jmp block1",
1286                "block3:",
1287            ]
1288        );
1289    }
1290
1291    /// A block reached only from the cold arm is laid out behind everything the trunk reaches.
1292    ///
1293    /// The shape is `if (unlikely) handle(); rest();`, where the handler and the rest of the
1294    /// function are both reached from the branch. Reverse postorder puts the handler between the
1295    /// branch and the rest of the function; the trace puts the rest of the function next, because
1296    /// that is where the branch nearly always goes, and the handler ends up last.
1297    #[test]
1298    fn a_block_only_the_cold_arm_reaches_goes_behind_the_rest_of_the_function() {
1299        let (mut names, mut func, made) = blank(4);
1300        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
1301        *func.succs_mut(made[1]) = vec![BlockCall::to(made[2])];
1302        *func.succs_mut(made[2]) = vec![BlockCall::to(made[3])];
1303        runs(&mut func, made[0], 10_000, &[100, 9_900]);
1304        runs(&mut func, made[1], 100, &[100]);
1305        runs(&mut func, made[2], 10_000, &[10_000]);
1306        runs(&mut func, made[3], 10_000, &[]);
1307
1308        assert_eq!(order(&func), [made[0], made[1], made[2], made[3]]);
1309
1310        traced(&mut func, &mut names);
1311
1312        assert_eq!(order_of(&func), [0, 2, 3, 1]);
1313    }
1314
1315    /// A block nothing reaches is still laid out, since the last round asks for nothing.
1316    #[test]
1317    fn the_last_round_picks_up_a_block_nothing_reaches() {
1318        let (mut names, mut func, made) = blank(3);
1319        *func.succs_mut(made[0]) = vec![BlockCall::to(made[2])];
1320        runs(&mut func, made[0], 10_000, &[10_000]);
1321        runs(&mut func, made[1], 0, &[]);
1322        runs(&mut func, made[2], 10_000, &[]);
1323
1324        traced(&mut func, &mut names);
1325
1326        assert_eq!(order_of(&func), [0, 2, 1]);
1327    }
1328
1329    /// The entry is laid out first however cold it is against the rest of the function.
1330    ///
1331    /// A function is entered at its first byte, so the block that runs first has to be the block
1332    /// that is written first, and the seed order is what makes that true rather than any check
1333    /// afterwards. Here the loop body runs ten times for every call and would otherwise have been
1334    /// the first seed.
1335    #[test]
1336    fn the_entry_is_the_first_seed_even_when_something_else_runs_more_often() {
1337        let (mut names, mut func, made) = blank(3);
1338        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
1339        branch(&mut func, &mut names, made[1], &[made[1], made[2]]);
1340        runs(&mut func, made[0], 10_000, &[10_000]);
1341        runs(&mut func, made[1], 100_000, &[90_000, 10_000]);
1342        runs(&mut func, made[2], 10_000, &[]);
1343
1344        traced(&mut func, &mut names);
1345
1346        assert_eq!(func.blocks().next().map(mir::Block::index), Some(0));
1347    }
1348
1349    /// A branch whose arms are even still falls into one of them rather than jumping to both.
1350    ///
1351    /// Nothing predicts a range check, so both arms come out at half, and half is under every
1352    /// branch threshold above the last round. The trace therefore ends at the branch, and what
1353    /// decides the layout is where the next one starts: at the likeliest arm out of the block the
1354    /// trace stopped in, which is a fall-through, and not at whichever of the two blocks was made
1355    /// first, which would have cost a jump on both paths out of an even branch.
1356    #[test]
1357    fn a_branch_whose_arms_are_even_is_still_laid_out_next_to_one_of_them() {
1358        let (mut names, mut func, made) = blank(3);
1359        // The second arm is the block made first, so a layout that fell back to the seed list
1360        // would lay that one out next and leave the arm written first to be jumped to.
1361        branch(&mut func, &mut names, made[0], &[made[2], made[1]]);
1362        runs(&mut func, made[0], 10_000, &[5_000, 5_000]);
1363        runs(&mut func, made[1], 5_000, &[]);
1364        runs(&mut func, made[2], 5_000, &[]);
1365
1366        traced(&mut func, &mut names);
1367
1368        assert_eq!(order_of(&func), [0, 2, 1]);
1369    }
1370
1371    /// A run of blocks the rounds cut in half comes back out in one piece.
1372    ///
1373    /// Two comparisons against a constant, one behind the other, which is what a switch over
1374    /// scattered labels is lowered to. The second comparison is only reached when the first one
1375    /// failed, so it runs half as often as the function is entered and the first round will not
1376    /// touch it: the trace stops at the first comparison and the block that was about to fall
1377    /// through it is left for a later round. What puts it back is [`connect`], and without it the
1378    /// body of the first case would sit between the two comparisons and both would pay a jump.
1379    #[test]
1380    fn a_chain_the_rounds_cut_in_half_is_run_back_together() {
1381        let (mut names, mut func, made) = blank(5);
1382        branch(&mut func, &mut names, made[0], &[made[2], made[1]]);
1383        branch(&mut func, &mut names, made[2], &[made[4], made[3]]);
1384        runs(&mut func, made[0], 10_000, &[5_000, 5_000]);
1385        runs(&mut func, made[1], 5_000, &[]);
1386        runs(&mut func, made[2], 5_000, &[3_000, 2_000]);
1387        runs(&mut func, made[3], 2_000, &[]);
1388        runs(&mut func, made[4], 3_000, &[]);
1389
1390        traced(&mut func, &mut names);
1391
1392        assert_eq!(order_of(&func), [0, 2, 4, 1, 3]);
1393    }
1394
1395    /// The head of a loop is the block its back edge runs to, and a function with no loop has none.
1396    #[test]
1397    fn the_head_of_a_loop_is_where_its_back_edge_lands() {
1398        let (mut names, mut func, made) = blank(4);
1399        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
1400        branch(&mut func, &mut names, made[1], &[made[2], made[3]]);
1401        *func.succs_mut(made[2]) = vec![BlockCall::to(made[1])];
1402        runs(&mut func, made[0], 10_000, &[10_000]);
1403        runs(&mut func, made[1], 100_000, &[90_000, 10_000]);
1404        runs(&mut func, made[2], 90_000, &[90_000]);
1405        runs(&mut func, made[3], 10_000, &[]);
1406        traced(&mut func, &mut names);
1407        assert_eq!(heads(&func), [made[1]]);
1408
1409        let (mut names, mut func, made) = blank(4);
1410        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
1411        *func.succs_mut(made[1]) = vec![BlockCall::to(made[2])];
1412        *func.succs_mut(made[2]) = vec![BlockCall::to(made[3])];
1413        traced(&mut func, &mut names);
1414        assert_eq!(heads(&func), [], "nothing runs backwards");
1415    }
1416
1417    /// A loop that runs less than a hundredth as often as the hottest block is left unpadded.
1418    #[test]
1419    fn a_loop_that_hardly_runs_is_not_padded() {
1420        let (mut names, mut func, made) = blank(5);
1421        branch(&mut func, &mut names, made[0], &[made[1], made[3]]);
1422        branch(&mut func, &mut names, made[1], &[made[1], made[2]]);
1423        branch(&mut func, &mut names, made[3], &[made[3], made[4]]);
1424        *func.succs_mut(made[2]) = vec![BlockCall::to(made[4])];
1425        runs(&mut func, made[0], 10_000, &[10, 9_990]);
1426        runs(&mut func, made[1], 900, &[890, 10]);
1427        runs(&mut func, made[2], 10, &[10]);
1428        runs(&mut func, made[3], 100_000, &[90_010, 9_990]);
1429        runs(&mut func, made[4], 10_000, &[]);
1430        traced(&mut func, &mut names);
1431        assert_eq!(heads(&func), [made[3]], "the cold loop runs 900 times to the hot one's 100000");
1432    }
1433
1434    /// A jump back to a block that cannot get back to the jump is the end of a cold arm rather
1435    /// than a loop, however the layout ordered the two.
1436    #[test]
1437    fn a_jump_backwards_out_of_a_cold_arm_is_not_a_loop() {
1438        let (mut names, mut func, made) = blank(4);
1439        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
1440        *func.succs_mut(made[1]) = vec![BlockCall::to(made[3])];
1441        *func.succs_mut(made[2]) = vec![BlockCall::to(made[3])];
1442        runs(&mut func, made[0], 10_000, &[9_990, 10]);
1443        runs(&mut func, made[1], 9_990, &[9_990]);
1444        runs(&mut func, made[2], 10, &[10]);
1445        runs(&mut func, made[3], 10_000, &[]);
1446        traced(&mut func, &mut names);
1447        let at = |block| func.blocks().position(|laid| laid == block);
1448        assert!(at(made[2]) > at(made[3]), "the cold arm is laid out behind where it rejoins");
1449        assert_eq!(heads(&func), []);
1450    }
1451
1452    /// A block that jumps to itself is a loop, and the first block is never padded even when a
1453    /// jump runs back to it.
1454    #[test]
1455    fn a_block_that_goes_round_itself_is_a_head_and_the_first_block_is_not() {
1456        let (mut names, mut func, made) = blank(3);
1457        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
1458        branch(&mut func, &mut names, made[1], &[made[1], made[2]]);
1459        branch(&mut func, &mut names, made[2], &[made[0], made[2]]);
1460        laid_out(&mut func, &mut names);
1461        assert_eq!(heads(&func), [made[1], made[2]]);
1462    }
1463}