use rucc_ir as ir;
use rucc_mir as mir;
use rucc_opt::{Callees, Cfg, Dominators, Frequencies, Loops};
pub fn carry(source: &ir::Func, blocks: &[Option<mir::Block>], func: &mut mir::Func) {
let cfg = Cfg::new(source);
if cfg.entry().is_none() {
return;
}
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
let freqs = Frequencies::of(source, &cfg, &loops, &Callees::nothing());
for block in source.blocks() {
let Some(&Some(out)) = blocks.get(block.index()) else { continue };
let weight = freqs.get(block);
func.set_weight(out, mir::Weight::parts(weight.raw()));
let Some(term) = source.terminator(block) else { continue };
let arms: Vec<ir::Block> = source.successors(term).map(|call| call.block).collect();
if arms.len() != func[out].succs.len() {
continue;
}
for (index, arm) in arms.iter().enumerate() {
let Some(at) = cfg.successors(block).iter().position(|succ| succ == arm) else {
continue;
};
func.succs_mut(out)[index].weight =
mir::Weight::parts(weight.along(freqs.taken(block, at)).raw());
}
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, Func, Opcode, Signature, Type};
use rucc_target::x86_64::SYSV;
use super::*;
use crate::elsewhere::Elsewhere;
use crate::lower;
fn lowered(source: &mut Func, names: &mut Interner) -> mir::Func {
let out = lower::func(source, names, &SYSV, &Elsewhere::default()).expect("it lowers");
let lower::Lowered { mut func, blocks, .. } = out;
carry(source, &blocks, &mut func);
func
}
fn weights(func: &mir::Func) -> Vec<u64> {
func.blocks().map(|block| func[block].weight.raw()).collect()
}
#[test]
fn a_function_with_no_branch_in_it_runs_every_block_once() {
let mut names = Interner::new();
let mut source = Func::new(names.intern("f"), Signature::new());
let entry = source.create_block();
Builder::new(&mut source, entry).ret(&[]);
let func = lowered(&mut source, &mut names);
assert_eq!(weights(&func), [mir::Weight::ONCE.raw()]);
}
#[test]
fn the_arms_of_a_branch_add_up_to_the_block_they_leave() {
let int = Type::int(32);
let mut names = Interner::new();
let mut source =
Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(1)]));
let entry = source.create_block();
let cond = source.append_param(entry, Type::int(1));
let yes = source.create_block();
let no = source.create_block();
Builder::new(&mut source, entry).br_if(cond, yes, &[], no, &[]);
let mut build = Builder::new(&mut source, yes);
let one = build.iconst(int, 1);
build.ret(&[one]);
let mut build = Builder::new(&mut source, no);
let two = build.iconst(int, 2);
build.ret(&[two]);
let func = lowered(&mut source, &mut names);
let head = func.blocks().next().expect("an entry");
let arms: Vec<u64> = func[head].succs.iter().map(|call| call.weight.raw()).collect();
assert_eq!(arms.len(), 2);
assert_eq!(arms.iter().sum::<u64>(), mir::Weight::ONCE.raw());
}
#[test]
fn a_loop_body_runs_more_often_than_the_block_that_follows_it() {
let int = Type::int(32);
let mut names = Interner::new();
let mut source =
Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(1)]));
let entry = source.create_block();
let cond = source.append_param(entry, Type::int(1));
let head = source.create_block();
let body = source.create_block();
let out = source.create_block();
Builder::new(&mut source, entry).jump(head, &[]);
Builder::new(&mut source, head).br_if(cond, body, &[], out, &[]);
Builder::new(&mut source, body).jump(head, &[]);
let mut build = Builder::new(&mut source, out);
let zero = build.iconst(int, 0);
build.ret(&[zero]);
let func = lowered(&mut source, &mut names);
let made: Vec<mir::Block> = func.blocks().collect();
let weight = |at: usize| func[made[at]].weight.raw();
assert!(weight(2) > weight(3), "the body {} the exit {}", weight(2), weight(3));
assert!(weight(3).abs_diff(mir::Weight::ONCE.raw()) <= 1, "the exit {}", weight(3));
}
#[test]
fn the_arm_control_does_not_come_back_from_is_the_colder_one() {
let int = Type::int(32);
let mut names = Interner::new();
let mut source =
Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(1)]));
let entry = source.create_block();
let cond = source.append_param(entry, Type::int(1));
let yes = source.create_block();
let no = source.create_block();
Builder::new(&mut source, entry).br_if(cond, yes, &[], no, &[]);
Builder::new(&mut source, yes).inst(ir::InstData::new(Opcode::Unreachable), &[]);
let mut build = Builder::new(&mut source, no);
let zero = build.iconst(int, 0);
build.ret(&[zero]);
let func = lowered(&mut source, &mut names);
let head = func.blocks().next().expect("an entry");
let arms: Vec<u64> = func[head].succs.iter().map(|call| call.weight.raw()).collect();
assert!(arms[0] < arms[1], "{arms:?}");
}
}