use rucc_mir as mir;
pub fn critical(func: &mut mir::Func) -> usize {
let preds = preds(func);
let blocks: Vec<mir::Block> = func.blocks().collect();
let mut split = 0;
for block in blocks {
if func[block].succs.len() < 2 {
continue;
}
for index in 0..func[block].succs.len() {
let call = func[block].succs[index].clone();
if call.args.is_empty() || preds[call.block.index()] < 2 {
continue;
}
let half = func.create_block();
*func.succs_mut(half) = vec![call];
func.succs_mut(block)[index] = mir::BlockCall::to(half);
split += 1;
}
}
split
}
fn preds(func: &mir::Func) -> Vec<usize> {
let mut counts = vec![0; func.block_count()];
for block in func.blocks() {
for call in &func[block].succs {
counts[call.block.index()] += 1;
}
}
counts
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_target::x86_64::{GPR, REGS};
use super::*;
fn diamond(params: usize) -> (Interner, mir::Func, [mir::Block; 4]) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let head = func.create_block();
let left = func.create_block();
let right = func.create_block();
let join = func.create_block();
let args: Vec<mir::Reg> = (0..params).map(|_| func.append_param(head, GPR)).collect();
for _ in 0..params {
func.append_param(join, GPR);
}
*func.succs_mut(head) = vec![mir::BlockCall::to(left), mir::BlockCall::to(right)];
*func.succs_mut(left) = vec![mir::BlockCall { block: join, args: args.clone() }];
*func.succs_mut(right) = vec![mir::BlockCall { block: join, args }];
(names, func, [head, left, right, join])
}
fn edges(func: &mir::Func) -> Vec<Vec<usize>> {
func.blocks()
.map(|block| func[block].succs.iter().map(|call| call.block.index()).collect())
.collect()
}
#[test]
fn an_edge_that_is_the_only_way_out_is_left_alone() {
let (_, mut func, _) = diamond(1);
assert_eq!(critical(&mut func), 0);
assert_eq!(edges(&func), vec![vec![1, 2], vec![3], vec![3], vec![]]);
}
#[test]
fn a_critical_edge_carrying_a_value_is_split_in_two() {
let (_, mut func, [head, _, _, join]) = diamond(1);
let arg = func.append_param(head, GPR);
func.succs_mut(head).push(mir::BlockCall { block: join, args: vec![arg] });
func.succs_mut(head).swap(1, 2);
assert_eq!(critical(&mut func), 1);
assert_eq!(
edges(&func),
vec![vec![1, 4, 2], vec![3], vec![3], vec![], vec![3]]
);
}
#[test]
fn a_critical_edge_carrying_nothing_is_left_alone() {
let (_, mut func, [head, _, _, join]) = diamond(0);
func.succs_mut(head).push(mir::BlockCall::to(join));
assert_eq!(critical(&mut func), 0);
}
#[test]
fn the_arguments_move_on_to_the_half_that_arrives() {
let (names, mut func, [head, _, _, join]) = diamond(1);
let arg = func.append_param(head, GPR);
func.succs_mut(head).push(mir::BlockCall { block: join, args: vec![arg] });
assert_eq!(critical(&mut func), 1);
let half = func.blocks().last().expect("the block the split added");
assert_eq!(func[head].succs[2].args, Vec::new());
assert_eq!(func[half].succs[0].args, vec![arg]);
assert_eq!(
mir::print_func(&func, &names, ®S),
"mfunc @f {\nblock0(%0:gpr, %1:gpr):\n block1, block2, block4\n\n\
block1:\n block3(%0)\n\nblock2:\n block3(%0)\n\n\
block3(%2:gpr):\n\nblock4:\n block3(%1)\n}\n"
);
}
#[test]
fn splitting_twice_is_splitting_once() {
let (_, mut func, [head, _, _, join]) = diamond(1);
let arg = func.append_param(head, GPR);
func.succs_mut(head).push(mir::BlockCall { block: join, args: vec![arg] });
assert_eq!(critical(&mut func), 1);
assert_eq!(critical(&mut func), 0);
}
}