use std::collections::HashMap;
use rucc_base::Interner;
use rucc_mir as mir;
use rucc_target::{BranchInsts, FrameInsts, RegClass};
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 weight = call.weight;
let half = func.create_block();
func.set_weight(half, weight);
*func.succs_mut(half) = vec![call];
func.succs_mut(block)[index] = mir::BlockCall::to(half).taken(weight);
split += 1;
}
}
split
}
pub fn indirect(
func: &mut mir::Func,
branch: &BranchInsts,
frame: &FrameInsts,
names: &mut Interner,
) -> usize {
let jump = mir::Opcode::new(names.intern(&format!("{}{}", branch.prefix, branch.indirect)));
let branches: Vec<mir::Block> = func
.blocks()
.filter(|&block| func.terminator(block).is_some_and(|last| func[last].opcode == jump))
.collect();
if branches.is_empty() {
return 0;
}
let mut targets: Vec<mir::Block> = Vec::new();
for &block in &branches {
for call in &func[block].succs {
if !call.args.is_empty() && !targets.contains(&call.block) {
targets.push(call.block);
}
}
}
let mut homes: HashMap<Given, mir::Reg> = HashMap::new();
let mut writes: Vec<Vec<(mir::Reg, mir::Reg, RegClass)>> = vec![Vec::new(); branches.len()];
let mut entries: HashMap<mir::Block, mir::Block> = HashMap::new();
for target in targets {
let params = func[target].params.clone();
let given = given(func, &branches, target);
let mut carried: Vec<mir::Reg> = Vec::new();
for (index, param) in params.iter().enumerate() {
let key: Given = (
param.class,
given.iter().map(|edges| edges.iter().map(|args| args[index]).collect()).collect(),
);
let home = *homes.entry(key).or_insert_with(|| func.new_vreg(param.class));
carried.push(home);
for (branch, edges) in given.iter().enumerate() {
for args in edges {
if !writes[branch].iter().any(|&(written, _, _)| written == home) {
writes[branch].push((home, args[index], param.class));
}
}
}
}
let entry = func.create_block();
let mut total = mir::Weight::NEVER;
for &block in &branches {
for index in 0..func[block].succs.len() {
if func[block].succs[index].block != target {
continue;
}
let weight = func[block].succs[index].weight;
total = mir::Weight::parts(total.raw().saturating_add(weight.raw()));
func.succs_mut(block)[index] = mir::BlockCall::to(entry).taken(weight);
}
}
func.set_weight(entry, total);
*func.succs_mut(entry) = vec![mir::BlockCall::with(target, carried).taken(total)];
entries.insert(target, entry);
}
for (branch, moves) in branches.iter().zip(&writes) {
let last = func.terminator(*branch).expect("a block that ends in a jump");
for &(home, arg, class) in moves {
let name = frame.moves(class).expect("a class this machine can move").mov;
let opcode = mir::Opcode::new(names.intern(&format!("{}{name}", frame.prefix)));
let inst = func.build_loose(opcode).def(home, class).uses(arg, class).finish();
func.insert_before(last, inst);
}
}
let mut addresses: Vec<mir::MemRef> = Vec::new();
for block in func.blocks() {
for inst in func.insts(block) {
if let Some(mem) = func[inst].mem {
addresses.push(mem);
}
}
}
for mem in addresses {
if let Some(named) = func[mem].block {
if let Some(&entry) = entries.get(&named) {
func[mem].block = Some(entry);
}
}
}
for (block, _) in &mut func.labels {
if let Some(&entry) = entries.get(block) {
*block = entry;
}
}
entries.len()
}
pub fn pads(
func: &mut mir::Func,
frame: &FrameInsts,
landing: Option<&'static str>,
names: &mut Interner,
) -> usize {
let Some(name) = landing else { return 0 };
let opcode = mir::Opcode::new(names.intern(&format!("{}{name}", frame.prefix)));
let mut addressed: Vec<mir::Block> = Vec::new();
for block in func.blocks() {
for inst in func.insts(block) {
if let Some(mem) = func[inst].mem {
if let Some(named) = func[mem].block {
if !addressed.contains(&named) {
addressed.push(named);
}
}
}
}
}
for &block in &addressed {
let inst = func.build_loose(opcode).finish();
func.prepend_inst(block, inst);
}
addressed.len()
}
type Given = (RegClass, Vec<Vec<mir::Reg>>);
fn given(func: &mir::Func, branches: &[mir::Block], target: mir::Block) -> Vec<Vec<Vec<mir::Reg>>> {
branches
.iter()
.map(|&block| {
func[block]
.succs
.iter()
.filter(|call| call.block == target)
.map(|call| call.args.clone())
.collect()
})
.collect()
}
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::{BRANCH, FRAME, 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::with(join, args.clone())];
*func.succs_mut(right) = vec![mir::BlockCall::with(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::with(join, 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::with(join, 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::with(join, vec![arg]));
assert_eq!(critical(&mut func), 1);
assert_eq!(critical(&mut func), 0);
}
fn computed(branches: usize, params: usize) -> (Interner, mir::Func) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let head = func.create_block();
let label = func.create_block();
for _ in 0..params {
func.append_param(label, GPR);
}
let lea = mir::Opcode::new(names.intern("x64.lea_64"));
let jump = mir::Opcode::new(names.intern("x64.jmp_reg"));
for _ in 0..branches {
let args: Vec<mir::Reg> = (0..params).map(|_| func.append_param(head, GPR)).collect();
let address = func.new_vreg(GPR);
let at = if branches == 1 { head } else { func.create_block() };
func.build(at, lea).def(address, GPR).mem(mir::Mem::block(label)).finish();
func.build(at, jump).operand(mir::Operand::read(address, GPR)).finish();
*func.succs_mut(at) = vec![mir::BlockCall::with(label, args)];
}
(names, func)
}
fn addressed(func: &mir::Func) -> Vec<usize> {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.filter_map(|inst| func[inst].mem)
.filter_map(|mem| func[mem].block)
.map(mir::Block::index)
.collect()
}
#[test]
fn the_values_a_computed_goto_carries_move_into_a_block_in_front_of_the_label() {
let (mut names, mut func) = computed(1, 1);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 1);
assert_eq!(edges(&func), vec![vec![2], vec![], vec![1]]);
assert_eq!(func[mir::Block::new(0)].succs[0].args, Vec::new());
assert_eq!(addressed(&func), vec![2]);
}
#[test]
fn two_computed_gotos_that_reach_one_label_are_made_to_agree() {
let (mut names, mut func) = computed(2, 1);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 1);
assert_eq!(edges(&func), vec![vec![], vec![], vec![4], vec![4], vec![1]]);
let text = mir::print_func(&func, &names, ®S);
assert_eq!(text.matches("x64.mov_rr_64").count(), 2, "{text}");
for line in text.lines().collect::<Vec<_>>().windows(2) {
if line[1].contains("x64.jmp_reg") {
assert!(line[0].contains("x64.mov_rr_64"), "{text}");
}
}
assert_eq!(addressed(&func), vec![4, 4]);
}
fn table(labels: usize) -> (Interner, mir::Func, Vec<mir::Block>) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let head = func.create_block();
let arg = func.append_param(head, GPR);
let lea = mir::Opcode::new(names.intern("x64.lea_64"));
let jump = mir::Opcode::new(names.intern("x64.jmp_reg"));
let mut targets = Vec::new();
for _ in 0..labels {
let label = func.create_block();
func.append_param(label, GPR);
targets.push(label);
func.succs_mut(head).push(mir::BlockCall::with(label, vec![arg]));
}
let address = func.new_vreg(GPR);
func.build(head, lea).def(address, GPR).mem(mir::Mem::block(targets[0])).finish();
func.build(head, jump).operand(mir::Operand::read(address, GPR)).finish();
(names, func, targets)
}
#[test]
fn labels_a_branch_gives_the_same_value_are_given_it_in_one_register() {
let (mut names, mut func, _) = table(8);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 8);
let text = mir::print_func(&func, &names, ®S);
assert_eq!(text.matches("x64.mov_rr_64").count(), 1, "{text}");
}
#[test]
fn a_label_given_something_else_keeps_a_register_of_its_own() {
let (mut names, mut func, targets) = table(8);
let head = mir::Block::new(0);
let other = func.append_param(head, GPR);
let last = func[head].succs.len() - 1;
func.succs_mut(head)[last] = mir::BlockCall::with(targets[7], vec![other]);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 8);
let text = mir::print_func(&func, &names, ®S);
assert_eq!(text.matches("x64.mov_rr_64").count(), 2, "{text}");
}
#[test]
fn labels_that_take_different_numbers_of_values_still_share_the_ones_they_agree_on() {
let (mut names, mut func, targets) = table(8);
let head = mir::Block::new(0);
let arg = func[head].params[0].reg;
let other = func.append_param(head, GPR);
func.append_param(targets[7], GPR);
let last = func[head].succs.len() - 1;
func.succs_mut(head)[last] = mir::BlockCall::with(targets[7], vec![arg, other]);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 8);
let text = mir::print_func(&func, &names, ®S);
assert_eq!(text.matches("x64.mov_rr_64").count(), 2, "{text}");
}
#[test]
fn an_edge_out_of_a_computed_goto_that_carries_nothing_is_left_alone() {
let (mut names, mut func) = computed(1, 0);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 0);
assert_eq!(addressed(&func), vec![1]);
}
#[test]
fn a_function_with_no_computed_goto_in_it_is_left_alone() {
let (mut names, mut func, _) = diamond(1);
assert_eq!(indirect(&mut func, &BRANCH, &FRAME, &mut names), 0);
assert_eq!(edges(&func), vec![vec![1, 2], vec![3], vec![3], vec![]]);
}
#[test]
fn what_it_leaves_is_nothing_for_the_splitting_below_to_do() {
let (mut names, mut func) = computed(2, 1);
indirect(&mut func, &BRANCH, &FRAME, &mut names);
assert_eq!(critical(&mut func), 0);
}
fn opens(func: &mir::Func, names: &Interner) -> Vec<String> {
func.blocks()
.map(|block| match func.insts(block).next() {
Some(inst) => names.resolve(func[inst].opcode.name()).to_owned(),
None => String::new(),
})
.collect()
}
#[test]
fn the_block_a_label_begins_at_gets_a_landing_pad_when_the_forward_edge_is_checked() {
let (mut names, mut func) = computed(2, 1);
indirect(&mut func, &BRANCH, &FRAME, &mut names);
assert_eq!(pads(&mut func, &FRAME, FRAME.landing, &mut names), 1);
assert_eq!(opens(&func, &names), ["", "", "x64.lea_64", "x64.lea_64", "x64.endbr64"]);
}
#[test]
fn a_label_with_no_block_in_front_of_it_gets_the_pad_itself() {
let (mut names, mut func) = computed(1, 0);
indirect(&mut func, &BRANCH, &FRAME, &mut names);
assert_eq!(pads(&mut func, &FRAME, FRAME.landing, &mut names), 1);
assert_eq!(opens(&func, &names), ["x64.lea_64", "x64.endbr64"]);
}
#[test]
fn nothing_is_written_when_the_forward_edge_is_not_checked() {
let (mut names, mut func) = computed(1, 0);
assert_eq!(pads(&mut func, &FRAME, None, &mut names), 0);
assert_eq!(opens(&func, &names), ["x64.lea_64", ""]);
}
}