use rucc_base::Interner;
use rucc_mir as mir;
use rucc_target::BranchInsts;
pub fn blocks(func: &mut mir::Func, insts: &BranchInsts, names: &mut Interner) {
let mut order = order(func);
let mut writer = Writer { func, insts, names };
let mut at = 0;
while at < order.len() {
if let Some(bridge) = writer.edges(order[at], order.get(at + 1).copied()) {
order.insert(at + 1, bridge);
}
at += 1;
}
func.set_block_order(&order);
}
fn order(func: &mir::Func) -> Vec<mir::Block> {
let mut order = Vec::with_capacity(func.block_count());
let mut seen = vec![false; func.block_count()];
if let Some(entry) = func.entry() {
seen[entry.index()] = true;
let mut stack = vec![(entry, 0usize)];
while let Some((block, next)) = stack.pop() {
let succs = &func[block].succs;
let Some(arm) = succs.len().checked_sub(next + 1) else {
order.push(block);
continue;
};
stack.push((block, next + 1));
let to = succs[arm].block;
if !std::mem::replace(&mut seen[to.index()], true) {
stack.push((to, 0));
}
}
order.reverse();
}
order.extend(func.blocks().filter(|block| !seen[block.index()]));
order
}
struct Writer<'a> {
func: &'a mut mir::Func,
insts: &'a BranchInsts,
names: &'a mut Interner,
}
impl Writer<'_> {
fn edges(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
match self.func[block].succs.len() {
0 => None,
1 => {
self.one(block, next);
None
}
2 => self.two(block, next),
arms => panic!("a block with {arms} arms, and nothing lowers to one"),
}
}
fn one(&mut self, block: mir::Block, next: Option<mir::Block>) {
if Some(self.func[block].succs[0].block) == next {
return;
}
let opcode = self.opcode(self.insts.jump);
self.func.build(block, opcode).finish();
}
fn two(&mut self, block: mir::Block, next: Option<mir::Block>) -> Option<mir::Block> {
let condition = self.take(block);
let arms: Vec<mir::Block> = self.func[block].succs.iter().map(|arm| arm.block).collect();
let (name, bridge) = if next == Some(arms[1]) {
(self.insts.if_true, None)
} else if next == Some(arms[0]) {
self.func.succs_mut(block).swap(0, 1);
(self.insts.if_false, None)
} else {
(self.insts.if_true, Some(self.bridge(block)))
};
let opcode = self.opcode(self.insts.test);
self.func.build(block, opcode).operand(condition).finish();
let opcode = self.opcode(name);
self.func.build(block, opcode).finish();
bridge
}
fn take(&mut self, block: mir::Block) -> mir::Operand {
let branch = self.func.terminator(block).expect("a block with two arms has a branch");
let cond = self.opcode(self.insts.cond);
assert_eq!(
self.func[branch].opcode, cond,
"a block with two arms whose last instruction is not the branch"
);
let operands = self.func[branch].operands;
let condition = self.func[operands][0];
self.func.remove_inst(branch);
condition
}
fn bridge(&mut self, block: mir::Block) -> mir::Block {
let bridge = self.func.create_block();
let edge = self.func[block].succs[1].clone();
*self.func.succs_mut(bridge) = vec![edge];
self.func.succs_mut(block)[1] = mir::BlockCall::to(bridge);
bridge
}
fn opcode(&mut self, name: &str) -> mir::Opcode {
mir::Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
}
}
#[cfg(test)]
mod tests {
use rucc_mir::{BlockCall, Opcode, Operand, Reg};
use rucc_target::x86_64::{BRANCH, GPR, RAX, REGS};
use super::*;
fn blank(count: usize) -> (Interner, mir::Func, Vec<mir::Block>) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let blocks = (0..count).map(|_| func.create_block()).collect();
(names, func, blocks)
}
fn branch(func: &mut mir::Func, names: &mut Interner, block: mir::Block, arms: &[mir::Block]) {
let opcode = Opcode::new(names.intern("x64.br_cond_8"));
func.build(block, opcode).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
*func.succs_mut(block) = arms.iter().map(|&arm| BlockCall::to(arm)).collect();
}
fn laid_out(func: &mut mir::Func, names: &mut Interner) -> Vec<String> {
blocks(func, &BRANCH, names);
mir::print_func(func, names, ®S)
.lines()
.filter(|line| !line.trim().is_empty() && !line.starts_with("mfunc") && *line != "}")
.map(|line| line.trim().to_string())
.collect()
}
fn order_of(func: &mir::Func) -> Vec<usize> {
func.blocks().map(mir::Block::index).collect()
}
#[test]
fn a_block_that_falls_into_the_next_one_gets_no_jump_at_all() {
let (mut names, mut func, made) = blank(2);
*func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
let text = laid_out(&mut func, &mut names);
assert_eq!(text, ["block0:", "block1", "block1:"]);
}
#[test]
fn a_block_that_goes_somewhere_that_is_not_next_gets_a_jump() {
let (mut names, mut func, made) = blank(2);
*func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
*func.succs_mut(made[1]) = vec![BlockCall::to(made[0])];
let text = laid_out(&mut func, &mut names);
assert_eq!(text, ["block0:", "block1", "block1:", "x64.jmp block0"]);
}
#[test]
fn a_branch_that_falls_into_its_false_arm_jumps_when_the_condition_holds() {
let (mut names, mut func, made) = blank(3);
*func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
branch(&mut func, &mut names, made[1], &[made[0], made[2]]);
let text = laid_out(&mut func, &mut names);
assert_eq!(order_of(&func), [0, 1, 2]);
assert_eq!(
text,
[
"block0:",
"block1",
"block1:",
"x64.test_rr_8 $rax",
"x64.jcc_ne block0, block2",
"block2:",
]
);
}
#[test]
fn a_branch_that_falls_into_its_true_arm_jumps_when_the_condition_does_not_hold() {
let (mut names, mut func, made) = blank(3);
branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
let text = laid_out(&mut func, &mut names);
assert_eq!(order_of(&func), [0, 1, 2]);
assert_eq!(
text,
["block0:", "x64.test_rr_8 $rax", "x64.jcc_e block2, block1", "block1:", "block2:"]
);
}
#[test]
fn a_branch_that_can_fall_into_neither_arm_is_given_a_block_to_jump_from() {
let (mut names, mut func, made) = blank(2);
*func.succs_mut(made[0]) = vec![BlockCall::to(made[1])];
branch(&mut func, &mut names, made[1], &[made[0], made[1]]);
let text = laid_out(&mut func, &mut names);
assert_eq!(order_of(&func), [0, 1, 2]);
assert_eq!(
text,
[
"block0:",
"block1",
"block1:",
"x64.test_rr_8 $rax",
"x64.jcc_ne block0, block2",
"block2:",
"x64.jmp block1",
]
);
}
#[test]
fn the_test_reads_the_register_the_branch_read() {
let (mut names, mut func, made) = blank(3);
branch(&mut func, &mut names, made[0], &[made[1], made[2]]);
blocks(&mut func, &BRANCH, &mut names);
let test = func.insts(made[0]).next().expect("a test");
let operands = func[test].operands;
assert_eq!(func[operands], [Operand::read(Reg::physical(RAX), GPR)]);
}
#[test]
fn a_block_nothing_reaches_is_laid_out_at_the_end_rather_than_deleted() {
let (mut names, mut func, made) = blank(4);
*func.succs_mut(made[0]) = vec![BlockCall::to(made[3])];
blocks(&mut func, &BRANCH, &mut names);
assert_eq!(order_of(&func), [0, 3, 1, 2]);
}
#[test]
fn a_function_with_no_blocks_is_left_alone() {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
blocks(&mut func, &BRANCH, &mut names);
assert_eq!(func.block_count(), 0);
}
#[test]
#[should_panic(expected = "a block with 3 arms")]
fn a_block_with_three_arms_is_refused_rather_than_laid_out_wrongly() {
let (mut names, mut func, made) = blank(4);
branch(&mut func, &mut names, made[0], &[made[1], made[2], made[3]]);
blocks(&mut func, &BRANCH, &mut names);
}
#[test]
#[should_panic(expected = "whose last instruction is not the branch")]
fn a_block_with_two_arms_and_no_branch_in_it_is_refused() {
let (mut names, mut func, made) = blank(3);
let opcode = Opcode::new(names.intern("x64.nop"));
func.build(made[0], opcode).finish();
*func.succs_mut(made[0]) = vec![BlockCall::to(made[1]), BlockCall::to(made[2])];
blocks(&mut func, &BRANCH, &mut names);
}
}