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 four 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 block a branch sometimes needs
54//!
55//! A branch whose second arm cannot be laid out next, because both its arms are blocks the walk
56//! has already been to, would need two jumps in one block. Rather than write one, this makes the
57//! block it needs: an empty one on the second edge, laid out immediately after the branch, that
58//! jumps where the edge went. That is exactly the critical edge splitting in [`crate::split`],
59//! done for a different reason, and it costs the same jump the second jump would have cost while
60//! leaving every block with at most one.
61//!
62//! # The test a comparison makes unnecessary
63//!
64//! Almost every branch a C program writes is on a comparison, and a comparison has already set
65//! the flags by the time the byte it wrote is tested against itself. So where the instruction in
66//! front of the branch is that comparison, and the branch is the whole of what reads its byte,
67//! the byte and the test both go and the jump names the condition the comparison was asked about
68//! instead of naming zero. Three instructions become two, and the two are what the machine has a
69//! comparison and a conditional jump for.
70//!
71//! This is where it happens rather than anywhere earlier because of what the flags are. Between
72//! the comparison and the jump they are live and they are not a register: no pass could be told
73//! about them, so no pass may put an instruction between the two. After this one there is no pass
74//! left, which is the whole of the argument, and it is the same argument
75//! `rucc_target::x86_64::Form::CmpSet` is one form rather than two under.
76//!
77//! What this cannot work out for itself is whether the byte has another reader. Every register is
78//! physical by the time this runs and a physical register is written many times in a function, so
79//! the question has to be asked while they are still virtual and written once. [`fusable`] is that
80//! question, asked before allocation, and its answer is one of the arguments to [`blocks`]. The
81//! same arrangement, and for the same reason, as the addresses [`crate::finish`] has still to
82//! write and [`crate::fold`] is handed.
83//!
84//! # Why it runs last
85//!
86//! [`crate::finish`] finds the blocks a function returns from by looking for the ones that go
87//! nowhere. Nothing here creates one of those, but everything here reads and writes the arms, and
88//! a pass that reorders them is one nothing before it should be looking at. Running the layout
89//! after the prologue and the epilogue are in is also what makes the epilogue something it can
90//! lay out around rather than something it has to leave room for.
91
92use std::cmp::Reverse;
93use std::collections::{BinaryHeap, HashMap, HashSet};
94
95use rucc_base::Interner;
96use rucc_mir as mir;
97use rucc_target::{BranchInsts, Fusion, Role};
98
99/// The scale a weight is in, which is what a share of a block is worked out against.
100const SCALE: u128 = mir::Weight::SCALE as u128;
101
102/// Puts a function's blocks in an order and writes the jumps that order needs.
103///
104/// Run last, after [`crate::finish`].
105///
106/// # Panics
107///
108/// Panics on a block with more than two successors, which nothing lowers to yet, and on a block
109/// with two whose last instruction is not the conditional branch the target named. Both are a
110/// function that was built wrongly somewhere earlier, and both are worth finding here rather than
111/// as a jump to the wrong place.
112pub fn blocks(
113    func: &mut mir::Func,
114    insts: &BranchInsts,
115    names: &mut Interner,
116    fusable: &HashSet<mir::Inst>,
117    reorder: bool,
118) {
119    let table = table(insts, names);
120    let mut order = if reorder { traces(func) } else { order(func) };
121    let mut writer = Writer { func, insts, names, table, fusable };
122    let mut at = 0;
123    while at < order.len() {
124        // A branch that can fall into neither arm asks for a block to put the second jump in, and
125        // that block goes immediately after it, which is where the loop reaches it next.
126        if let Some(bridge) = writer.edges(order[at], order.get(at + 1).copied()) {
127            order.insert(at + 1, bridge);
128        }
129        at += 1;
130    }
131    func.set_block_order(&order);
132}
133
134/// The order the blocks are laid out in, which is every block the function has exactly once.
135fn order(func: &mir::Func) -> Vec<mir::Block> {
136    let mut order = Vec::with_capacity(func.block_count());
137    let mut seen = vec![false; func.block_count()];
138    if let Some(entry) = func.entry() {
139        seen[entry.index()] = true;
140        // The walk is explicit rather than recursive because a function with a hundred thousand
141        // blocks in it is a function somebody generated, and it should compile rather than run out
142        // of stack. Each entry is a block and how many of its arms have been started.
143        let mut stack = vec![(entry, 0usize)];
144        while let Some((block, next)) = stack.pop() {
145            let succs = &func[block].succs;
146            let Some(arm) = succs.len().checked_sub(next + 1) else {
147                order.push(block);
148                continue;
149            };
150            stack.push((block, next + 1));
151            let to = succs[arm].block;
152            if !std::mem::replace(&mut seen[to.index()], true) {
153                stack.push((to, 0));
154            }
155        }
156        order.reverse();
157    }
158    // Whatever the walk did not reach, in the order the blocks were made, which is the only order
159    // there is anything to be said for when nothing goes to any of them.
160    order.extend(func.blocks().filter(|block| !seen[block.index()]));
161    order
162}
163
164/// The rounds the traces are built in, each asking for less than the one before it.
165///
166/// Design: `spec/optimizer/38-scheduling-and-layout.md` section 38.4, which quotes
167/// `gcc/bb-reorder.cc:32` on why there is more than one round: a first round that only follows
168/// the arms almost always taken builds the trunk of the function, and the rounds below it pick up
169/// what is left without being able to break the trunk apart. It costs one more pass over the
170/// blocks per round and it is the difference between "stc" and "simple".
171///
172/// A round is a pair. The first number is how likely an arm has to be for the trace to follow it,
173/// in parts of [`mir::Weight::SCALE`], which is GCC's branch threshold. The second is how often
174/// the block at the end of that arm has to run, in the same parts of how often the function is
175/// entered, which is GCC's exec threshold. The last round asks for nothing, which is what makes
176/// every block end up somewhere.
177///
178/// The eight numbers are GCC's own, out of `branch_threshold` and `exec_threshold` in
179/// `gcc/bb-reorder.cc`, in ten thousandths where GCC writes thousandths. Two things about them
180/// are worth saying out loud because both were got wrong here first.
181///
182/// The branch threshold is low. Two fifths, not nine tenths: an arm taken half the time is an arm
183/// the first round follows, and since one arm of a two way branch always is, the first round walks
184/// straight through an unpredicted function the way a depth first walk would. A high threshold
185/// stops the trace at every branch nothing predicted, which is most of them, and hands both arms
186/// back to the seed list to be laid out by weight, and weight is exactly what has nothing to say
187/// about them.
188///
189/// The exec threshold is against the entry and not against the hottest block. A block that runs
190/// once per call is a block in the trunk of the function, and measuring it against a loop that
191/// runs twenty times a call makes the whole trunk cold: the preheader of every loop lands at the
192/// end of the function behind a jump, which is the opposite of what this is for.
193const ROUNDS: [(u64, u64); 4] = [(4_000, 5_000), (2_000, 2_000), (1_000, 500), (0, 0)];
194
195/// The order the blocks are laid out in above `-O0`, which is traces grown from the hottest
196/// blocks outwards.
197///
198/// Design: `spec/optimizer/38-scheduling-and-layout.md` section 38.4.
199///
200/// A trace is a run of blocks that control is expected to walk straight through. It is grown from
201/// a seed by repeatedly taking the arm most likely to be the one taken, stopping when no arm is
202/// likely enough for the round or when the likeliest one leads somewhere the layout has already
203/// been. Every block is a seed in some round, the hotter ones first, and the traces come out in
204/// the order they were grown. So the function's trunk is laid out first and contiguously, its
205/// error paths end up behind it, and the branch that leaves the trunk is the one that costs a
206/// jump.
207///
208/// The entry is the first seed whatever its weight, because on this machine a function is entered
209/// at its first byte and the block laid out first is the block that runs first. A hotter block
210/// inside a loop would otherwise take the seat.
211///
212/// The traces are then run together by [`connect`], which is what keeps a run of blocks the rounds
213/// cut in half from coming out in two places.
214///
215/// # Which block the next trace starts at
216///
217/// Not simply the hottest one left. A block something already laid out goes to comes first, and
218/// among those the one with the hottest edge into it, which is [`Seed`] and which is GCC's
219/// `bb_to_key` in `gcc/bb-reorder.cc`. The reason is the whole of what a layout costs: a block laid
220/// out in front of everything that reaches it pays a jump on every one of those paths and saves
221/// nothing, and a block laid out behind the trace that reaches it pays nothing on the path that
222/// falls into it. Seeding by weight alone gets this wrong on the commonest shape in C, which is two
223/// arms that both end at one block: the block both arms join at is the hottest of the three and
224/// goes first, and then both arms jump to it.
225///
226/// # Loop rotation, and where it comes from
227///
228/// Section 38.4 asks for the loop to be rotated so that its exit is the last block of the trace,
229/// and there is no step here that does it. It falls out of the walk instead: a trace that enters
230/// a loop header follows the body, reaches the latch, finds that the latch's likeliest arm is the
231/// header it has already laid out, and stops. The exit is then a seed of its own and comes next.
232/// That is the rotated order, back edge running backwards and exit falling through, arrived at
233/// from the greedy rule rather than from a rule about loops.
234///
235/// What that does not cover is a loop whose header is its exit test and whose body is cold, where
236/// GCC would duplicate the header. Section 38.4 says the first version should not copy code and
237/// this does not.
238fn traces(func: &mir::Func) -> Vec<mir::Block> {
239    // Where the shape of the graph would have put each block, which is what decides between two
240    // blocks that run equally often. Most branches in most functions have nothing to predict them
241    // by and come out even, so without this the seed order between them would be the order the
242    // blocks happen to have been made in, and a block that falls into the one after it under
243    // [`order`] would be laid out somewhere else for no reason and pay a jump for it.
244    let mut place = vec![usize::MAX; func.block_count()];
245    for (at, &block) in order(func).iter().enumerate() {
246        place[block.index()] = at;
247    }
248
249    let mut found: Vec<Vec<mir::Block>> = Vec::new();
250    let mut seen = vec![false; func.block_count()];
251    // How often the function is entered, which every exec threshold is a share of. A function
252    // whose entry says nothing is one nobody wrote a weight on, and then once is the right answer
253    // for every block in it and every round behaves the same.
254    let entered = func.entry().map_or(mir::Weight::ONCE, |entry| func[entry].weight).raw();
255    // The hottest edge into each block out of a block already laid out, which is what the queue is
256    // ordered by and what says whether an entry popped off it is out of date. It outlives the
257    // round it was written in on purpose: a trace that stops because the next block is below this
258    // round's exec threshold leaves that block remembered as reached, and the round that does take
259    // it starts its first trace there rather than wherever the weights happen to point. That is
260    // how a chain of comparisons whose tail cools off below the threshold stays a straight line.
261    let mut reached = vec![0; func.block_count()];
262
263    for (likely, often) in ROUNDS {
264        // The exec threshold as a number rather than a fraction. In a hundred and twenty eight
265        // bits because a weight saturates at the top of a sixty four bit one and a nest of loops
266        // gets there.
267        let floor =
268            u64::try_from(u128::from(entered) * u128::from(often) / SCALE).unwrap_or(u64::MAX);
269        // A round does not start a trace in a block colder than its exec threshold, which is what
270        // keeps an error path out of the middle of the trunk: it waits for a round that asks for
271        // less. The entry is the exception below, because the block laid out first is the block
272        // that runs first and that has to be the entry whatever it weighs.
273        let mut queue: BinaryHeap<Seed> = func
274            .blocks()
275            .filter(|&block| !seen[block.index()] && func[block].weight.raw() >= floor)
276            .map(|block| Seed {
277                reached: reached[block.index()],
278                weight: func[block].weight,
279                place: Reverse(place[block.index()]),
280                block,
281            })
282            .collect();
283        let mut start = func.entry().filter(|entry| !seen[entry.index()]);
284
285        while let Some(from) = start.take().or_else(|| next_seed(&mut queue, &seen, &reached)) {
286            let mut trace = Vec::new();
287            let mut block = from;
288            loop {
289                seen[block.index()] = true;
290                trace.push(block);
291                let next = along(func, block, &seen, likely, floor);
292                // Everything this block goes to and the trace does not, so that the next trace can
293                // start at one of them rather than wherever the weights point. A block too cold
294                // for this round is still written down as reached, because the round that is cold
295                // enough to take it wants to know it hangs off something already laid out.
296                for call in &func[block].succs {
297                    let to = call.block;
298                    if seen[to.index()]
299                        || Some(to) == next
300                        || call.weight.raw() <= reached[to.index()]
301                    {
302                        continue;
303                    }
304                    reached[to.index()] = call.weight.raw();
305                    if func[to].weight.raw() >= floor {
306                        queue.push(Seed {
307                            reached: call.weight.raw(),
308                            weight: func[to].weight,
309                            place: Reverse(place[to.index()]),
310                            block: to,
311                        });
312                    }
313                }
314                let Some(next) = next else { break };
315                block = next;
316            }
317            found.push(trace);
318        }
319    }
320    connect(func, found)
321}
322
323/// The traces run together into one order, each one followed where possible by the trace control
324/// leaves it for.
325///
326/// Design: `gcc/bb-reorder.cc`, `connect_traces`.
327///
328/// The rounds cut a straight run of blocks into pieces whenever the run cools below the round's
329/// exec threshold, and a chain of comparisons against a constant is exactly that: each comparison
330/// is reached only when every one before it failed, so the chain halves in weight at every step and
331/// the round that laid the head of it down will not touch the tail. Left alone, the pieces come out
332/// in round order with other traces between them, and every piece pays a jump to reach the next.
333///
334/// So the pieces are put back together. Each trace is followed by the unplaced trace its last block
335/// most often goes to, and that one by the trace its last block most often goes to, until there is
336/// none, and only then does the next trace in round order start a new run. The rounds still decide
337/// which trace is hot and comes first, and this decides what falls in behind it.
338fn connect(func: &mir::Func, traces: Vec<Vec<mir::Block>>) -> Vec<mir::Block> {
339    // Which trace each block starts, for the blocks that start one. A trace may only be joined at
340    // its first block, because joining it anywhere else would mean cutting it in half and the
341    // rounds put it together for a reason.
342    let mut head = vec![usize::MAX; func.block_count()];
343    for (at, trace) in traces.iter().enumerate() {
344        if let Some(&first) = trace.first() {
345            head[first.index()] = at;
346        }
347    }
348
349    let mut order = Vec::with_capacity(func.block_count());
350    let mut used = vec![false; traces.len()];
351    for from in 0..traces.len() {
352        if used[from] {
353            continue;
354        }
355        let mut at = from;
356        loop {
357            used[at] = true;
358            order.extend_from_slice(&traces[at]);
359            let Some(&last) = traces[at].last() else { break };
360            let mut best: Option<(u64, usize)> = None;
361            for call in &func[last].succs {
362                let to = head[call.block.index()];
363                if to == usize::MAX || used[to] {
364                    continue;
365                }
366                let weight = call.weight.raw();
367                // Ties go to the trace found first, which is the hotter of the two, because the
368                // rounds laid the traces down hottest first.
369                if best.is_none_or(|(found, over)| weight > found || (weight == found && to < over))
370                {
371                    best = Some((weight, to));
372                }
373            }
374            let Some((_, next)) = best else { break };
375            at = next;
376        }
377    }
378    order
379}
380
381/// A block a trace could start at, ordered so that the greatest is the one to start at next.
382///
383/// Design: `gcc/bb-reorder.cc`, `bb_to_key`, of which this is the same three answers in the order
384/// GCC asks them.
385#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
386struct Seed {
387    /// How often the hottest edge into this block out of a block already laid out is taken, and
388    /// zero while nothing laid out goes here. First, so that a block something reaches beats a
389    /// block nothing reaches however hot the second one is.
390    reached: u64,
391    /// How often the block runs, which decides between two blocks nothing laid out reaches.
392    weight: mir::Weight,
393    /// Where reverse postorder would have put it, which decides between two blocks that are equal
394    /// on both of the above, so that a function with no weights on it comes out in the order the
395    /// shape of its graph gives rather than in whatever order the queue settles.
396    place: Reverse<usize>,
397    /// The block, last, so that two blocks equal on everything else still come out in one order.
398    block: mir::Block,
399}
400
401/// The next block to start a trace at, out of the queue, or nothing when there is none left.
402///
403/// An entry whose block has been laid out since it was queued, or which was queued before a hotter
404/// edge into the same block was found, is thrown away here rather than found and updated in place
405/// when that happens. The queue is a heap and an entry in the middle of one cannot be reached, so
406/// the choice is between this and an index beside it, and a stale entry costs one pop.
407fn next_seed(queue: &mut BinaryHeap<Seed>, seen: &[bool], reached: &[u64]) -> Option<mir::Block> {
408    while let Some(seed) = queue.pop() {
409        if !seen[seed.block.index()] && seed.reached >= reached[seed.block.index()] {
410            return Some(seed.block);
411        }
412    }
413    None
414}
415
416/// The arm the trace follows out of a block, or nothing when no arm is worth following.
417///
418/// The likeliest arm that has not been laid out already, is taken at least as often as the
419/// round's floor, and takes at least the round's share of the times the block runs. Ties go to
420/// the arm written first, which is the arm a conditional branch takes when its condition holds,
421/// so a function with no weights on it at all comes out following the true arm.
422fn along(
423    func: &mir::Func,
424    block: mir::Block,
425    seen: &[bool],
426    likely: u64,
427    floor: u64,
428) -> Option<mir::Block> {
429    let whole = func[block].weight;
430    let mut best: Option<&mir::BlockCall> = None;
431    for call in &func[block].succs {
432        if seen[call.block.index()]
433            || call.weight.raw() < floor
434            || call.weight.out_of(whole) < likely
435        {
436            continue;
437        }
438        if best.is_none_or(|found| call.weight > found.weight) {
439            best = Some(call);
440        }
441    }
442    best.map(|call| call.block)
443}
444
445/// The comparisons a branch may be folded into, which [`blocks`] can then find by opcode.
446///
447/// One entry per name the target's table holds, interned once for the function rather than once
448/// per block, since a block that ends in a branch is most of the blocks there are.
449fn table(insts: &BranchInsts, names: &mut Interner) -> HashMap<mir::Opcode, &'static Fusion> {
450    insts
451        .fused
452        .iter()
453        .map(|fusion| {
454            (mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, fusion.set))), fusion)
455        })
456        .collect()
457}
458
459/// The comparisons a branch on their answer is the whole of what reads, which [`blocks`] may fold
460/// the test out of.
461///
462/// Run before allocation, on the same function [`blocks`] is later given. What it answers is
463/// whether anything but the branch reads the byte a comparison wrote, and that is a question about
464/// a virtual register: a physical one is written many times in a function and counting its readers
465/// would mean asking which of the writes each reader belongs to. So it is asked here, where a
466/// register is written once, and the answer is carried to the pass that can use it.
467///
468/// Being on this list is necessary and not sufficient. Allocation may put a reload between the
469/// comparison and the branch, and a comparison that is no longer the instruction in front of the
470/// branch is not one the flags survive to, so [`blocks`] checks that again on what it finds.
471#[must_use]
472pub fn fusable(func: &mir::Func, insts: &BranchInsts, names: &mut Interner) -> HashSet<mir::Inst> {
473    let table = table(insts, names);
474    let branch = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.cond)));
475    let reads = crate::fold::reads(func);
476    let mut found = HashSet::new();
477    for block in func.blocks() {
478        let insts: Vec<mir::Inst> = func.insts(block).collect();
479        let [.., compare, last] = insts[..] else { continue };
480        if func[last].opcode != branch || !table.contains_key(&func[compare].opcode) {
481            continue;
482        }
483        let operands = &func[func[compare].operands];
484        let Some(byte) = operands.first().filter(|operand| operand.role != Role::Use) else {
485            continue;
486        };
487        if !byte.reg.is_virtual() || reads.get(&byte.reg) != Some(&1) {
488            continue;
489        }
490        // And it is this branch that reads it rather than one in some other block, which the
491        // count alone does not say.
492        if func[func[last].operands].first().map(|operand| operand.reg) == Some(byte.reg) {
493            found.insert(compare);
494        }
495    }
496    found
497}
498
499/// The one thing that writes an instruction here, over the function it writes into.
500struct Writer<'a> {
501    func: &'a mut mir::Func,
502    insts: &'a BranchInsts,
503    names: &'a mut Interner,
504    table: HashMap<mir::Opcode, &'static Fusion>,
505    fusable: &'a HashSet<mir::Inst>,
506}
507
508impl Writer<'_> {
509    /// Writes the jumps one block needs, given the block laid out after it, and gives back the
510    /// block that has to go between the two when the branch needed one.
511    fn edges(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
512        match self.func[block].succs.len() {
513            0 => None,
514            1 => {
515                self.one(block, next);
516                None
517            }
518            2 => self.two(block, next),
519            arms => panic!("a block with {arms} arms, and nothing lowers to one"),
520        }
521    }
522
523    /// A block that goes to one place, which either follows it or has to be jumped to.
524    fn one(&mut self, block: mir::Block, next: Option<mir::Block>) {
525        if Some(self.func[block].succs[0].block) == next {
526            return;
527        }
528        let opcode = self.opcode(self.insts.jump);
529        self.func.build(block, opcode).finish();
530    }
531
532    /// A block that goes to two places, which is a test and a jump to one of them.
533    ///
534    /// The condition is read off the branch the rules selected and the branch is taken out, so the
535    /// register the test reads is the one the branch read and no new value is made. That is what
536    /// makes this safe to run after allocation: it writes no register that was not already
537    /// written and it asks for none that was not already asked for.
538    fn two(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
539        // Asked before the branch is taken out, because what it looks at is the instruction in
540        // front of the branch and taking the branch out would make that the last one.
541        let fused = self.fused(block);
542        let condition = self.take(block);
543
544        // Whichever arm is laid out next is the one the block falls into, and the jump is then
545        // the one taken when the condition sends it the other way. Falling into the arm the
546        // condition is false for leaves the jump taken when it holds, and falling into the arm it
547        // is true for leaves the other jump and the arms the other way round.
548        let (if_true, if_false) = match fused {
549            Some((_, fusion)) => (fusion.if_true, fusion.if_false),
550            None => (self.insts.if_true, self.insts.if_false),
551        };
552        let arms: Vec<mir::Block> = self.func[block].succs.iter().map(|arm| arm.block).collect();
553        let (name, bridge) = if next == Some(arms[1]) {
554            (if_true, None)
555        } else if next == Some(arms[0]) {
556            self.func.succs_mut(block).swap(0, 1);
557            (if_false, None)
558        } else {
559            (if_true, Some(self.bridge(block)))
560        };
561
562        match fused {
563            Some((compare, fusion)) => self.keep_only_the_flags(compare, fusion),
564            None => {
565                let opcode = self.opcode(self.insts.test);
566                self.func.build(block, opcode).operand(condition).finish();
567            }
568        }
569        let opcode = self.opcode(name);
570        self.func.build(block, opcode).finish();
571        bridge
572    }
573
574    /// The comparison the block's branch can be folded into, when there is one.
575    ///
576    /// Three things have to hold and [`fusable`] has already answered the one that cannot be
577    /// answered here. What is left is that the comparison is still the instruction in front of the
578    /// branch, since allocation may have put a reload between them and the flags do not survive
579    /// one, and that the byte the branch reads is the byte that comparison wrote, since the
580    /// allocator has since given both of them a physical register and two registers that were
581    /// different could have become the same one.
582    fn fused(&self, block: mir::Block) -> Option<(mir::Inst, &'static Fusion)> {
583        let insts: Vec<mir::Inst> = self.func.insts(block).collect();
584        let [.., compare, last] = insts[..] else { return None };
585        if !self.fusable.contains(&compare) {
586            return None;
587        }
588        let fusion = *self.table.get(&self.func[compare].opcode)?;
589        let byte = self.func[self.func[compare].operands].first()?.reg;
590        (self.func[self.func[last].operands].first()?.reg == byte).then_some((compare, fusion))
591    }
592
593    /// Turns a comparison that wrote a byte into the same comparison that writes nothing.
594    ///
595    /// The instruction stays where it is and keeps its immediate, which is the point: what it does
596    /// to the flags is what it already did, and the jump written behind it reads those. Only the
597    /// operand at the front goes, which is the byte, and the opcode changes to the one that has no
598    /// operand there.
599    fn keep_only_the_flags(&mut self, compare: mir::Inst, fusion: &Fusion) {
600        let read: Vec<mir::Operand> =
601            self.func[self.func[compare].operands].iter().skip(1).copied().collect();
602        let operands = self.func.push_operands(&read);
603        self.func[compare].opcode = self.opcode(fusion.cmp);
604        self.func[compare].operands = operands;
605    }
606
607    /// Takes the conditional branch off the end of a block and gives back what it read.
608    fn take(&mut self, block: mir::Block) -> mir::Operand {
609        let branch = self.func.terminator(block).expect("a block with two arms has a branch");
610        let cond = self.opcode(self.insts.cond);
611        assert_eq!(
612            self.func[branch].opcode, cond,
613            "a block with two arms whose last instruction is not the branch"
614        );
615        let operands = self.func[branch].operands;
616        let condition = self.func[operands][0];
617        self.func.remove_inst(branch);
618        condition
619    }
620
621    /// Puts an empty block on a branch's second edge, so that the branch has something to fall
622    /// into and the jump the edge really needs is in a block of its own.
623    fn bridge(&mut self, block: mir::Block) -> mir::Block {
624        let bridge = self.func.create_block();
625        let edge = self.func[block].succs[1].clone();
626        let weight = edge.weight;
627        self.func.set_weight(bridge, weight);
628        *self.func.succs_mut(bridge) = vec![edge];
629        self.func.succs_mut(block)[1] = mir::BlockCall::to(bridge).taken(weight);
630        bridge
631    }
632
633    /// The opcode of that name on this target, which is the name with the target's prefix in
634    /// front of it.
635    fn opcode(&mut self, name: &str) -> mir::Opcode {
636        mir::Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use rucc_mir::{BlockCall, Opcode, Operand, Reg};
643    use rucc_target::x86_64::{BRANCH, GPR, RAX, RCX, REGS};
644
645    use super::*;
646
647    /// A function with that many blocks, none of which goes anywhere yet.
648    fn blank(count: usize) -> (Interner, mir::Func, Vec<mir::Block>) {
649        let mut names = Interner::new();
650        let mut func = mir::Func::new(names.intern("f"));
651        let blocks = (0..count).map(|_| func.create_block()).collect();
652        (names, func, blocks)
653    }
654
655    /// Puts a conditional branch at the end of a block, on a register that is already physical
656    /// the way one is by the time this pass runs.
657    fn branch(func: &mut mir::Func, names: &mut Interner, block: mir::Block, arms: &[mir::Block]) {
658        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
659        func.build(block, opcode).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
660        *func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
661    }
662
663    /// Laying the blocks out for the one machine this crate has, and the dump of what came out.
664    ///
665    /// The dump rather than the function, because where a jump goes is on the block and the dump
666    /// is the one place the instruction and the arm are put back together. A test that read the
667    /// two separately would pass on a function whose jump and whose edge disagreed, which is the
668    /// mistake this pass is most able to make.
669    ///
670    /// A block is named in the dump by where it is in the layout rather than by the number it was
671    /// made with, which is why every expectation below reads that way and why the order is worth
672    /// asserting on its own.
673    fn laid_out(func: &mut mir::Func, names: &mut Interner) -> Vec<String> {
674        // Both halves, in the order the pipeline runs them, so that a test which builds a
675        // comparison in front of its branch sees what a compiled function would see.
676        let fusable = fusable(func, &BRANCH, names);
677        blocks(func, &BRANCH, names, &fusable, false);
678        mir::print_func(func, names, &REGS)
679            .lines()
680            .filter(|line| !line.trim().is_empty() && !line.starts_with("mfunc") && *line != "}")
681            .map(|line| line.trim().to_string())
682            .collect()
683    }
684
685    /// The blocks in layout order, by the number each was made with.
686    fn order_of(func: &mir::Func) -> Vec<usize> {
687        func.blocks().map(mir::Block::index).collect()
688    }
689
690    #[test]
691    fn a_block_that_falls_into_the_next_one_gets_no_jump_at_all() {
692        let (mut names, mut func, made) = blank(2);
693        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
694
695        let text = laid_out(&mut func, &mut names);
696
697        // The arm is still on the block, because the graph is still worth reading, and there is
698        // no instruction on it because the block it goes to is the one that runs next anyway.
699        assert_eq!(text, ["block0:", "block1", "block1:"]);
700    }
701
702    #[test]
703    fn a_block_that_goes_somewhere_that_is_not_next_gets_a_jump() {
704        let (mut names, mut func, made) = blank(2);
705        // A loop with nothing in it and no way out, which is the smallest function there is with
706        // an edge that runs backwards. Every layout puts the two blocks in this order, so the
707        // second one has nothing after it and its edge has to be a jump.
708        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
709        *func.succs_mut(made[1]) = vec![BlockCall::to(made[0])];
710
711        let text = laid_out(&mut func, &mut names);
712
713        assert_eq!(text, ["block0:", "block1", "block1:", "x64.jmp block0"]);
714    }
715
716    #[test]
717    fn a_branch_that_falls_into_its_false_arm_jumps_when_the_condition_holds() {
718        let (mut names, mut func, made) = blank(3);
719        // A loop whose body is the block it came from: the arm taken when the condition holds is
720        // a block the walk has already been to, so the other arm is what comes next.
721        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
722        branch(&mut func, &mut names, made[1], &[made[0], made[2]]);
723
724        let text = laid_out(&mut func, &mut names);
725
726        assert_eq!(order_of(&func), [0, 1, 2]);
727        assert_eq!(
728            text,
729            [
730                "block0:",
731                "block1",
732                "block1:",
733                "x64.test_rr_8 $rax",
734                "x64.jcc_ne block0, block2",
735                "block2:",
736            ]
737        );
738    }
739
740    #[test]
741    fn a_branch_that_falls_into_its_true_arm_jumps_when_the_condition_does_not_hold() {
742        let (mut names, mut func, made) = blank(3);
743        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
744
745        let text = laid_out(&mut func, &mut names);
746
747        // The arms come out swapped, because after this the first is where the jump goes and the
748        // second is what runs next, and the jump is the one taken when the condition failed.
749        assert_eq!(order_of(&func), [0, 1, 2]);
750        assert_eq!(
751            text,
752            ["block0:", "x64.test_rr_8 $rax", "x64.jcc_e block2, block1", "block1:", "block2:"]
753        );
754    }
755
756    #[test]
757    fn a_branch_that_can_fall_into_neither_arm_is_given_a_block_to_jump_from() {
758        let (mut names, mut func, made) = blank(2);
759        // A loop that goes back to the top or round again, so both arms are blocks the walk has
760        // already been to and nothing is left to lay out after it.
761        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
762        branch(&mut func, &mut names, made[1], &[made[0], made[1]]);
763
764        let text = laid_out(&mut func, &mut names);
765
766        // Block two is the one this made. It is empty, it is laid out where the branch falls into
767        // it, and the jump the second arm needed is in it rather than being a second jump in the
768        // block above.
769        assert_eq!(order_of(&func), [0, 1, 2]);
770        assert_eq!(
771            text,
772            [
773                "block0:",
774                "block1",
775                "block1:",
776                "x64.test_rr_8 $rax",
777                "x64.jcc_ne block0, block2",
778                "block2:",
779                "x64.jmp block1",
780            ]
781        );
782    }
783
784    #[test]
785    fn the_test_reads_the_register_the_branch_read() {
786        let (mut names, mut func, made) = blank(3);
787        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
788
789        let fusable = fusable(&func, &BRANCH, &mut names);
790        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
791
792        let test = func.insts(made[0]).next().expect("a test");
793        let operands = func[test].operands;
794        assert_eq!(func[operands], [Operand::read(Reg::physical(RAX), GPR)]);
795    }
796
797    #[test]
798    fn a_block_nothing_reaches_is_laid_out_at_the_end_rather_than_deleted() {
799        let (mut names, mut func, made) = blank(4);
800        *func.succs_mut(made[0]) = vec![BlockCall::to(made[3])];
801
802        let fusable = fusable(&func, &BRANCH, &mut names);
803        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
804
805        // Blocks one and two are reached by nothing, so they go last, in the order they were
806        // made. Deleting one would be a decision about what the program does, and this pass has
807        // no business making it.
808        assert_eq!(order_of(&func), [0, 3, 1, 2]);
809    }
810
811    #[test]
812    fn a_function_with_no_blocks_is_left_alone() {
813        let mut names = Interner::new();
814        let mut func = mir::Func::new(names.intern("f"));
815
816        let fusable = fusable(&func, &BRANCH, &mut names);
817        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
818
819        assert_eq!(func.block_count(), 0);
820    }
821
822    #[test]
823    #[should_panic(expected = "a block with 3 arms")]
824    fn a_block_with_three_arms_is_refused_rather_than_laid_out_wrongly() {
825        let (mut names, mut func, made) = blank(4);
826        branch(&mut func, &mut names, made[0], &[made[1], made[2], made[3]]);
827
828        let fusable = fusable(&func, &BRANCH, &mut names);
829        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
830    }
831
832    #[test]
833    #[should_panic(expected = "whose last instruction is not the branch")]
834    fn a_block_with_two_arms_and_no_branch_in_it_is_refused() {
835        let (mut names, mut func, made) = blank(3);
836        let opcode = Opcode::new(names.intern("x64.nop"));
837        func.build(made[0], opcode).finish();
838        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1]), BlockCall::to(made[2])];
839
840        let fusable = fusable(&func, &BRANCH, &mut names);
841        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
842    }
843
844    /// Puts a comparison and a branch on its answer at the end of a block.
845    ///
846    /// The byte is a virtual register, which is what it is when [`fusable`] is asked and is not
847    /// what it is when [`blocks`] runs. Nothing in either half cares which it is except the
848    /// counting, so a test that runs both over one function has to use the register the counting
849    /// wants, and what it costs is that this is one thing the unit tests cannot check about the
850    /// two halves running at different times. `crate::pipeline` runs them the real way round.
851    fn compare(
852        func: &mut mir::Func,
853        names: &mut Interner,
854        block: mir::Block,
855        arms: &[mir::Block],
856    ) -> Reg {
857        let byte = func.new_vreg(GPR);
858        let opcode = Opcode::new(names.intern("x64.cmp_set_l_32"));
859        func.build(block, opcode)
860            .def(byte, GPR)
861            .operand(Operand::read(Reg::physical(RAX), GPR))
862            .operand(Operand::read(Reg::physical(RCX), GPR))
863            .finish();
864        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
865        func.build(block, opcode).operand(Operand::read(byte, GPR)).finish();
866        *func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
867        byte
868    }
869
870    /// A branch on a comparison is the comparison and a jump on what it found.
871    ///
872    /// Three instructions go in and two come out. The byte goes because nothing reads it, the test
873    /// goes because the comparison set the flags the test was going to set, and the jump names the
874    /// condition rather than naming zero. Which condition it names is the opposite of the one the
875    /// comparison asked about, since the block falls into the arm the comparison is true for.
876    #[test]
877    fn a_branch_on_a_comparison_is_the_comparison_and_a_jump_on_what_it_found() {
878        let (mut names, mut func, made) = blank(3);
879        compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
880
881        let text = laid_out(&mut func, &mut names);
882
883        assert_eq!(
884            text,
885            [
886                "block0:",
887                "x64.cmp_rr_32 $rax, $rcx",
888                "x64.jcc_ge block2, block1",
889                "block1:",
890                "block2:",
891            ]
892        );
893    }
894
895    /// The same comparison with something else reading its answer, which keeps everything.
896    ///
897    /// Folding the byte away when a second instruction wants it would be deleting a value the
898    /// program computes. This is the whole of what [`fusable`] is asked before allocation, and the
899    /// second reader here is in another block so that it is a question about the function rather
900    /// than about the block the branch is in.
901    #[test]
902    fn a_comparison_whose_answer_something_else_reads_keeps_its_byte_and_its_test() {
903        let (mut names, mut func, made) = blank(3);
904        let byte = compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
905        let opcode = Opcode::new(names.intern("x64.mov_rr_64"));
906        func.build(made[1], opcode)
907            .def(Reg::physical(RAX), GPR)
908            .operand(Operand::read(byte, GPR))
909            .finish();
910
911        let text = laid_out(&mut func, &mut names);
912
913        assert!(text.contains(&"x64.test_rr_8 %0".to_owned()), "{text:?}");
914        assert!(text.contains(&"x64.jcc_e block2, block1".to_owned()), "{text:?}");
915    }
916
917    /// A comparison allocation moved away from its branch, which keeps its test.
918    ///
919    /// [`fusable`] says the byte has one reader and says nothing about where the two instructions
920    /// end up, because allocation runs between the two halves and may put a reload in front of the
921    /// branch. The flags do not survive one, so the second half looks again, and this is the case
922    /// where it finds something and refuses. The instruction is put in between the two calls
923    /// because that is when allocation would have put it there.
924    #[test]
925    fn a_comparison_that_is_no_longer_in_front_of_its_branch_keeps_its_test() {
926        let (mut names, mut func, made) = blank(3);
927        compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
928        let fusable = fusable(&func, &BRANCH, &mut names);
929        assert_eq!(fusable.len(), 1, "the comparison is one the byte's count allows");
930
931        let branch = func.terminator(made[0]).expect("a block with two arms has a branch");
932        let opcode = Opcode::new(names.intern("x64.mov_rr_64"));
933        let reload = func
934            .build_loose(opcode)
935            .def(Reg::physical(RCX), GPR)
936            .operand(Operand::read(Reg::physical(RAX), GPR))
937            .finish();
938        func.insert_before(branch, reload);
939        blocks(&mut func, &BRANCH, &mut names, &fusable, false);
940        let text = mir::print_func(&func, &names, &REGS);
941
942        assert!(text.contains("x64.cmp_set_l_32"), "{text}");
943        assert!(text.contains("x64.test_rr_8"), "{text}");
944        assert!(!text.contains("x64.cmp_rr_32"), "{text}");
945    }
946
947    /// Laying the blocks out along the traces the weights say, which is what every level above
948    /// `-O0` asks for.
949    fn traced(func: &mut mir::Func, names: &mut Interner) -> Vec<String> {
950        let fusable = fusable(func, &BRANCH, names);
951        blocks(func, &BRANCH, names, &fusable, true);
952        mir::print_func(func, names, &REGS)
953            .lines()
954            .filter(|line| !line.trim().is_empty() && !line.starts_with("mfunc") && *line != "}")
955            .map(|line| line.trim().to_string())
956            .collect()
957    }
958
959    /// Says how often a block runs and how often each of its arms is taken, in parts of ten
960    /// thousand, the way `crate::weights` would have.
961    fn runs(func: &mut mir::Func, block: mir::Block, weight: u64, arms: &[u64]) {
962        func.set_weight(block, mir::Weight::parts(weight));
963        for (index, &taken) in arms.iter().enumerate() {
964            func.succs_mut(block)[index].weight = mir::Weight::parts(taken);
965        }
966    }
967
968    /// The arm almost always taken is the one laid out next, whichever of the two it is.
969    ///
970    /// Same function twice, with the two arms weighted the two ways round. At `-O0` the order is
971    /// the shape of the graph and the first arm always comes next; here it is the weights, so the
972    /// block that hardly ever runs goes behind the one that nearly always does and the jump is
973    /// spent on it rather than on the common path.
974    #[test]
975    fn the_arm_that_is_nearly_always_taken_is_the_one_laid_out_next() {
976        let (mut names, mut func, made) = blank(3);
977        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
978        runs(&mut func, made[0], 10_000, &[200, 9_800]);
979        runs(&mut func, made[1], 200, &[]);
980        runs(&mut func, made[2], 9_800, &[]);
981
982        traced(&mut func, &mut names);
983
984        assert_eq!(order_of(&func), [0, 2, 1]);
985
986        let (mut names, mut func, made) = blank(3);
987        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
988        runs(&mut func, made[0], 10_000, &[9_800, 200]);
989        runs(&mut func, made[1], 9_800, &[]);
990        runs(&mut func, made[2], 200, &[]);
991
992        traced(&mut func, &mut names);
993
994        assert_eq!(order_of(&func), [0, 1, 2]);
995    }
996
997    /// A loop comes out as its header, its body and then its exit, with the back edge backwards.
998    ///
999    /// Nothing here rotates anything. The trace walks out of the header into the body because the
1000    /// body is where the header nearly always goes, stops at the latch because the header it
1001    /// wants next is already laid out, and the exit is picked up as the next seed. That is the
1002    /// order a branch predictor's static guess expects and it is what the greedy rule gives.
1003    #[test]
1004    fn a_loop_is_laid_out_with_its_exit_behind_it_and_its_back_edge_running_backwards() {
1005        let (mut names, mut func, made) = blank(4);
1006        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
1007        branch(&mut func, &mut names, made[1], &[made[2], made[3]]);
1008        *func.succs_mut(made[2]) = vec![BlockCall::to(made[1])];
1009        runs(&mut func, made[0], 10_000, &[10_000]);
1010        runs(&mut func, made[1], 100_000, &[90_000, 10_000]);
1011        runs(&mut func, made[2], 90_000, &[90_000]);
1012        runs(&mut func, made[3], 10_000, &[]);
1013
1014        let text = traced(&mut func, &mut names);
1015
1016        assert_eq!(order_of(&func), [0, 1, 2, 3]);
1017        assert_eq!(
1018            text,
1019            [
1020                "block0:",
1021                "block1",
1022                "block1:",
1023                "x64.test_rr_8 $rax",
1024                "x64.jcc_e block3, block2",
1025                "block2:",
1026                "x64.jmp block1",
1027                "block3:",
1028            ]
1029        );
1030    }
1031
1032    /// A block reached only from the cold arm is laid out behind everything the trunk reaches.
1033    ///
1034    /// The shape is `if (unlikely) handle(); rest();`, where the handler and the rest of the
1035    /// function are both reached from the branch. Reverse postorder puts the handler between the
1036    /// branch and the rest of the function; the trace puts the rest of the function next, because
1037    /// that is where the branch nearly always goes, and the handler ends up last.
1038    #[test]
1039    fn a_block_only_the_cold_arm_reaches_goes_behind_the_rest_of_the_function() {
1040        let (mut names, mut func, made) = blank(4);
1041        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
1042        *func.succs_mut(made[1]) = vec![BlockCall::to(made[2])];
1043        *func.succs_mut(made[2]) = vec![BlockCall::to(made[3])];
1044        runs(&mut func, made[0], 10_000, &[100, 9_900]);
1045        runs(&mut func, made[1], 100, &[100]);
1046        runs(&mut func, made[2], 10_000, &[10_000]);
1047        runs(&mut func, made[3], 10_000, &[]);
1048
1049        assert_eq!(order(&func), [made[0], made[1], made[2], made[3]]);
1050
1051        traced(&mut func, &mut names);
1052
1053        assert_eq!(order_of(&func), [0, 2, 3, 1]);
1054    }
1055
1056    /// A block nothing reaches is still laid out, since the last round asks for nothing.
1057    #[test]
1058    fn the_last_round_picks_up_a_block_nothing_reaches() {
1059        let (mut names, mut func, made) = blank(3);
1060        *func.succs_mut(made[0]) = vec![BlockCall::to(made[2])];
1061        runs(&mut func, made[0], 10_000, &[10_000]);
1062        runs(&mut func, made[1], 0, &[]);
1063        runs(&mut func, made[2], 10_000, &[]);
1064
1065        traced(&mut func, &mut names);
1066
1067        assert_eq!(order_of(&func), [0, 2, 1]);
1068    }
1069
1070    /// The entry is laid out first however cold it is against the rest of the function.
1071    ///
1072    /// A function is entered at its first byte, so the block that runs first has to be the block
1073    /// that is written first, and the seed order is what makes that true rather than any check
1074    /// afterwards. Here the loop body runs ten times for every call and would otherwise have been
1075    /// the first seed.
1076    #[test]
1077    fn the_entry_is_the_first_seed_even_when_something_else_runs_more_often() {
1078        let (mut names, mut func, made) = blank(3);
1079        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
1080        branch(&mut func, &mut names, made[1], &[made[1], made[2]]);
1081        runs(&mut func, made[0], 10_000, &[10_000]);
1082        runs(&mut func, made[1], 100_000, &[90_000, 10_000]);
1083        runs(&mut func, made[2], 10_000, &[]);
1084
1085        traced(&mut func, &mut names);
1086
1087        assert_eq!(func.blocks().next().map(mir::Block::index), Some(0));
1088    }
1089
1090    /// A branch whose arms are even still falls into one of them rather than jumping to both.
1091    ///
1092    /// Nothing predicts a range check, so both arms come out at half, and half is under every
1093    /// branch threshold above the last round. The trace therefore ends at the branch, and what
1094    /// decides the layout is where the next one starts: at the likeliest arm out of the block the
1095    /// trace stopped in, which is a fall-through, and not at whichever of the two blocks was made
1096    /// first, which would have cost a jump on both paths out of an even branch.
1097    #[test]
1098    fn a_branch_whose_arms_are_even_is_still_laid_out_next_to_one_of_them() {
1099        let (mut names, mut func, made) = blank(3);
1100        // The second arm is the block made first, so a layout that fell back to the seed list
1101        // would lay that one out next and leave the arm written first to be jumped to.
1102        branch(&mut func, &mut names, made[0], &[made[2], made[1]]);
1103        runs(&mut func, made[0], 10_000, &[5_000, 5_000]);
1104        runs(&mut func, made[1], 5_000, &[]);
1105        runs(&mut func, made[2], 5_000, &[]);
1106
1107        traced(&mut func, &mut names);
1108
1109        assert_eq!(order_of(&func), [0, 2, 1]);
1110    }
1111
1112    /// A run of blocks the rounds cut in half comes back out in one piece.
1113    ///
1114    /// Two comparisons against a constant, one behind the other, which is what a switch over
1115    /// scattered labels is lowered to. The second comparison is only reached when the first one
1116    /// failed, so it runs half as often as the function is entered and the first round will not
1117    /// touch it: the trace stops at the first comparison and the block that was about to fall
1118    /// through it is left for a later round. What puts it back is [`connect`], and without it the
1119    /// body of the first case would sit between the two comparisons and both would pay a jump.
1120    #[test]
1121    fn a_chain_the_rounds_cut_in_half_is_run_back_together() {
1122        let (mut names, mut func, made) = blank(5);
1123        branch(&mut func, &mut names, made[0], &[made[2], made[1]]);
1124        branch(&mut func, &mut names, made[2], &[made[4], made[3]]);
1125        runs(&mut func, made[0], 10_000, &[5_000, 5_000]);
1126        runs(&mut func, made[1], 5_000, &[]);
1127        runs(&mut func, made[2], 5_000, &[3_000, 2_000]);
1128        runs(&mut func, made[3], 2_000, &[]);
1129        runs(&mut func, made[4], 3_000, &[]);
1130
1131        traced(&mut func, &mut names);
1132
1133        assert_eq!(order_of(&func), [0, 2, 4, 1, 3]);
1134    }
1135}