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//! Reverse postorder over the CFG, with each block's successors walked in reverse, and anything
14//! unreachable put at the end in block order.
15//!
16//! That is the `-O0` order `spec/10-backend.md` section 10.3 asks for, and it is not arbitrary.
17//! Walking the successors in reverse is what makes the first arm of a branch come out first,
18//! because a depth-first walk finishes its last child first and reverse postorder then puts that
19//! child last. So an `if` with no `else` falls through into its body, and a loop comes out as its
20//! header, its body and then whatever follows it, which is the shape where the back edge is the
21//! only jump in it. The chain construction weighted by block frequency that section 10.6
22//! describes is what replaces this above `-O0`, and it is not written yet.
23//!
24//! Unreachable blocks are laid out rather than deleted. Deleting one is a decision about what the
25//! program does and this pass has no business making it, and a block nothing reaches costs the
26//! bytes it occupies and nothing else.
27//!
28//! # What a block looks like afterwards
29//!
30//! A block still holds where it goes, and it still holds every arm, which is what keeps the
31//! control flow graph readable after this has run. What changes is that the order the arms are in
32//! now means something it did not mean before:
33//!
34//! ```text
35//!   no arms      it returns
36//!   one arm      it falls into that block if that block is next, and jumps to it if not
37//!   two arms     a test and a conditional jump to the first, and the second is always next
38//! ```
39//!
40//! So a jump target is a block without an instruction growing a field for one.
41//! `rucc_mir::InstData` is twenty four bytes by assertion and a block reference does not fit in
42//! it, and every pass over the graph already reads the arms, so putting the target where the
43//! graph already is costs nothing and keeps the two from disagreeing.
44//!
45//! Which arm is which is no longer which way the condition went, because a block that falls into
46//! the arm the condition is true for is a block whose jump has to be taken when it is false. That
47//! is what the two conditional jumps in [`BranchInsts`] are for, and it is why the arms may come
48//! out swapped: what the condition meant is in the opcode afterwards, and what the arms mean is
49//! where the jump goes and what comes next.
50//!
51//! # The block a branch sometimes needs
52//!
53//! A branch whose second arm cannot be laid out next, because both its arms are blocks the walk
54//! has already been to, would need two jumps in one block. Rather than write one, this makes the
55//! block it needs: an empty one on the second edge, laid out immediately after the branch, that
56//! jumps where the edge went. That is exactly the critical edge splitting in [`crate::split`],
57//! done for a different reason, and it costs the same jump the second jump would have cost while
58//! leaving every block with at most one.
59//!
60//! # The test a comparison makes unnecessary
61//!
62//! Almost every branch a C program writes is on a comparison, and a comparison has already set
63//! the flags by the time the byte it wrote is tested against itself. So where the instruction in
64//! front of the branch is that comparison, and the branch is the whole of what reads its byte,
65//! the byte and the test both go and the jump names the condition the comparison was asked about
66//! instead of naming zero. Three instructions become two, and the two are what the machine has a
67//! comparison and a conditional jump for.
68//!
69//! This is where it happens rather than anywhere earlier because of what the flags are. Between
70//! the comparison and the jump they are live and they are not a register: no pass could be told
71//! about them, so no pass may put an instruction between the two. After this one there is no pass
72//! left, which is the whole of the argument, and it is the same argument
73//! `rucc_target::x86_64::Form::CmpSet` is one form rather than two under.
74//!
75//! What this cannot work out for itself is whether the byte has another reader. Every register is
76//! physical by the time this runs and a physical register is written many times in a function, so
77//! the question has to be asked while they are still virtual and written once. [`fusable`] is that
78//! question, asked before allocation, and its answer is one of the arguments to [`blocks`]. The
79//! same arrangement, and for the same reason, as the addresses [`crate::finish`] has still to
80//! write and [`crate::fold`] is handed.
81//!
82//! # Why it runs last
83//!
84//! [`crate::finish`] finds the blocks a function returns from by looking for the ones that go
85//! nowhere. Nothing here creates one of those, but everything here reads and writes the arms, and
86//! a pass that reorders them is one nothing before it should be looking at. Running the layout
87//! after the prologue and the epilogue are in is also what makes the epilogue something it can
88//! lay out around rather than something it has to leave room for.
89
90use std::collections::{HashMap, HashSet};
91
92use rucc_base::Interner;
93use rucc_mir as mir;
94use rucc_target::{BranchInsts, Fusion, Role};
95
96/// Puts a function's blocks in an order and writes the jumps that order needs.
97///
98/// Run last, after [`crate::finish`].
99///
100/// # Panics
101///
102/// Panics on a block with more than two successors, which nothing lowers to yet, and on a block
103/// with two whose last instruction is not the conditional branch the target named. Both are a
104/// function that was built wrongly somewhere earlier, and both are worth finding here rather than
105/// as a jump to the wrong place.
106pub fn blocks(
107    func: &mut mir::Func,
108    insts: &BranchInsts,
109    names: &mut Interner,
110    fusable: &HashSet<mir::Inst>,
111) {
112    let table = table(insts, names);
113    let mut order = order(func);
114    let mut writer = Writer { func, insts, names, table, fusable };
115    let mut at = 0;
116    while at < order.len() {
117        // A branch that can fall into neither arm asks for a block to put the second jump in, and
118        // that block goes immediately after it, which is where the loop reaches it next.
119        if let Some(bridge) = writer.edges(order[at], order.get(at + 1).copied()) {
120            order.insert(at + 1, bridge);
121        }
122        at += 1;
123    }
124    func.set_block_order(&order);
125}
126
127/// The order the blocks are laid out in, which is every block the function has exactly once.
128fn order(func: &mir::Func) -> Vec<mir::Block> {
129    let mut order = Vec::with_capacity(func.block_count());
130    let mut seen = vec![false; func.block_count()];
131    if let Some(entry) = func.entry() {
132        seen[entry.index()] = true;
133        // The walk is explicit rather than recursive because a function with a hundred thousand
134        // blocks in it is a function somebody generated, and it should compile rather than run out
135        // of stack. Each entry is a block and how many of its arms have been started.
136        let mut stack = vec![(entry, 0usize)];
137        while let Some((block, next)) = stack.pop() {
138            let succs = &func[block].succs;
139            let Some(arm) = succs.len().checked_sub(next + 1) else {
140                order.push(block);
141                continue;
142            };
143            stack.push((block, next + 1));
144            let to = succs[arm].block;
145            if !std::mem::replace(&mut seen[to.index()], true) {
146                stack.push((to, 0));
147            }
148        }
149        order.reverse();
150    }
151    // Whatever the walk did not reach, in the order the blocks were made, which is the only order
152    // there is anything to be said for when nothing goes to any of them.
153    order.extend(func.blocks().filter(|block| !seen[block.index()]));
154    order
155}
156
157/// The comparisons a branch may be folded into, which [`blocks`] can then find by opcode.
158///
159/// One entry per name the target's table holds, interned once for the function rather than once
160/// per block, since a block that ends in a branch is most of the blocks there are.
161fn table(insts: &BranchInsts, names: &mut Interner) -> HashMap<mir::Opcode, &'static Fusion> {
162    insts
163        .fused
164        .iter()
165        .map(|fusion| {
166            (mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, fusion.set))), fusion)
167        })
168        .collect()
169}
170
171/// The comparisons a branch on their answer is the whole of what reads, which [`blocks`] may fold
172/// the test out of.
173///
174/// Run before allocation, on the same function [`blocks`] is later given. What it answers is
175/// whether anything but the branch reads the byte a comparison wrote, and that is a question about
176/// a virtual register: a physical one is written many times in a function and counting its readers
177/// would mean asking which of the writes each reader belongs to. So it is asked here, where a
178/// register is written once, and the answer is carried to the pass that can use it.
179///
180/// Being on this list is necessary and not sufficient. Allocation may put a reload between the
181/// comparison and the branch, and a comparison that is no longer the instruction in front of the
182/// branch is not one the flags survive to, so [`blocks`] checks that again on what it finds.
183#[must_use]
184pub fn fusable(func: &mir::Func, insts: &BranchInsts, names: &mut Interner) -> HashSet<mir::Inst> {
185    let table = table(insts, names);
186    let branch = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.cond)));
187    let reads = crate::fold::reads(func);
188    let mut found = HashSet::new();
189    for block in func.blocks() {
190        let insts: Vec<mir::Inst> = func.insts(block).collect();
191        let [.., compare, last] = insts[..] else { continue };
192        if func[last].opcode != branch || !table.contains_key(&func[compare].opcode) {
193            continue;
194        }
195        let operands = &func[func[compare].operands];
196        let Some(byte) = operands.first().filter(|operand| operand.role != Role::Use) else {
197            continue;
198        };
199        if !byte.reg.is_virtual() || reads.get(&byte.reg) != Some(&1) {
200            continue;
201        }
202        // And it is this branch that reads it rather than one in some other block, which the
203        // count alone does not say.
204        if func[func[last].operands].first().map(|operand| operand.reg) == Some(byte.reg) {
205            found.insert(compare);
206        }
207    }
208    found
209}
210
211/// The one thing that writes an instruction here, over the function it writes into.
212struct Writer<'a> {
213    func: &'a mut mir::Func,
214    insts: &'a BranchInsts,
215    names: &'a mut Interner,
216    table: HashMap<mir::Opcode, &'static Fusion>,
217    fusable: &'a HashSet<mir::Inst>,
218}
219
220impl Writer<'_> {
221    /// Writes the jumps one block needs, given the block laid out after it, and gives back the
222    /// block that has to go between the two when the branch needed one.
223    fn edges(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
224        match self.func[block].succs.len() {
225            0 => None,
226            1 => {
227                self.one(block, next);
228                None
229            }
230            2 => self.two(block, next),
231            arms => panic!("a block with {arms} arms, and nothing lowers to one"),
232        }
233    }
234
235    /// A block that goes to one place, which either follows it or has to be jumped to.
236    fn one(&mut self, block: mir::Block, next: Option<mir::Block>) {
237        if Some(self.func[block].succs[0].block) == next {
238            return;
239        }
240        let opcode = self.opcode(self.insts.jump);
241        self.func.build(block, opcode).finish();
242    }
243
244    /// A block that goes to two places, which is a test and a jump to one of them.
245    ///
246    /// The condition is read off the branch the rules selected and the branch is taken out, so the
247    /// register the test reads is the one the branch read and no new value is made. That is what
248    /// makes this safe to run after allocation: it writes no register that was not already
249    /// written and it asks for none that was not already asked for.
250    fn two(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
251        // Asked before the branch is taken out, because what it looks at is the instruction in
252        // front of the branch and taking the branch out would make that the last one.
253        let fused = self.fused(block);
254        let condition = self.take(block);
255
256        // Whichever arm is laid out next is the one the block falls into, and the jump is then
257        // the one taken when the condition sends it the other way. Falling into the arm the
258        // condition is false for leaves the jump taken when it holds, and falling into the arm it
259        // is true for leaves the other jump and the arms the other way round.
260        let (if_true, if_false) = match fused {
261            Some((_, fusion)) => (fusion.if_true, fusion.if_false),
262            None => (self.insts.if_true, self.insts.if_false),
263        };
264        let arms: Vec<mir::Block> = self.func[block].succs.iter().map(|arm| arm.block).collect();
265        let (name, bridge) = if next == Some(arms[1]) {
266            (if_true, None)
267        } else if next == Some(arms[0]) {
268            self.func.succs_mut(block).swap(0, 1);
269            (if_false, None)
270        } else {
271            (if_true, Some(self.bridge(block)))
272        };
273
274        match fused {
275            Some((compare, fusion)) => self.keep_only_the_flags(compare, fusion),
276            None => {
277                let opcode = self.opcode(self.insts.test);
278                self.func.build(block, opcode).operand(condition).finish();
279            }
280        }
281        let opcode = self.opcode(name);
282        self.func.build(block, opcode).finish();
283        bridge
284    }
285
286    /// The comparison the block's branch can be folded into, when there is one.
287    ///
288    /// Three things have to hold and [`fusable`] has already answered the one that cannot be
289    /// answered here. What is left is that the comparison is still the instruction in front of the
290    /// branch, since allocation may have put a reload between them and the flags do not survive
291    /// one, and that the byte the branch reads is the byte that comparison wrote, since the
292    /// allocator has since given both of them a physical register and two registers that were
293    /// different could have become the same one.
294    fn fused(&self, block: mir::Block) -> Option<(mir::Inst, &'static Fusion)> {
295        let insts: Vec<mir::Inst> = self.func.insts(block).collect();
296        let [.., compare, last] = insts[..] else { return None };
297        if !self.fusable.contains(&compare) {
298            return None;
299        }
300        let fusion = *self.table.get(&self.func[compare].opcode)?;
301        let byte = self.func[self.func[compare].operands].first()?.reg;
302        (self.func[self.func[last].operands].first()?.reg == byte).then_some((compare, fusion))
303    }
304
305    /// Turns a comparison that wrote a byte into the same comparison that writes nothing.
306    ///
307    /// The instruction stays where it is and keeps its immediate, which is the point: what it does
308    /// to the flags is what it already did, and the jump written behind it reads those. Only the
309    /// operand at the front goes, which is the byte, and the opcode changes to the one that has no
310    /// operand there.
311    fn keep_only_the_flags(&mut self, compare: mir::Inst, fusion: &Fusion) {
312        let read: Vec<mir::Operand> =
313            self.func[self.func[compare].operands].iter().skip(1).copied().collect();
314        let operands = self.func.push_operands(&read);
315        self.func[compare].opcode = self.opcode(fusion.cmp);
316        self.func[compare].operands = operands;
317    }
318
319    /// Takes the conditional branch off the end of a block and gives back what it read.
320    fn take(&mut self, block: mir::Block) -> mir::Operand {
321        let branch = self.func.terminator(block).expect("a block with two arms has a branch");
322        let cond = self.opcode(self.insts.cond);
323        assert_eq!(
324            self.func[branch].opcode, cond,
325            "a block with two arms whose last instruction is not the branch"
326        );
327        let operands = self.func[branch].operands;
328        let condition = self.func[operands][0];
329        self.func.remove_inst(branch);
330        condition
331    }
332
333    /// Puts an empty block on a branch's second edge, so that the branch has something to fall
334    /// into and the jump the edge really needs is in a block of its own.
335    fn bridge(&mut self, block: mir::Block) -> mir::Block {
336        let bridge = self.func.create_block();
337        let edge = self.func[block].succs[1].clone();
338        *self.func.succs_mut(bridge) = vec![edge];
339        self.func.succs_mut(block)[1] = mir::BlockCall::to(bridge);
340        bridge
341    }
342
343    /// The opcode of that name on this target, which is the name with the target's prefix in
344    /// front of it.
345    fn opcode(&mut self, name: &str) -> mir::Opcode {
346        mir::Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use rucc_mir::{BlockCall, Opcode, Operand, Reg};
353    use rucc_target::x86_64::{BRANCH, GPR, RAX, RCX, REGS};
354
355    use super::*;
356
357    /// A function with that many blocks, none of which goes anywhere yet.
358    fn blank(count: usize) -> (Interner, mir::Func, Vec<mir::Block>) {
359        let mut names = Interner::new();
360        let mut func = mir::Func::new(names.intern("f"));
361        let blocks = (0..count).map(|_| func.create_block()).collect();
362        (names, func, blocks)
363    }
364
365    /// Puts a conditional branch at the end of a block, on a register that is already physical
366    /// the way one is by the time this pass runs.
367    fn branch(func: &mut mir::Func, names: &mut Interner, block: mir::Block, arms: &[mir::Block]) {
368        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
369        func.build(block, opcode).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
370        *func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
371    }
372
373    /// Laying the blocks out for the one machine this crate has, and the dump of what came out.
374    ///
375    /// The dump rather than the function, because where a jump goes is on the block and the dump
376    /// is the one place the instruction and the arm are put back together. A test that read the
377    /// two separately would pass on a function whose jump and whose edge disagreed, which is the
378    /// mistake this pass is most able to make.
379    ///
380    /// A block is named in the dump by where it is in the layout rather than by the number it was
381    /// made with, which is why every expectation below reads that way and why the order is worth
382    /// asserting on its own.
383    fn laid_out(func: &mut mir::Func, names: &mut Interner) -> Vec<String> {
384        // Both halves, in the order the pipeline runs them, so that a test which builds a
385        // comparison in front of its branch sees what a compiled function would see.
386        let fusable = fusable(func, &BRANCH, names);
387        blocks(func, &BRANCH, names, &fusable);
388        mir::print_func(func, names, &REGS)
389            .lines()
390            .filter(|line| !line.trim().is_empty() && !line.starts_with("mfunc") && *line != "}")
391            .map(|line| line.trim().to_string())
392            .collect()
393    }
394
395    /// The blocks in layout order, by the number each was made with.
396    fn order_of(func: &mir::Func) -> Vec<usize> {
397        func.blocks().map(mir::Block::index).collect()
398    }
399
400    #[test]
401    fn a_block_that_falls_into_the_next_one_gets_no_jump_at_all() {
402        let (mut names, mut func, made) = blank(2);
403        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
404
405        let text = laid_out(&mut func, &mut names);
406
407        // The arm is still on the block, because the graph is still worth reading, and there is
408        // no instruction on it because the block it goes to is the one that runs next anyway.
409        assert_eq!(text, ["block0:", "block1", "block1:"]);
410    }
411
412    #[test]
413    fn a_block_that_goes_somewhere_that_is_not_next_gets_a_jump() {
414        let (mut names, mut func, made) = blank(2);
415        // A loop with nothing in it and no way out, which is the smallest function there is with
416        // an edge that runs backwards. Every layout puts the two blocks in this order, so the
417        // second one has nothing after it and its edge has to be a jump.
418        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
419        *func.succs_mut(made[1]) = vec![BlockCall::to(made[0])];
420
421        let text = laid_out(&mut func, &mut names);
422
423        assert_eq!(text, ["block0:", "block1", "block1:", "x64.jmp block0"]);
424    }
425
426    #[test]
427    fn a_branch_that_falls_into_its_false_arm_jumps_when_the_condition_holds() {
428        let (mut names, mut func, made) = blank(3);
429        // A loop whose body is the block it came from: the arm taken when the condition holds is
430        // a block the walk has already been to, so the other arm is what comes next.
431        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
432        branch(&mut func, &mut names, made[1], &[made[0], made[2]]);
433
434        let text = laid_out(&mut func, &mut names);
435
436        assert_eq!(order_of(&func), [0, 1, 2]);
437        assert_eq!(
438            text,
439            [
440                "block0:",
441                "block1",
442                "block1:",
443                "x64.test_rr_8 $rax",
444                "x64.jcc_ne block0, block2",
445                "block2:",
446            ]
447        );
448    }
449
450    #[test]
451    fn a_branch_that_falls_into_its_true_arm_jumps_when_the_condition_does_not_hold() {
452        let (mut names, mut func, made) = blank(3);
453        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
454
455        let text = laid_out(&mut func, &mut names);
456
457        // The arms come out swapped, because after this the first is where the jump goes and the
458        // second is what runs next, and the jump is the one taken when the condition failed.
459        assert_eq!(order_of(&func), [0, 1, 2]);
460        assert_eq!(
461            text,
462            ["block0:", "x64.test_rr_8 $rax", "x64.jcc_e block2, block1", "block1:", "block2:"]
463        );
464    }
465
466    #[test]
467    fn a_branch_that_can_fall_into_neither_arm_is_given_a_block_to_jump_from() {
468        let (mut names, mut func, made) = blank(2);
469        // A loop that goes back to the top or round again, so both arms are blocks the walk has
470        // already been to and nothing is left to lay out after it.
471        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
472        branch(&mut func, &mut names, made[1], &[made[0], made[1]]);
473
474        let text = laid_out(&mut func, &mut names);
475
476        // Block two is the one this made. It is empty, it is laid out where the branch falls into
477        // it, and the jump the second arm needed is in it rather than being a second jump in the
478        // block above.
479        assert_eq!(order_of(&func), [0, 1, 2]);
480        assert_eq!(
481            text,
482            [
483                "block0:",
484                "block1",
485                "block1:",
486                "x64.test_rr_8 $rax",
487                "x64.jcc_ne block0, block2",
488                "block2:",
489                "x64.jmp block1",
490            ]
491        );
492    }
493
494    #[test]
495    fn the_test_reads_the_register_the_branch_read() {
496        let (mut names, mut func, made) = blank(3);
497        branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
498
499        let fusable = fusable(&func, &BRANCH, &mut names);
500        blocks(&mut func, &BRANCH, &mut names, &fusable);
501
502        let test = func.insts(made[0]).next().expect("a test");
503        let operands = func[test].operands;
504        assert_eq!(func[operands], [Operand::read(Reg::physical(RAX), GPR)]);
505    }
506
507    #[test]
508    fn a_block_nothing_reaches_is_laid_out_at_the_end_rather_than_deleted() {
509        let (mut names, mut func, made) = blank(4);
510        *func.succs_mut(made[0]) = vec![BlockCall::to(made[3])];
511
512        let fusable = fusable(&func, &BRANCH, &mut names);
513        blocks(&mut func, &BRANCH, &mut names, &fusable);
514
515        // Blocks one and two are reached by nothing, so they go last, in the order they were
516        // made. Deleting one would be a decision about what the program does, and this pass has
517        // no business making it.
518        assert_eq!(order_of(&func), [0, 3, 1, 2]);
519    }
520
521    #[test]
522    fn a_function_with_no_blocks_is_left_alone() {
523        let mut names = Interner::new();
524        let mut func = mir::Func::new(names.intern("f"));
525
526        let fusable = fusable(&func, &BRANCH, &mut names);
527        blocks(&mut func, &BRANCH, &mut names, &fusable);
528
529        assert_eq!(func.block_count(), 0);
530    }
531
532    #[test]
533    #[should_panic(expected = "a block with 3 arms")]
534    fn a_block_with_three_arms_is_refused_rather_than_laid_out_wrongly() {
535        let (mut names, mut func, made) = blank(4);
536        branch(&mut func, &mut names, made[0], &[made[1], made[2], made[3]]);
537
538        let fusable = fusable(&func, &BRANCH, &mut names);
539        blocks(&mut func, &BRANCH, &mut names, &fusable);
540    }
541
542    #[test]
543    #[should_panic(expected = "whose last instruction is not the branch")]
544    fn a_block_with_two_arms_and_no_branch_in_it_is_refused() {
545        let (mut names, mut func, made) = blank(3);
546        let opcode = Opcode::new(names.intern("x64.nop"));
547        func.build(made[0], opcode).finish();
548        *func.succs_mut(made[0]) = vec![BlockCall::to(made[1]), BlockCall::to(made[2])];
549
550        let fusable = fusable(&func, &BRANCH, &mut names);
551        blocks(&mut func, &BRANCH, &mut names, &fusable);
552    }
553
554    /// Puts a comparison and a branch on its answer at the end of a block.
555    ///
556    /// The byte is a virtual register, which is what it is when [`fusable`] is asked and is not
557    /// what it is when [`blocks`] runs. Nothing in either half cares which it is except the
558    /// counting, so a test that runs both over one function has to use the register the counting
559    /// wants, and what it costs is that this is one thing the unit tests cannot check about the
560    /// two halves running at different times. `crate::pipeline` runs them the real way round.
561    fn compare(
562        func: &mut mir::Func,
563        names: &mut Interner,
564        block: mir::Block,
565        arms: &[mir::Block],
566    ) -> Reg {
567        let byte = func.new_vreg(GPR);
568        let opcode = Opcode::new(names.intern("x64.cmp_set_l_32"));
569        func.build(block, opcode)
570            .def(byte, GPR)
571            .operand(Operand::read(Reg::physical(RAX), GPR))
572            .operand(Operand::read(Reg::physical(RCX), GPR))
573            .finish();
574        let opcode = Opcode::new(names.intern("x64.br_cond_8"));
575        func.build(block, opcode).operand(Operand::read(byte, GPR)).finish();
576        *func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
577        byte
578    }
579
580    /// A branch on a comparison is the comparison and a jump on what it found.
581    ///
582    /// Three instructions go in and two come out. The byte goes because nothing reads it, the test
583    /// goes because the comparison set the flags the test was going to set, and the jump names the
584    /// condition rather than naming zero. Which condition it names is the opposite of the one the
585    /// comparison asked about, since the block falls into the arm the comparison is true for.
586    #[test]
587    fn a_branch_on_a_comparison_is_the_comparison_and_a_jump_on_what_it_found() {
588        let (mut names, mut func, made) = blank(3);
589        compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
590
591        let text = laid_out(&mut func, &mut names);
592
593        assert_eq!(
594            text,
595            [
596                "block0:",
597                "x64.cmp_rr_32 $rax, $rcx",
598                "x64.jcc_ge block2, block1",
599                "block1:",
600                "block2:",
601            ]
602        );
603    }
604
605    /// The same comparison with something else reading its answer, which keeps everything.
606    ///
607    /// Folding the byte away when a second instruction wants it would be deleting a value the
608    /// program computes. This is the whole of what [`fusable`] is asked before allocation, and the
609    /// second reader here is in another block so that it is a question about the function rather
610    /// than about the block the branch is in.
611    #[test]
612    fn a_comparison_whose_answer_something_else_reads_keeps_its_byte_and_its_test() {
613        let (mut names, mut func, made) = blank(3);
614        let byte = compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
615        let opcode = Opcode::new(names.intern("x64.mov_rr_64"));
616        func.build(made[1], opcode)
617            .def(Reg::physical(RAX), GPR)
618            .operand(Operand::read(byte, GPR))
619            .finish();
620
621        let text = laid_out(&mut func, &mut names);
622
623        assert!(text.contains(&"x64.test_rr_8 %0".to_owned()), "{text:?}");
624        assert!(text.contains(&"x64.jcc_e block2, block1".to_owned()), "{text:?}");
625    }
626
627    /// A comparison allocation moved away from its branch, which keeps its test.
628    ///
629    /// [`fusable`] says the byte has one reader and says nothing about where the two instructions
630    /// end up, because allocation runs between the two halves and may put a reload in front of the
631    /// branch. The flags do not survive one, so the second half looks again, and this is the case
632    /// where it finds something and refuses. The instruction is put in between the two calls
633    /// because that is when allocation would have put it there.
634    #[test]
635    fn a_comparison_that_is_no_longer_in_front_of_its_branch_keeps_its_test() {
636        let (mut names, mut func, made) = blank(3);
637        compare(&mut func, &mut names, made[0], &[made[1], made[2]]);
638        let fusable = fusable(&func, &BRANCH, &mut names);
639        assert_eq!(fusable.len(), 1, "the comparison is one the byte's count allows");
640
641        let branch = func.terminator(made[0]).expect("a block with two arms has a branch");
642        let opcode = Opcode::new(names.intern("x64.mov_rr_64"));
643        let reload = func
644            .build_loose(opcode)
645            .def(Reg::physical(RCX), GPR)
646            .operand(Operand::read(Reg::physical(RAX), GPR))
647            .finish();
648        func.insert_before(branch, reload);
649        blocks(&mut func, &BRANCH, &mut names, &fusable);
650        let text = mir::print_func(&func, &names, &REGS);
651
652        assert!(text.contains("x64.cmp_set_l_32"), "{text}");
653        assert!(text.contains("x64.test_rr_8"), "{text}");
654        assert!(!text.contains("x64.cmp_rr_32"), "{text}");
655    }
656}