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, ®S)
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, ®S);
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}