Skip to main content

rucc_codegen/
split.rs

1//! Splitting critical edges, so that every edge that carries values has somewhere to put them.
2//!
3//! Design: `spec/10-backend.md` section 10.4.
4//!
5//! An edge carries values when the block it goes to takes parameters, and giving a parameter its
6//! value is a move. The move has to happen on the edge and not before it or after it, because
7//! before it is a block that goes somewhere else too and after it is a block that is arrived at
8//! from somewhere else too, and in either case the move would run on a path it was not written
9//! for. An edge out of a block with one successor can put its moves at the end of that block,
10//! since every path through it takes the edge. An edge into a block with one predecessor can put
11//! them at the start of that block, for the same reason the other way round. An edge that is
12//! neither, which is what a critical edge is, has neither place, and the allocator says so:
13//! `rucc_regalloc` asserts that it never sees one.
14//!
15//! So one is turned into two. A block with nothing in it goes on the edge, the arguments move on
16//! to the second half, and both halves are now uncritical: the first goes to a block with one
17//! predecessor and the second leaves a block with one successor. Which of the two the moves end
18//! up in is the allocator's answer and not this one's, and either is correct.
19//!
20//! # What it leaves behind
21//!
22//! An empty block, which is a jump to the next thing unless the layout puts it where it falls
23//! through. That is a cost, and it is why an edge with nothing to carry is left alone: there are
24//! no moves to find a place for, so splitting it would buy a jump and nothing else.
25//!
26//! # The other edge with nowhere to put a move
27//!
28//! A computed `goto` leaves its block through a register, and the moves an edge out of it carries
29//! would have to be written somewhere the jump has already gone past. So there is a second pass
30//! here, [`indirect`], which takes the values off those edges and puts them in a block of their
31//! own in front of each label. It runs first, and what it leaves behind is edges the splitting
32//! below then has nothing to do about.
33//!
34//! [`pads`] is here for the same reason and not for a reason of its own: the blocks those labels
35//! begin at are addresses an indirect branch arrives at, and a machine that checks the forward edge
36//! wants a landing pad at every one of them. Which block an address names is settled by the pass
37//! above, so the pad is written after it and not where the prologue's own pad is written.
38
39use rucc_base::Interner;
40use rucc_base::hash::Map;
41use rucc_mir as mir;
42use rucc_target::{BranchInsts, FrameInsts, RegClass};
43
44/// Splits every critical edge that carries values, and gives back how many it split.
45///
46/// Run after lowering and before allocation. Running it twice is running it once, because the
47/// blocks it adds have one successor each and are never the source of a critical edge.
48pub fn critical(func: &mut mir::Func) -> usize {
49    let preds = preds(func);
50    let blocks: Vec<mir::Block> = func.blocks().collect();
51    let mut split = 0;
52    for block in blocks {
53        if func[block].succs.len() < 2 {
54            continue;
55        }
56        for index in 0..func[block].succs.len() {
57            let call = func[block].succs[index].clone();
58            if call.args.is_empty() || preds[call.block.index()] < 2 {
59                continue;
60            }
61            // The new block is at the end of the layout, which is where a block that is a jump
62            // and nothing else does the least harm before the layout pass has an opinion.
63            //
64            // It runs exactly as often as the edge it sits on is taken, and both halves of that
65            // edge are now that edge, which is why the weight is copied onto all three rather
66            // than left at what a block nobody told anything runs. A block on a cold edge that
67            // claimed to run once per call would be one the layout put in the middle of the hot
68            // path.
69            let weight = call.weight;
70            let half = func.create_block();
71            func.set_weight(half, weight);
72            *func.succs_mut(half) = vec![call];
73            func.succs_mut(block)[index] = mir::BlockCall::to(half).taken(weight);
74            split += 1;
75        }
76    }
77    split
78}
79
80/// Takes the values off every edge out of a computed `goto`, and gives back how many blocks it
81/// made to hold them.
82///
83/// Run after lowering and before [`critical`], which then sees edges with nothing on them and
84/// leaves them alone. Running it twice is running it once, for the reason the splitting above is:
85/// the blocks it adds end in a jump rather than in a branch through a register.
86///
87/// # What is wrong with the edge it takes the values off
88///
89/// Every other edge in the function is out of a block whose last instruction the layout writes, so
90/// an edge that is the only way out of its block can put its moves at the end of that block and
91/// they land in front of the jump. A block that leaves through a register already ends in the jump
92/// when the allocator runs, because where it goes is a value and a value is something selection
93/// reads rather than something the layout knows. Moves at the end of that block would be written
94/// after the jump, where nothing runs them, and moves in front of it would be written across the
95/// register the jump reads, which the allocator believes is dead from the jump onwards and is free
96/// to hand to one of the moves.
97///
98/// So the moves go somewhere else. Each label an indirect branch reaches gets a block in front of
99/// it that carries the values, the branch goes to that block with nothing on the edge, and the
100/// address the `&&label` produces is the address of that block rather than of the label's own. The
101/// new block is arrived at one way and leaves one way, so its own edge has both of the places the
102/// splitting above talks about and the allocator is content.
103///
104/// # One label, one address, and two branches that disagree
105///
106/// A label has one address, so two computed `goto`s that reach it both arrive at whatever block
107/// that address names, and the values they carry are not the same values. One block in front of
108/// the label cannot move two different sets of registers.
109///
110/// So they are made to agree first. Each parameter of the label gets a register of its own, every
111/// branch writes that register in front of its jump, and the block in front of the label carries
112/// those registers and nothing else. That is what gcc does about the same problem, which it calls
113/// coalescing across an abnormal edge, done here rather than while the values are still the
114/// optimizer's.
115///
116/// Writing them in front of the jump is safe, which is not obvious, since a branch that goes five
117/// ways writes the registers of one of those ways on the path to all five. What makes it safe is
118/// that nothing reads those registers except the block in front of the label, and the only way to
119/// reach that block is an edge out of a branch, which writes them on the way. So a value written
120/// here and not used is a value overwritten before anything looks, whichever way the jump went.
121///
122/// # One register for one value, and not one for every place it is given to
123///
124/// That safety is also what makes the cost of it worth watching. A branch writes the registers of
125/// every label it can reach, so a register for every parameter of every label is a whole table's
126/// worth of moves in front of every jump in the function, and a dispatch table is a branch that
127/// reaches hundreds of labels. An interpreter hands each of them whatever its loop had in hand at
128/// the jump, which is the same few values over and over, so a register for each place one of them
129/// lands means those values written a hundred times over before every instruction the interpreter
130/// runs. That is not a small constant. It is what makes an interpreter built this way ten times
131/// slower than the same interpreter built with a `switch` instead of the computed `goto`.
132///
133/// So the register belongs to the value rather than to the place. Two parameters are given one
134/// register when they are drawn from the same class and every branch in the function gives them
135/// the same register, which is exactly when one register can stand for both, and a branch writes
136/// each register it has to write once however many labels asked for it. A dispatch table where
137/// every label wants the instruction pointer writes the instruction pointer once. A label
138/// something else reaches, or a label given something no other label is given, keeps a register of
139/// its own, and a branch that gives one label nothing shares nothing with it, since a register
140/// that branch never wrote is not one the label can be given.
141///
142/// # Panics
143///
144/// Panics on a class of register the machine named no move for, which is a function carrying a
145/// value of a kind the target never said how to copy, and on a branch that has lost the terminator
146/// it was found by, which nothing between the finding and the use of it can do. Both are a target
147/// description or a function that was built wrongly, and both are worth finding here rather than as
148/// a value that arrives somewhere it was never written.
149pub fn indirect(
150    func: &mut mir::Func,
151    branch: &BranchInsts,
152    frame: &FrameInsts,
153    names: &mut Interner,
154) -> usize {
155    let jump = mir::Opcode::new(names.intern(&format!("{}{}", branch.prefix, branch.indirect)));
156    let branches: Vec<mir::Block> = func
157        .blocks()
158        .filter(|&block| func.terminator(block).is_some_and(|last| func[last].opcode == jump))
159        .collect();
160    // Nothing at all in almost every function, and the walk at the bottom is over every instruction
161    // in it, so the answer is arrived at here rather than paid for everywhere.
162    if branches.is_empty() {
163        return 0;
164    }
165    // In the order the branches name them rather than in whatever order a hash gives, so that two
166    // runs of the compiler over one program write the same blocks.
167    let mut targets: Vec<mir::Block> = Vec::new();
168    for &block in &branches {
169        for call in &func[block].succs {
170            if !call.args.is_empty() && !targets.contains(&call.block) {
171                targets.push(call.block);
172            }
173        }
174    }
175
176    // One register per thing a branch has to give, rather than one per place it is given to. The
177    // key is what every branch gives that parameter, so two parameters given the same register by
178    // the same branches are given it in one register and a branch writes that register once.
179    let mut homes: Map<Given, mir::Reg> = Map::default();
180    // What each branch writes in front of its jump, in the order it was first asked for, and never
181    // the same register twice. Two parameters that share a register are given it by the one move.
182    let mut writes: Vec<Vec<(mir::Reg, mir::Reg, RegClass)>> = vec![Vec::new(); branches.len()];
183    let mut entries: Map<mir::Block, mir::Block> = Map::default();
184
185    for target in targets {
186        let params = func[target].params.clone();
187        let given = given(func, &branches, target);
188        let mut carried: Vec<mir::Reg> = Vec::new();
189        for (index, param) in params.iter().enumerate() {
190            let key: Given = (
191                param.class,
192                given.iter().map(|edges| edges.iter().map(|args| args[index]).collect()).collect(),
193            );
194            let home = match homes.get(&key) {
195                Some(&home) => home,
196                None => {
197                    // As narrow as the widest thing it is given, since what it holds is one of
198                    // them, and the whole register when any of them is.
199                    let widths = key.1.iter().flatten().map(|&arg| func.width(arg));
200                    let width =
201                        widths.collect::<Option<Vec<u8>>>().and_then(|all| all.into_iter().max());
202                    let home = func.new_vreg(param.class);
203                    func.set_width(home, width.map_or(0, u32::from));
204                    homes.insert(key, home);
205                    home
206                }
207            };
208            carried.push(home);
209            for (branch, edges) in given.iter().enumerate() {
210                for args in edges {
211                    if !writes[branch].iter().any(|&(written, _, _)| written == home) {
212                        writes[branch].push((home, args[index], param.class));
213                    }
214                }
215            }
216        }
217        let entry = func.create_block();
218        let mut total = mir::Weight::NEVER;
219        for &block in &branches {
220            for index in 0..func[block].succs.len() {
221                if func[block].succs[index].block != target {
222                    continue;
223                }
224                // The block in front of the label runs as often as every branch that reaches it,
225                // which is the same sum the weight of a block with that many edges into it would
226                // be.
227                let weight = func[block].succs[index].weight;
228                total = mir::Weight::parts(total.raw().saturating_add(weight.raw()));
229                func.succs_mut(block)[index] = mir::BlockCall::to(entry).taken(weight);
230            }
231        }
232        func.set_weight(entry, total);
233        *func.succs_mut(entry) = vec![mir::BlockCall::with(target, carried).taken(total)];
234        entries.insert(target, entry);
235    }
236
237    // And the moves themselves, once every label has asked for what it wants, since what one label
238    // asks for is what another may already have asked the same branch for.
239    for (branch, moves) in branches.iter().zip(&writes) {
240        let last = func.terminator(*branch).expect("a block that ends in a jump");
241        for &(home, arg, class) in moves {
242            let name = frame.moves(class).expect("a class this machine can move").mov;
243            let opcode = mir::Opcode::new(names.intern(&format!("{}{name}", frame.prefix)));
244            let inst = func.build_loose(opcode).def(home, class).uses(arg, class).finish();
245            func.insert_before(last, inst);
246        }
247    }
248
249    // And the addresses, which is the half of this that is not about edges. Every `&&label` in the
250    // function names a block, and a label with a block in front of it now begins at that block, so
251    // an address left pointing at the label's own block would be a jump past the moves.
252    let mut addresses: Vec<mir::MemRef> = Vec::new();
253    for block in func.blocks() {
254        for inst in func.insts(block) {
255            if let Some(mem) = func[inst].mem {
256                addresses.push(mem);
257            }
258        }
259    }
260    for mem in addresses {
261        if let Some(named) = func[mem].block {
262            if let Some(&entry) = entries.get(&named) {
263                func[mem].block = Some(entry);
264            }
265        }
266    }
267    // And the names, for the same reason. A block an image points at is one a `goto *p` arrives at,
268    // so a name left on the label's own block would be an address in a table that skips the moves,
269    // which is the one way into the block that would not have made them.
270    for (block, _) in &mut func.labels {
271        if let Some(&entry) = entries.get(block) {
272            *block = entry;
273        }
274    }
275    entries.len()
276}
277
278/// Puts a landing pad at the front of every block whose address is taken, and gives back how many
279/// it wrote.
280///
281/// Run after [`indirect`], because the block an address names is not settled until that has moved
282/// the addresses on to the blocks it made, and only when the command line asked for the forward
283/// edge to be checked. Nothing is written otherwise, which is why the name comes in as an option
284/// and why a target with no such instruction is a target this does nothing on.
285///
286/// The pad a prologue opens with is written elsewhere, in `crate::finish`, because the address it
287/// makes reachable is the address of the function rather than a place inside it. These are the
288/// other addresses an indirect branch may arrive at, and a machine that checks the forward edge
289/// faults on one that has no pad, so a computed `goto` compiled without this would be a program
290/// that ran everywhere except on the hardware the flag was turned on for.
291pub fn pads(
292    func: &mut mir::Func,
293    frame: &FrameInsts,
294    landing: Option<&'static str>,
295    names: &mut Interner,
296) -> usize {
297    let Some(name) = landing else { return 0 };
298    let opcode = mir::Opcode::new(names.intern(&format!("{}{name}", frame.prefix)));
299    let mut addressed: Vec<mir::Block> = Vec::new();
300    for block in func.blocks() {
301        for inst in func.insts(block) {
302            if let Some(mem) = func[inst].mem {
303                if let Some(named) = func[mem].block {
304                    if !addressed.contains(&named) {
305                        addressed.push(named);
306                    }
307                }
308            }
309        }
310    }
311    // And every arm of a jump table, which an indirect jump arrives at the same way.
312    for table in &func.tables {
313        let Some(jump) = func.block_of(table.jump) else { continue };
314        for &cell in &table.cells {
315            let named = func[jump].succs[cell as usize].block;
316            if !addressed.contains(&named) {
317                addressed.push(named);
318            }
319        }
320    }
321    for &block in &addressed {
322        let inst = func.build_loose(opcode).finish();
323        func.prepend_inst(block, inst);
324    }
325    addressed.len()
326}
327
328/// What decides whether two parameters can be given their value in one register: the class the
329/// parameter is drawn from, and the register every branch in the function gives it, in the order
330/// the branches are in and with one entry per edge inside that. A branch that does not reach the
331/// label gives nothing, which is a length of zero and is as much a part of the answer as a
332/// register is, since sharing with a parameter a branch never gives anything to would be reading a
333/// register that branch never wrote.
334type Given = (RegClass, Vec<Vec<mir::Reg>>);
335
336/// What each branch gives that label, edge by edge.
337///
338/// One entry per branch and in the branches' own order, since a label two branches reach and a
339/// label one branch reaches twice are not given the same thing. A branch is allowed to reach one
340/// label twice, which a table with the same label in two of its cells is, so what a branch gives
341/// is a list of what it gives rather than one set of registers.
342fn given(func: &mir::Func, branches: &[mir::Block], target: mir::Block) -> Vec<Vec<Vec<mir::Reg>>> {
343    branches
344        .iter()
345        .map(|&block| {
346            func[block]
347                .succs
348                .iter()
349                .filter(|call| call.block == target)
350                .map(|call| call.args.clone())
351                .collect()
352        })
353        .collect()
354}
355
356/// How many edges arrive at each block, counted by index rather than in layout order so that a
357/// block added while splitting can be looked up in the same table.
358fn preds(func: &mir::Func) -> Vec<usize> {
359    let mut counts = vec![0; func.block_count()];
360    for block in func.blocks() {
361        for call in &func[block].succs {
362            counts[call.block.index()] += 1;
363        }
364    }
365    counts
366}
367
368#[cfg(test)]
369mod tests {
370    use rucc_base::Interner;
371    use rucc_target::x86_64::{BRANCH, FRAME, GPR, REGS};
372
373    use super::*;
374
375    /// A diamond: one block that goes two ways and one block both ways arrive at, with as many
376    /// parameters on the block they arrive at as the test asks for.
377    fn diamond(params: usize) -> (Interner, mir::Func, [mir::Block; 4]) {
378        let mut names = Interner::new();
379        let mut func = mir::Func::new(names.intern("f"));
380        let head = func.create_block();
381        let left = func.create_block();
382        let right = func.create_block();
383        let join = func.create_block();
384        // The values arrive in the head, so that they have somewhere to be defined and the
385        // printer has a name for them. Nothing here runs an allocator, which is the one thing
386        // that would object to a first block with parameters.
387        let args: Vec<mir::Reg> = (0..params).map(|_| func.append_param(head, GPR)).collect();
388        for _ in 0..params {
389            func.append_param(join, GPR);
390        }
391        *func.succs_mut(head) = vec![mir::BlockCall::to(left), mir::BlockCall::to(right)];
392        *func.succs_mut(left) = vec![mir::BlockCall::with(join, args.clone())];
393        *func.succs_mut(right) = vec![mir::BlockCall::with(join, args)];
394        (names, func, [head, left, right, join])
395    }
396
397    /// Where each block goes, which is the whole of what this changes.
398    fn edges(func: &mir::Func) -> Vec<Vec<usize>> {
399        func.blocks()
400            .map(|block| func[block].succs.iter().map(|call| call.block.index()).collect())
401            .collect()
402    }
403
404    #[test]
405    fn an_edge_that_is_the_only_way_out_is_left_alone() {
406        let (_, mut func, _) = diamond(1);
407        // The two edges into the join carry a value each and neither is critical, because the
408        // block each leaves goes nowhere else.
409        assert_eq!(critical(&mut func), 0);
410        assert_eq!(edges(&func), vec![vec![1, 2], vec![3], vec![3], vec![]]);
411    }
412
413    #[test]
414    fn a_critical_edge_carrying_a_value_is_split_in_two() {
415        let (_, mut func, [head, _, _, join]) = diamond(1);
416        // Now the head goes straight to the join as well, so both of its arms are critical: it
417        // has two ways out and the join has three ways in.
418        let arg = func.append_param(head, GPR);
419        func.succs_mut(head).push(mir::BlockCall::with(join, vec![arg]));
420        func.succs_mut(head).swap(1, 2);
421
422        assert_eq!(critical(&mut func), 1);
423        assert_eq!(
424            edges(&func),
425            // The head's second arm is the new block and the new block goes to the join. The
426            // other two arms are untouched, because each goes to a block with one way in.
427            vec![vec![1, 4, 2], vec![3], vec![3], vec![], vec![3]]
428        );
429    }
430
431    #[test]
432    fn a_critical_edge_carrying_nothing_is_left_alone() {
433        let (_, mut func, [head, _, _, join]) = diamond(0);
434        func.succs_mut(head).push(mir::BlockCall::to(join));
435
436        // Critical and not split, because there is no move to find a place for and a block that
437        // is a jump and nothing else is worth more than nothing.
438        assert_eq!(critical(&mut func), 0);
439    }
440
441    #[test]
442    fn the_arguments_move_on_to_the_half_that_arrives() {
443        let (names, mut func, [head, _, _, join]) = diamond(1);
444        let arg = func.append_param(head, GPR);
445        func.succs_mut(head).push(mir::BlockCall::with(join, vec![arg]));
446
447        assert_eq!(critical(&mut func), 1);
448        // What the first half carries is nothing, since the block it goes to asks for nothing,
449        // and what the second half carries is what the whole edge used to.
450        let half = func.blocks().last().expect("the block the split added");
451        assert_eq!(func[head].succs[2].args, Vec::new());
452        assert_eq!(func[half].succs[0].args, vec![arg]);
453        assert_eq!(
454            mir::print_func(&func, &names, &REGS),
455            "mfunc @f {\nblock0(%0:gpr, %1:gpr):\n    block1, block2, block4\n\n\
456             block1:\n    block3(%0)\n\nblock2:\n    block3(%0)\n\n\
457             block3(%2:gpr):\n\nblock4:\n    block3(%1)\n}\n"
458        );
459    }
460
461    #[test]
462    fn splitting_twice_is_splitting_once() {
463        let (_, mut func, [head, _, _, join]) = diamond(1);
464        let arg = func.append_param(head, GPR);
465        func.succs_mut(head).push(mir::BlockCall::with(join, vec![arg]));
466
467        assert_eq!(critical(&mut func), 1);
468        assert_eq!(critical(&mut func), 0);
469    }
470
471    /// A function with one label whose address is taken and as many blocks leaving through that
472    /// address as the test asks for, each carrying as many values to the label as it asks for.
473    fn computed(branches: usize, params: usize) -> (Interner, mir::Func) {
474        let mut names = Interner::new();
475        let mut func = mir::Func::new(names.intern("f"));
476        let head = func.create_block();
477        let label = func.create_block();
478        for _ in 0..params {
479            func.append_param(label, GPR);
480        }
481        let lea = mir::Opcode::new(names.intern("x64.lea_64"));
482        let jump = mir::Opcode::new(names.intern("x64.jmp_reg"));
483        for _ in 0..branches {
484            // Every branch works the address out for itself, which is what a program that takes
485            // the address of a label twice looks like once the values are in registers.
486            let args: Vec<mir::Reg> = (0..params).map(|_| func.append_param(head, GPR)).collect();
487            let address = func.new_vreg(GPR);
488            let at = if branches == 1 { head } else { func.create_block() };
489            func.build(at, lea).def(address, GPR).mem(mir::Mem::block(label)).finish();
490            func.build(at, jump).operand(mir::Operand::read(address, GPR)).finish();
491            *func.succs_mut(at) = vec![mir::BlockCall::with(label, args)];
492        }
493        (names, func)
494    }
495
496    /// Which block each address in the function names, in the order the instructions are in.
497    fn addressed(func: &mir::Func) -> Vec<usize> {
498        func.blocks()
499            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
500            .filter_map(|inst| func[inst].mem)
501            .filter_map(|mem| func[mem].block)
502            .map(mir::Block::index)
503            .collect()
504    }
505
506    #[test]
507    fn the_values_a_computed_goto_carries_move_into_a_block_in_front_of_the_label() {
508        let (mut names, mut func) = computed(1, 1);
509        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 1);
510
511        // The branch goes to the new block carrying nothing, and the new block carries the value
512        // the branch used to. The address the `lea` works out is the new block's as well, since
513        // arriving at the label without going through the new block is arriving without the value.
514        assert_eq!(edges(&func), vec![vec![2], vec![], vec![1]]);
515        assert_eq!(func[mir::Block::new(0)].succs[0].args, Vec::new());
516        assert_eq!(addressed(&func), vec![2]);
517    }
518
519    #[test]
520    fn two_computed_gotos_that_reach_one_label_are_made_to_agree() {
521        let (mut names, mut func) = computed(2, 1);
522        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 1);
523
524        // One block in front of the label and not two, because the label has one address and both
525        // branches arrive at it. What makes that sound is the move each branch writes in front of
526        // its own jump, which puts its value in the register that block carries.
527        assert_eq!(edges(&func), vec![vec![], vec![], vec![4], vec![4], vec![1]]);
528        let text = mir::print_func(&func, &names, &REGS);
529        assert_eq!(text.matches("x64.mov_rr_64").count(), 2, "{text}");
530        // In front of the jump rather than behind it, since nothing behind a jump runs.
531        for line in text.lines().collect::<Vec<_>>().windows(2) {
532            if line[1].contains("x64.jmp_reg") {
533                assert!(line[0].contains("x64.mov_rr_64"), "{text}");
534            }
535        }
536        assert_eq!(addressed(&func), vec![4, 4]);
537    }
538
539    /// A function with one computed `goto` that reaches as many labels as the test asks for, each
540    /// given the one value the branch has in hand, which is the shape of a dispatch table.
541    fn table(labels: usize) -> (Interner, mir::Func, Vec<mir::Block>) {
542        let mut names = Interner::new();
543        let mut func = mir::Func::new(names.intern("f"));
544        let head = func.create_block();
545        let arg = func.append_param(head, GPR);
546        let lea = mir::Opcode::new(names.intern("x64.lea_64"));
547        let jump = mir::Opcode::new(names.intern("x64.jmp_reg"));
548        let mut targets = Vec::new();
549        for _ in 0..labels {
550            let label = func.create_block();
551            func.append_param(label, GPR);
552            targets.push(label);
553            func.succs_mut(head).push(mir::BlockCall::with(label, vec![arg]));
554        }
555        let address = func.new_vreg(GPR);
556        func.build(head, lea).def(address, GPR).mem(mir::Mem::block(targets[0])).finish();
557        func.build(head, jump).operand(mir::Operand::read(address, GPR)).finish();
558        (names, func, targets)
559    }
560
561    #[test]
562    fn labels_a_branch_gives_the_same_value_are_given_it_in_one_register() {
563        let (mut names, mut func, _) = table(8);
564        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 8);
565
566        // One move in front of the jump and not eight, because the eight labels are given the one
567        // value and it is now in the one register. Eight blocks were still made, since each label
568        // needs the block that moves that register on to its own parameter.
569        let text = mir::print_func(&func, &names, &REGS);
570        assert_eq!(text.matches("x64.mov_rr_64").count(), 1, "{text}");
571    }
572
573    #[test]
574    fn a_label_given_something_else_keeps_a_register_of_its_own() {
575        let (mut names, mut func, targets) = table(8);
576        let head = mir::Block::new(0);
577        let other = func.append_param(head, GPR);
578        let last = func[head].succs.len() - 1;
579        func.succs_mut(head)[last] = mir::BlockCall::with(targets[7], vec![other]);
580
581        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 8);
582        // Two moves: one register for the seven labels given the same value, and one for the label
583        // given the other. Sharing is about what a label is given and not about how many there are.
584        let text = mir::print_func(&func, &names, &REGS);
585        assert_eq!(text.matches("x64.mov_rr_64").count(), 2, "{text}");
586    }
587
588    #[test]
589    fn labels_that_take_different_numbers_of_values_still_share_the_ones_they_agree_on() {
590        let (mut names, mut func, targets) = table(8);
591        let head = mir::Block::new(0);
592        let arg = func[head].params[0].reg;
593        let other = func.append_param(head, GPR);
594        // The last label takes a second value, which is what an interpreter looks like: each of
595        // its labels uses what it needs and no two of them need quite the same list.
596        func.append_param(targets[7], GPR);
597        let last = func[head].succs.len() - 1;
598        func.succs_mut(head)[last] = mir::BlockCall::with(targets[7], vec![arg, other]);
599
600        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 8);
601        // Two moves, not nine. The first parameter of the long label is given what the other seven
602        // are given, so it takes the same register, and only the value nothing else is given needs
603        // one of its own.
604        let text = mir::print_func(&func, &names, &REGS);
605        assert_eq!(text.matches("x64.mov_rr_64").count(), 2, "{text}");
606    }
607
608    #[test]
609    fn an_edge_out_of_a_computed_goto_that_carries_nothing_is_left_alone() {
610        let (mut names, mut func) = computed(1, 0);
611
612        // No values to carry, so no block to carry them, and the address stays the label's own.
613        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 0);
614        assert_eq!(addressed(&func), vec![1]);
615    }
616
617    #[test]
618    fn a_function_with_no_computed_goto_in_it_is_left_alone() {
619        let (mut names, mut func, _) = diamond(1);
620        assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 0);
621        assert_eq!(edges(&func), vec![vec![1, 2], vec![3], vec![3], vec![]]);
622    }
623
624    #[test]
625    fn what_it_leaves_is_nothing_for_the_splitting_below_to_do() {
626        let (mut names, mut func) = computed(2, 1);
627        indirect(&mut func, &BRANCH, &FRAME, &mut names);
628        // The edges out of the branches carry nothing now, and the edges out of the blocks it
629        // added are the only way out of those blocks, so neither kind is critical.
630        assert_eq!(critical(&mut func), 0);
631    }
632
633    /// The first instruction of each block, by opcode, and an empty string for a block with
634    /// nothing in it.
635    fn opens(func: &mir::Func, names: &Interner) -> Vec<String> {
636        func.blocks()
637            .map(|block| match func.insts(block).next() {
638                Some(inst) => names.resolve(func[inst].opcode.name()).to_owned(),
639                None => String::new(),
640            })
641            .collect()
642    }
643
644    #[test]
645    fn the_block_a_label_begins_at_gets_a_landing_pad_when_the_forward_edge_is_checked() {
646        let (mut names, mut func) = computed(2, 1);
647        indirect(&mut func, &BRANCH, &FRAME, &mut names);
648
649        // One pad, at the block in front of the label, because that is the block both addresses
650        // name once the values have been moved on to it. The label's own block is arrived at by an
651        // ordinary edge from there and wants nothing.
652        assert_eq!(pads(&mut func, &FRAME, FRAME.landing, &mut names), 1);
653        assert_eq!(opens(&func, &names), ["", "", "x64.lea_64", "x64.lea_64", "x64.endbr64"]);
654    }
655
656    #[test]
657    fn a_label_with_no_block_in_front_of_it_gets_the_pad_itself() {
658        let (mut names, mut func) = computed(1, 0);
659        indirect(&mut func, &BRANCH, &FRAME, &mut names);
660
661        // Nothing was moved on to anything, so the address still names the label and the pad goes
662        // where the address goes.
663        assert_eq!(pads(&mut func, &FRAME, FRAME.landing, &mut names), 1);
664        assert_eq!(opens(&func, &names), ["x64.lea_64", "x64.endbr64"]);
665    }
666
667    #[test]
668    fn nothing_is_written_when_the_forward_edge_is_not_checked() {
669        let (mut names, mut func) = computed(1, 0);
670        assert_eq!(pads(&mut func, &FRAME, None, &mut names), 0);
671        assert_eq!(opens(&func, &names), ["x64.lea_64", ""]);
672    }
673}