use rucc_cost::heuristics::{MAX_PREDICTED_ITERATIONS, PROFILE_SUM_TOLERANCE_PERCENT};
use rucc_ir::{Block, Func};
use crate::cfg::Cfg;
use crate::loops::{LoopId, Loops};
use crate::predict::{Callees, Predictions};
use crate::profile::{Frequency, Probability, Quality};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Frequencies {
told: Predictions,
of: Vec<Frequency>,
reliable: Vec<bool>,
capped: Vec<bool>,
cyclic: Vec<Probability>,
entry: Frequency,
}
impl Frequencies {
#[must_use]
pub fn of(func: &Func, cfg: &Cfg, loops: &Loops, callees: &Callees) -> Self {
let told = Predictions::of(func, cfg, loops, callees);
let width = cfg.capacity();
let cyclic = cyclic_probabilities(cfg, loops, &told);
let mut of = vec![Frequency::NEVER; width];
let mut reliable = vec![true; width];
let mut capped = vec![false; width];
let Some(entry) = cfg.entry() else {
return Self { told, of, reliable, capped, cyclic, entry: Frequency::UNKNOWN };
};
of[entry.index()] = Frequency::ENTRY;
for block in cfg.reverse_postorder() {
if block != entry {
let mut total = Frequency::NEVER;
let mut sound = !loops.is_irreducible(block);
for &pred in cfg.predecessors(block) {
if !forward(cfg, pred, block) {
continue;
}
total = total.plus(of[pred.index()].along(edge(&told, cfg, pred, block)));
sound = sound && reliable[pred.index()];
}
of[block.index()] = total;
reliable[block.index()] = sound;
}
let Some(id) = heads(loops, block) else { continue };
let again = cyclic[id.index()];
of[block.index()] = of[block.index()].repeated_while(again, MAX_PREDICTED_ITERATIONS);
capped[block.index()] = is_capped(again);
}
let entry = of[entry.index()];
Self { told, of, reliable, capped, cyclic, entry }
}
#[must_use]
pub fn told(&self) -> &Predictions {
&self.told
}
#[must_use]
pub fn taken(&self, block: Block, index: usize) -> Probability {
self.told.taken(block, index)
}
#[must_use]
pub fn get(&self, block: Block) -> Frequency {
self.of.get(block.index()).copied().unwrap_or(Frequency::UNKNOWN)
}
#[must_use]
pub fn entry(&self) -> Frequency {
self.entry
}
#[must_use]
pub fn is_reliable(&self, block: Block) -> bool {
self.reliable.get(block.index()).copied().unwrap_or(false)
}
#[must_use]
pub fn is_capped(&self, block: Block) -> bool {
self.capped.get(block.index()).copied().unwrap_or(false)
}
#[must_use]
pub fn is_hot(&self, block: Block) -> bool {
self.get(block).is_hot_in_function(self.entry)
}
#[must_use]
pub fn cyclic(&self, id: LoopId) -> Probability {
self.cyclic.get(id.index()).copied().unwrap_or_else(Probability::never)
}
#[must_use]
pub fn iterations(&self, id: LoopId) -> u32 {
let once = Frequency::ENTRY.repeated_while(self.cyclic(id), MAX_PREDICTED_ITERATIONS);
let count = once.raw() / u64::from(Probability::SCALE);
u32::try_from(count).unwrap_or(MAX_PREDICTED_ITERATIONS)
}
#[must_use]
pub fn hottest(&self, func: &Func) -> Option<Block> {
func.blocks().max_by_key(|&block| self.get(block).raw())
}
#[must_use]
pub fn problems(&self, func: &Func, cfg: &Cfg) -> Vec<String> {
let mut problems = Vec::new();
let entry = cfg.entry();
for block in func.blocks() {
let out: u32 = self.told.edges(block).iter().map(|edge| edge.parts()).sum();
if !self.told.edges(block).is_empty() && out != Probability::SCALE {
problems.push(format!(
"the edges out of {block:?} are taken {out} parts in {} of the time",
Probability::SCALE
));
}
if Some(block) == entry || self.is_capped(block) || !self.is_reliable(block) {
continue;
}
let mut arriving = Frequency::NEVER;
let mut edges = 0;
for &pred in cfg.predecessors(block) {
arriving = arriving.plus(self.get(pred).along(edge(&self.told, cfg, pred, block)));
edges += 1;
}
let here = self.get(block);
let apart = here.raw().abs_diff(arriving.raw());
let allowed = here.raw() / 100 * u64::from(PROFILE_SUM_TOLERANCE_PERCENT) + edges;
if apart > allowed {
problems.push(format!(
"{block:?} runs at {here} and the paths into it add up to {arriving}"
));
}
}
problems
}
}
fn cyclic_probabilities(cfg: &Cfg, loops: &Loops, told: &Predictions) -> Vec<Probability> {
let mut cyclic = vec![Probability::never(); loops.count()];
let mut relative = vec![Frequency::NEVER; cfg.capacity()];
let order: Vec<LoopId> = loops.all().collect();
for &id in order.iter().rev() {
let header = loops.header(id);
let mut inside: Vec<Block> = loops.blocks(id).to_vec();
inside.sort_by_key(|&block| cfg.rank(block));
for &block in &inside {
relative[block.index()] = Frequency::NEVER;
}
relative[header.index()] = Frequency::ENTRY;
for &block in &inside {
if block != header {
let mut total = Frequency::NEVER;
for &pred in cfg.predecessors(block) {
if !loops.contains(id, pred) || !forward(cfg, pred, block) {
continue;
}
total = total.plus(relative[pred.index()].along(edge(told, cfg, pred, block)));
}
relative[block.index()] = total;
}
let Some(inner) = heads(loops, block) else { continue };
if inner == id {
continue;
}
let again = cyclic[inner.index()];
relative[block.index()] =
relative[block.index()].repeated_while(again, MAX_PREDICTED_ITERATIONS);
}
let mut round = Frequency::NEVER;
for &latch in loops.latches(id) {
round = round.plus(relative[latch.index()].along(edge(told, cfg, latch, header)));
}
let parts = u32::try_from(round.raw()).unwrap_or(Probability::SCALE);
cyclic[id.index()] = Probability::new(parts, round.quality().min(Quality::Guessed));
}
cyclic
}
fn heads(loops: &Loops, block: Block) -> Option<LoopId> {
let id = loops.innermost(block)?;
(loops.header(id) == block).then_some(id)
}
fn forward(cfg: &Cfg, from: Block, to: Block) -> bool {
match (cfg.rank(from), cfg.rank(to)) {
(Some(from), Some(to)) => from < to,
_ => false,
}
}
fn edge(told: &Predictions, cfg: &Cfg, from: Block, to: Block) -> Probability {
match cfg.successors(from).iter().position(|&block| block == to) {
Some(at) => told.taken(from, at),
None => Probability::never(),
}
}
fn is_capped(again: Probability) -> bool {
let stop = Probability::SCALE - again.parts().min(Probability::SCALE);
stop <= Probability::SCALE.div_ceil(MAX_PREDICTED_ITERATIONS)
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Block, Builder, Func, Signature, Type};
use super::Frequencies;
use crate::cfg::Cfg;
use crate::dom::Dominators;
use crate::loops::Loops;
use crate::predict::Callees;
use crate::profile::{Frequency, Probability, Quality};
const ONE: u64 = Probability::SCALE as u64;
fn frequencies(func: &Func) -> (Frequencies, Cfg, Loops) {
let cfg = Cfg::new(func);
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
let of = Frequencies::of(func, &cfg, &loops, &Callees::nothing());
assert!(of.problems(func, &cfg).is_empty(), "{:?}", of.problems(func, &cfg));
(of, cfg, loops)
}
fn blank(blocks: usize) -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new());
let list = (0..blocks).map(|_| func.create_block()).collect();
(names, func, list)
}
fn ret(func: &mut Func, block: Block) {
let mut build = Builder::new(func, block);
let zero = build.iconst(Type::int(32), 0);
build.ret(&[zero]);
}
fn line() -> (Func, Vec<Block>) {
let (_, mut func, at) = blank(3);
Builder::new(&mut func, at[0]).jump(at[1], &[]);
Builder::new(&mut func, at[1]).jump(at[2], &[]);
ret(&mut func, at[2]);
(func, at)
}
fn fork() -> (Func, Vec<Block>) {
let (_, mut func, at) = blank(4);
let mut build = Builder::new(&mut func, at[0]);
let cond = build.iconst(Type::int(1), 1);
build.br_if(cond, at[1], &[], at[2], &[]);
Builder::new(&mut func, at[1]).jump(at[3], &[]);
Builder::new(&mut func, at[2]).jump(at[3], &[]);
ret(&mut func, at[3]);
(func, at)
}
fn loop_shape() -> (Func, Vec<Block>) {
let (_, mut func, at) = blank(4);
Builder::new(&mut func, at[0]).jump(at[1], &[]);
let mut build = Builder::new(&mut func, at[1]);
let cond = build.iconst(Type::int(1), 1);
build.br_if(cond, at[2], &[], at[3], &[]);
Builder::new(&mut func, at[2]).jump(at[1], &[]);
ret(&mut func, at[3]);
(func, at)
}
fn nest() -> (Func, Vec<Block>) {
let (_, mut func, at) = blank(6);
Builder::new(&mut func, at[0]).jump(at[1], &[]);
for (test, stay, leave) in [(at[1], at[2], at[5]), (at[2], at[3], at[4])] {
let mut build = Builder::new(&mut func, test);
let cond = build.iconst(Type::int(1), 1);
build.br_if(cond, stay, &[], leave, &[]);
}
Builder::new(&mut func, at[3]).jump(at[2], &[]);
Builder::new(&mut func, at[4]).jump(at[1], &[]);
ret(&mut func, at[5]);
(func, at)
}
#[test]
fn a_straight_line_runs_once_and_that_is_not_a_guess() {
let (func, at) = line();
let (of, ..) = frequencies(&func);
for block in at {
assert_eq!(of.get(block).raw(), ONE, "{block:?}");
assert_eq!(of.get(block).quality(), Quality::Precise);
}
}
#[test]
fn the_arms_of_a_branch_nobody_predicted_run_half_the_time_each() {
let (func, at) = fork();
let (of, ..) = frequencies(&func);
assert_eq!(of.get(at[1]).raw(), ONE / 2);
assert_eq!(of.get(at[2]).raw(), ONE / 2);
assert_eq!(of.get(at[3]).raw(), ONE);
assert_eq!(of.get(at[3]).quality(), Quality::Guessed);
}
#[test]
fn a_loop_body_runs_as_many_times_as_the_series_says() {
let (func, at) = loop_shape();
let (of, _, loops) = frequencies(&func);
let id = loops.all().next().expect("a loop");
assert_eq!(of.cyclic(id), Probability::percent(89, Quality::Guessed));
assert_eq!(of.taken(at[1], 0), of.cyclic(id));
assert_eq!(of.get(at[1]).raw(), ONE * ONE / 1_100);
assert_eq!(of.iterations(id), 9);
assert_eq!(of.get(at[2]).raw(), of.get(at[1]).along(of.cyclic(id)).raw());
assert!(of.get(at[3]).raw().abs_diff(ONE) < ONE / 100, "{}", of.get(at[3]));
}
#[test]
fn a_loop_inside_a_loop_multiplies() {
let (func, at) = nest();
let (of, _, loops) = frequencies(&func);
let mut all = loops.all();
let outer = all.next().expect("the outer loop");
let inner = all.next().expect("the inner loop");
assert_eq!(loops.header(outer), at[1]);
assert_eq!(loops.header(inner), at[2]);
assert_eq!(of.iterations(outer), 9);
assert_eq!(of.iterations(inner), 9);
let round = u64::from(of.iterations(outer) * of.iterations(inner));
assert!(of.get(at[3]).raw() > round * ONE * 3 / 4, "{}", of.get(at[3]));
assert!(of.get(at[5]).raw().abs_diff(ONE) < ONE / 100, "{}", of.get(at[5]));
}
#[test]
fn a_loop_nothing_predicts_an_exit_for_gets_the_cap_rather_than_a_division_by_zero() {
let (_, mut func, at) = blank(2);
Builder::new(&mut func, at[0]).jump(at[1], &[]);
Builder::new(&mut func, at[1]).jump(at[1], &[]);
let (of, _, loops) = frequencies(&func);
let id = loops.all().next().expect("a loop");
assert_eq!(of.cyclic(id).parts(), Probability::SCALE);
assert!(of.is_capped(at[1]));
assert_eq!(of.iterations(id), 100);
assert_eq!(of.get(at[1]).raw(), ONE * 100);
}
#[test]
fn a_frequency_in_an_irreducible_region_says_it_does_not_mean_anything() {
let (_, mut func, at) = blank(3);
let mut build = Builder::new(&mut func, at[0]);
let cond = build.iconst(Type::int(1), 1);
build.br_if(cond, at[1], &[], at[2], &[]);
Builder::new(&mut func, at[1]).jump(at[2], &[]);
Builder::new(&mut func, at[2]).jump(at[1], &[]);
let (of, ..) = frequencies(&func);
assert!(of.is_reliable(at[0]));
assert!(!of.is_reliable(at[1]), "a two entry cycle has no header and no series");
assert!(!of.is_reliable(at[2]));
}
#[test]
fn a_block_nothing_reaches_never_runs_and_is_not_hot() {
let (_, mut func, at) = blank(3);
Builder::new(&mut func, at[0]).jump(at[1], &[]);
ret(&mut func, at[1]);
ret(&mut func, at[2]);
let (of, ..) = frequencies(&func);
assert_eq!(of.get(at[2]), Frequency::NEVER);
assert!(!of.is_hot(at[2]));
assert!(of.is_hot(at[0]));
}
#[test]
fn the_hottest_block_of_a_loop_is_the_one_in_it() {
let (func, at) = loop_shape();
let (of, ..) = frequencies(&func);
assert_eq!(of.hottest(&func), Some(at[1]));
assert!(of.is_hot(at[2]));
assert_eq!(of.entry(), Frequency::ENTRY);
}
#[test]
fn what_arrives_at_a_block_adds_up_to_the_block_which_is_the_check_section_11_5_asks_for() {
for (func, _) in [line(), fork(), loop_shape(), nest()] {
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
let mut of = Frequencies::of(&func, &cfg, &loops, &Callees::nothing());
assert!(of.problems(&func, &cfg).is_empty());
let last = func.blocks().last().expect("a block");
of.of[last.index()] = Frequency::times(7, Quality::Precise);
let complaints = of.problems(&func, &cfg);
assert_eq!(complaints.len(), 1, "{complaints:?}");
assert!(complaints[0].contains("add up to"), "{}", complaints[0]);
}
}
}