use rucc_ir::Func;
use crate::predict::Callees;
use crate::{Cfg, ControlDependence, Dominators, Frequencies, Frontiers, Loops, PostDominators};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Analysis {
Cfg,
Dominators,
PostDominators,
Loops,
Frontiers,
ControlDependence,
Frequencies,
}
impl Analysis {
pub const EVERY: &'static [Analysis] = &[
Analysis::Cfg,
Analysis::Dominators,
Analysis::PostDominators,
Analysis::Loops,
Analysis::Frontiers,
Analysis::ControlDependence,
Analysis::Frequencies,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Cfg => "the control flow graph",
Self::Dominators => "the dominator tree",
Self::PostDominators => "the post-dominator tree",
Self::Loops => "the loop forest",
Self::Frontiers => "the dominance frontiers",
Self::ControlDependence => "the control dependence relation",
Self::Frequencies => "the block frequencies",
}
}
#[must_use]
pub const fn needs(self) -> &'static [Analysis] {
match self {
Self::Cfg => &[],
Self::Dominators | Self::PostDominators => &[Analysis::Cfg],
Self::Loops | Self::Frontiers => &[Analysis::Cfg, Analysis::Dominators],
Self::ControlDependence => &[Analysis::Cfg, Analysis::PostDominators],
Self::Frequencies => &[Analysis::Cfg, Analysis::Dominators, Analysis::Loops],
}
}
const fn bit(self) -> u8 {
1 << (self as u8)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Preserved(u8);
impl Preserved {
pub const ALL: Preserved = Preserved(u8::MAX);
pub const NONE: Preserved = Preserved(0);
#[must_use]
pub const fn and(self, analysis: Analysis) -> Self {
Self(self.0 | analysis.bit())
}
#[must_use]
const fn without(self, analysis: Analysis) -> Self {
Self(self.0 & !analysis.bit())
}
#[must_use]
pub const fn keeps(self, analysis: Analysis) -> bool {
self.0 & analysis.bit() != 0
}
}
#[derive(Clone, Debug, Default)]
pub struct Analyses {
cfg: Option<Cfg>,
doms: Option<Dominators>,
post: Option<PostDominators>,
loops: Option<Loops>,
frontiers: Option<Frontiers>,
control: Option<ControlDependence>,
frequencies: Option<Frequencies>,
}
impl Analyses {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn cfg(&mut self, func: &Func) -> &Cfg {
self.cfg.get_or_insert_with(|| Cfg::new(func))
}
pub fn dominators(&mut self, func: &Func) -> &Dominators {
let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
self.doms.get_or_insert_with(|| Dominators::new(cfg))
}
pub fn post_dominators(&mut self, func: &Func) -> &PostDominators {
let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
self.post.get_or_insert_with(|| PostDominators::new(cfg))
}
pub fn loops(&mut self, func: &Func) -> &Loops {
let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
let doms: &Dominators = self.doms.get_or_insert_with(|| Dominators::new(cfg));
self.loops.get_or_insert_with(|| Loops::new(cfg, doms))
}
pub fn frontiers(&mut self, func: &Func) -> &Frontiers {
let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
let doms: &Dominators = self.doms.get_or_insert_with(|| Dominators::new(cfg));
self.frontiers.get_or_insert_with(|| Frontiers::new(cfg, doms))
}
pub fn control_dependence(&mut self, func: &Func) -> &ControlDependence {
let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
let post: &PostDominators = self.post.get_or_insert_with(|| PostDominators::new(cfg));
self.control.get_or_insert_with(|| ControlDependence::new(cfg, post))
}
pub fn frequencies(&mut self, func: &Func) -> &Frequencies {
let cfg: &Cfg = self.cfg.get_or_insert_with(|| Cfg::new(func));
let doms: &Dominators = self.doms.get_or_insert_with(|| Dominators::new(cfg));
let loops: &Loops = self.loops.get_or_insert_with(|| Loops::new(cfg, doms));
self.frequencies
.get_or_insert_with(|| Frequencies::of(func, cfg, loops, &Callees::nothing()))
}
#[must_use]
pub fn holds(&self, analysis: Analysis) -> bool {
match analysis {
Analysis::Cfg => self.cfg.is_some(),
Analysis::Dominators => self.doms.is_some(),
Analysis::PostDominators => self.post.is_some(),
Analysis::Loops => self.loops.is_some(),
Analysis::Frontiers => self.frontiers.is_some(),
Analysis::ControlDependence => self.control.is_some(),
Analysis::Frequencies => self.frequencies.is_some(),
}
}
pub fn settle(&mut self, func: &Func, keeps: Preserved, check: bool) -> Vec<Analysis> {
let lied = if check { self.lies(func, keeps) } else { Vec::new() };
let mut keeps = keeps;
for &analysis in &lied {
keeps = keeps.without(analysis);
}
let mut alive = [false; Analysis::EVERY.len()];
for &analysis in Analysis::EVERY {
let kept =
keeps.keeps(analysis) && analysis.needs().iter().all(|&need| alive[need as usize]);
alive[analysis as usize] = kept;
if !kept {
self.drop(analysis);
}
}
lied
}
pub fn clear(&mut self) {
*self = Self::default();
}
fn drop(&mut self, analysis: Analysis) {
match analysis {
Analysis::Cfg => self.cfg = None,
Analysis::Dominators => self.doms = None,
Analysis::PostDominators => self.post = None,
Analysis::Loops => self.loops = None,
Analysis::Frontiers => self.frontiers = None,
Analysis::ControlDependence => self.control = None,
Analysis::Frequencies => self.frequencies = None,
}
}
fn lies(&self, func: &Func, keeps: Preserved) -> Vec<Analysis> {
let wanted: Vec<Analysis> = Analysis::EVERY
.iter()
.copied()
.filter(|&it| self.holds(it) && keeps.keeps(it))
.collect();
if wanted.is_empty() {
return Vec::new();
}
let cfg = Cfg::new(func);
let mut lied = Vec::new();
for analysis in wanted {
let same = match analysis {
Analysis::Cfg => self.cfg.as_ref() == Some(&cfg),
Analysis::Dominators => self.doms.as_ref() == Some(&Dominators::new(&cfg)),
Analysis::PostDominators => self.post.as_ref() == Some(&PostDominators::new(&cfg)),
Analysis::Loops => {
self.loops.as_ref() == Some(&Loops::new(&cfg, &Dominators::new(&cfg)))
}
Analysis::Frontiers => {
self.frontiers.as_ref() == Some(&Frontiers::new(&cfg, &Dominators::new(&cfg)))
}
Analysis::ControlDependence => {
self.control.as_ref()
== Some(&ControlDependence::new(&cfg, &PostDominators::new(&cfg)))
}
Analysis::Frequencies => {
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
let now = Frequencies::of(func, &cfg, &loops, &Callees::nothing());
self.frequencies.as_ref() == Some(&now)
}
};
if !same {
lied.push(analysis);
}
}
lied
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Block, Func, Signature};
use super::{Analyses, Analysis, Preserved};
use crate::testing::graph;
fn func() -> Func {
graph(&[&[1, 2], &[3], &[3], &[4, 1], &[]])
}
#[test]
fn an_analysis_is_built_out_of_ones_that_come_before_it() {
for &analysis in Analysis::EVERY {
for &need in analysis.needs() {
assert!(need < analysis, "{} is built out of a later analysis", analysis.name());
}
}
}
#[test]
fn every_analysis_is_in_the_list_once() {
for &analysis in Analysis::EVERY {
let found = Analysis::EVERY.iter().filter(|&&it| it == analysis).count();
assert_eq!(found, 1, "{} appears twice", analysis.name());
}
assert_eq!(Analysis::EVERY.len(), 7);
}
#[test]
fn all_keeps_everything_and_none_keeps_nothing() {
for &analysis in Analysis::EVERY {
assert!(Preserved::ALL.keeps(analysis));
assert!(!Preserved::NONE.keeps(analysis));
}
}
#[test]
fn a_named_set_holds_what_was_named_and_nothing_else() {
let keeps = Preserved::NONE.and(Analysis::Cfg).and(Analysis::Loops);
assert!(keeps.keeps(Analysis::Cfg));
assert!(keeps.keeps(Analysis::Loops));
assert!(!keeps.keeps(Analysis::Dominators));
assert!(!keeps.keeps(Analysis::PostDominators));
}
#[test]
fn nothing_is_computed_until_it_is_asked_for() {
let mut an = Analyses::new();
for &analysis in Analysis::EVERY {
assert!(!an.holds(analysis));
}
let func = func();
an.dominators(&func);
assert!(an.holds(Analysis::Cfg));
assert!(an.holds(Analysis::Dominators));
assert!(!an.holds(Analysis::Loops));
assert!(!an.holds(Analysis::PostDominators));
}
#[test]
fn asking_twice_gives_the_same_answer_and_the_second_one_is_free() {
let func = func();
let mut an = Analyses::new();
let first = an.cfg(&func).clone();
let second = an.cfg(&func);
assert_eq!(&first, second);
}
#[test]
fn the_loop_forest_pulls_in_what_it_is_built_out_of() {
let func = func();
let mut an = Analyses::new();
an.loops(&func);
assert!(an.holds(Analysis::Cfg));
assert!(an.holds(Analysis::Dominators));
assert!(an.holds(Analysis::Loops));
}
#[test]
fn preserving_everything_keeps_everything() {
let func = func();
let mut an = Analyses::new();
an.loops(&func);
an.frontiers(&func);
an.control_dependence(&func);
an.frequencies(&func);
assert!(an.settle(&func, Preserved::ALL, true).is_empty());
for &analysis in Analysis::EVERY {
assert!(an.holds(analysis), "{} was thrown away", analysis.name());
}
}
#[test]
fn preserving_nothing_empties_the_cache() {
let func = func();
let mut an = Analyses::new();
an.loops(&func);
an.frontiers(&func);
an.control_dependence(&func);
an.settle(&func, Preserved::NONE, false);
for &analysis in Analysis::EVERY {
assert!(!an.holds(analysis), "{} outlived the pass", analysis.name());
}
}
#[test]
fn losing_the_graph_loses_what_was_built_on_it() {
let func = func();
let mut an = Analyses::new();
an.loops(&func);
an.post_dominators(&func);
let keeps = Preserved::NONE
.and(Analysis::Dominators)
.and(Analysis::PostDominators)
.and(Analysis::Loops);
an.settle(&func, keeps, false);
for &analysis in Analysis::EVERY {
assert!(!an.holds(analysis), "{} outlived the graph", analysis.name());
}
}
#[test]
fn losing_the_dominator_tree_loses_the_forest_and_leaves_the_graph() {
let func = func();
let mut an = Analyses::new();
an.loops(&func);
an.post_dominators(&func);
let keeps =
Preserved::NONE.and(Analysis::Cfg).and(Analysis::PostDominators).and(Analysis::Loops);
an.settle(&func, keeps, false);
assert!(an.holds(Analysis::Cfg));
assert!(an.holds(Analysis::PostDominators));
assert!(!an.holds(Analysis::Dominators), "the tree was not preserved");
assert!(!an.holds(Analysis::Loops), "the forest outlived the tree it needs");
}
#[test]
fn each_frontier_falls_with_the_tree_it_was_walked_on_and_not_the_other_one() {
let func = func();
let mut an = Analyses::new();
an.frontiers(&func);
an.control_dependence(&func);
let keeps = Preserved::NONE
.and(Analysis::Cfg)
.and(Analysis::Dominators)
.and(Analysis::Frontiers)
.and(Analysis::ControlDependence);
an.settle(&func, keeps, false);
assert!(an.holds(Analysis::Frontiers), "the frontier stands on a tree that stood");
assert!(!an.holds(Analysis::ControlDependence), "the post-dominator tree went with it");
}
#[test]
fn the_frequencies_fall_with_the_loop_forest_they_were_worked_out_from() {
let func = func();
let mut an = Analyses::new();
an.frequencies(&func);
for analysis in [Analysis::Cfg, Analysis::Dominators, Analysis::Loops] {
assert!(an.holds(analysis), "{} was not pulled in", analysis.name());
}
let keeps =
Preserved::NONE.and(Analysis::Cfg).and(Analysis::Dominators).and(Analysis::Frequencies);
an.settle(&func, keeps, false);
assert!(!an.holds(Analysis::Loops), "the forest was not preserved");
assert!(!an.holds(Analysis::Frequencies), "a frequency outlived the loop it counted");
}
#[test]
fn a_pass_that_says_it_kept_the_graph_and_moved_an_edge_is_caught() {
let mut func = func();
let mut an = Analyses::new();
an.loops(&func);
let block = Block::from_usize(3);
let term = func.terminator(block).expect("the helper gives every block a terminator");
func.remove_inst(term);
let mut build = rucc_ir::Builder::new(&mut func, block);
build.ret(&[]);
let lied = an.settle(&func, Preserved::ALL, true);
assert_eq!(lied, vec![Analysis::Cfg, Analysis::Dominators, Analysis::Loops]);
for &analysis in Analysis::EVERY {
assert!(!an.holds(analysis));
}
}
#[test]
fn a_lie_about_the_frontiers_is_caught_the_same_way() {
let mut func = func();
let mut an = Analyses::new();
an.frontiers(&func);
an.control_dependence(&func);
let block = Block::from_usize(3);
let term = func.terminator(block).expect("the helper gives every block a terminator");
func.remove_inst(term);
let mut build = rucc_ir::Builder::new(&mut func, block);
build.ret(&[]);
let lied = an.settle(&func, Preserved::ALL, true);
assert!(lied.contains(&Analysis::Frontiers));
assert!(lied.contains(&Analysis::ControlDependence));
}
#[test]
fn the_check_costs_nothing_when_it_is_off() {
let mut func = func();
let mut an = Analyses::new();
an.cfg(&func);
let block = Block::from_usize(3);
let term = func.terminator(block).expect("the helper gives every block a terminator");
func.remove_inst(term);
let mut build = rucc_ir::Builder::new(&mut func, block);
build.ret(&[]);
assert!(an.settle(&func, Preserved::ALL, false).is_empty());
assert!(an.holds(Analysis::Cfg));
}
#[test]
fn an_analysis_nobody_asked_for_is_not_checked() {
let func = func();
let mut an = Analyses::new();
assert!(an.settle(&func, Preserved::ALL, true).is_empty());
}
#[test]
fn a_declaration_has_analyses_like_anything_else() {
let mut names = Interner::new();
let func = Func::new(names.intern("declared"), Signature::new());
let mut an = Analyses::new();
assert!(an.cfg(&func).entry().is_none());
an.loops(&func);
an.post_dominators(&func);
assert!(an.settle(&func, Preserved::ALL, true).is_empty());
}
#[test]
fn clearing_takes_everything() {
let func = func();
let mut an = Analyses::new();
an.loops(&func);
an.clear();
for &analysis in Analysis::EVERY {
assert!(!an.holds(analysis));
}
}
}