use rucc_ir::Block;
use crate::{Cfg, Dominators, PostDominators};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Frontiers {
of: Vec<Vec<Block>>,
}
impl Frontiers {
#[must_use]
pub fn new(cfg: &Cfg, doms: &Dominators) -> Self {
let mut of = vec![Vec::new(); cfg.capacity()];
for &block in cfg.postorder() {
let arriving = || cfg.predecessors(block).iter().copied().filter(|&p| cfg.reaches(p));
let arrivals = arriving().count() + usize::from(Some(block) == cfg.entry());
if arrivals < 2 {
continue;
}
let stop = doms.immediate_dominator(block);
for pred in arriving() {
let mut runner = pred;
while Some(runner) != stop {
of[runner.index()].push(block);
match doms.immediate_dominator(runner) {
Some(next) => runner = next,
None => break,
}
}
}
}
for list in &mut of {
list.sort_unstable_by_key(|b: &Block| b.index());
list.dedup();
}
Self { of }
}
#[must_use]
pub fn of(&self, block: Block) -> &[Block] {
self.of.get(block.index()).map_or(&[][..], Vec::as_slice)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ControlDependence {
on: Vec<Vec<Block>>,
}
impl ControlDependence {
#[must_use]
pub fn new(cfg: &Cfg, post: &PostDominators) -> Self {
let mut on = vec![Vec::new(); cfg.capacity()];
for &block in cfg.postorder() {
let invented = post.fake_exits().contains(&block) || cfg.successors(block).is_empty();
let arrivals = cfg.successors(block).len() + usize::from(invented);
if arrivals < 2 {
continue;
}
let stop = post.immediate_post_dominator(block);
for &succ in cfg.successors(block) {
let mut runner = succ;
while Some(runner) != stop {
on[runner.index()].push(block);
match post.immediate_post_dominator(runner) {
Some(next) => runner = next,
None => break,
}
}
}
}
for list in &mut on {
list.sort_unstable_by_key(|b: &Block| b.index());
list.dedup();
}
Self { on }
}
#[must_use]
pub fn on(&self, block: Block) -> &[Block] {
self.on.get(block.index()).map_or(&[][..], Vec::as_slice)
}
#[must_use]
pub fn unconditional(&self, block: Block) -> bool {
self.on(block).is_empty()
}
}
#[cfg(test)]
mod tests {
use super::{ControlDependence, Frontiers};
use crate::testing::graph;
use crate::{Cfg, Dominators, PostDominators};
use rucc_ir::{Block, Signature};
fn by_definition(cfg: &Cfg, doms: &Dominators) -> Vec<Vec<Block>> {
let mut out = vec![Vec::new(); cfg.capacity()];
for &of in cfg.postorder() {
for &block in cfg.postorder() {
let reaches_a_pred =
cfg.predecessors(block).iter().any(|&pred| doms.dominates(of, pred));
if reaches_a_pred && !doms.strictly_dominates(of, block) {
out[of.index()].push(block);
}
}
}
for list in &mut out {
list.sort_unstable_by_key(|b: &Block| b.index());
}
out
}
fn control_by_definition(cfg: &Cfg, post: &PostDominators) -> Vec<Vec<Block>> {
let mut out = vec![Vec::new(); cfg.capacity()];
for &of in cfg.postorder() {
for &block in cfg.postorder() {
let reaches_a_succ =
cfg.successors(block).iter().any(|&succ| post.post_dominates(of, succ));
if reaches_a_succ && !post.strictly_post_dominates(of, block) {
out[of.index()].push(block);
}
}
}
for list in &mut out {
list.sort_unstable_by_key(|b: &Block| b.index());
}
out
}
fn frontiers(edges: &[&[usize]]) -> Vec<Vec<usize>> {
let func = graph(edges);
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let built = Frontiers::new(&cfg, &doms);
(0..edges.len())
.map(|index| built.of(Block::from_usize(index)).iter().map(|b| b.index()).collect())
.collect()
}
fn depends(edges: &[&[usize]]) -> Vec<Vec<usize>> {
let func = graph(edges);
let cfg = Cfg::new(&func);
let post = PostDominators::new(&cfg);
let built = ControlDependence::new(&cfg, &post);
(0..edges.len())
.map(|index| built.on(Block::from_usize(index)).iter().map(|b| b.index()).collect())
.collect()
}
#[test]
fn a_chain_of_blocks_has_no_frontier_anywhere() {
assert_eq!(frontiers(&[&[1], &[2], &[]]), vec![vec![], vec![], vec![]]);
}
#[test]
fn the_two_arms_of_a_branch_meet_at_the_block_after_it() {
let df = frontiers(&[&[1, 2], &[3], &[3], &[]]);
assert_eq!(df, vec![vec![], vec![3], vec![3], vec![]]);
}
#[test]
fn a_loop_header_is_in_its_own_frontier() {
let df = frontiers(&[&[1], &[1, 2], &[]]);
assert_eq!(df, vec![vec![], vec![1], vec![]]);
}
#[test]
fn a_back_edge_to_the_entry_puts_the_entry_in_its_own_frontier() {
let df = frontiers(&[&[1, 2], &[0], &[]]);
assert_eq!(df, vec![vec![0], vec![0], vec![]]);
}
#[test]
fn the_frontier_is_what_the_definition_says_on_every_graph_the_design_names() {
for edges in shapes() {
let func = graph(edges);
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let built = Frontiers::new(&cfg, &doms);
let wanted = by_definition(&cfg, &doms);
for &block in cfg.postorder() {
assert_eq!(
built.of(block),
wanted[block.index()].as_slice(),
"block {} of {edges:?}",
block.index()
);
}
}
}
#[test]
fn control_dependence_is_what_the_definition_says_on_every_graph_the_design_names() {
for edges in shapes() {
let func = graph(edges);
let cfg = Cfg::new(&func);
let post = PostDominators::new(&cfg);
let built = ControlDependence::new(&cfg, &post);
let wanted = control_by_definition(&cfg, &post);
for &block in cfg.postorder() {
assert_eq!(
built.on(block),
wanted[block.index()].as_slice(),
"block {} of {edges:?}",
block.index()
);
}
}
}
#[test]
fn only_the_arms_of_a_branch_depend_on_it() {
let cd = depends(&[&[1, 2], &[3], &[3], &[]]);
assert_eq!(cd, vec![vec![], vec![0], vec![0], vec![]]);
}
#[test]
fn a_block_that_always_runs_depends_on_nothing() {
let func = graph(&[&[1], &[2], &[]]);
let cfg = Cfg::new(&func);
let post = PostDominators::new(&cfg);
let cd = ControlDependence::new(&cfg, &post);
for index in 0..3 {
assert!(cd.unconditional(Block::from_usize(index)), "block {index}");
}
}
#[test]
fn a_loop_body_depends_on_the_test_that_ends_the_loop() {
let cd = depends(&[&[1], &[2, 3], &[1], &[]]);
assert_eq!(cd, vec![vec![], vec![1], vec![1], vec![]]);
}
#[test]
fn one_arm_falling_through_still_depends_on_the_branch() {
let cd = depends(&[&[1, 2], &[2], &[]]);
assert_eq!(cd, vec![vec![], vec![0], vec![]]);
}
#[test]
fn nothing_after_an_infinite_loop_is_forgotten() {
let func = graph(&[&[1, 2], &[1], &[]]);
let cfg = Cfg::new(&func);
let post = PostDominators::new(&cfg);
assert_eq!(post.fake_exits(), [Block::from_usize(1)]);
assert_eq!(depends(&[&[1, 2], &[1], &[]]), vec![vec![], vec![0, 1], vec![0]]);
}
#[test]
fn a_switch_puts_every_arm_on_the_block_that_chose_it() {
let cd = depends(&[&[1, 2, 3, 4], &[4], &[4], &[4], &[]]);
assert_eq!(cd, vec![vec![], vec![0], vec![0], vec![0], vec![]]);
}
#[test]
fn a_declaration_has_a_frontier_like_anything_else() {
let func = rucc_ir::Func::new(rucc_base::Interner::new().intern("f"), Signature::new());
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let post = PostDominators::new(&cfg);
assert!(Frontiers::new(&cfg, &doms).of(Block::from_usize(0)).is_empty());
assert!(ControlDependence::new(&cfg, &post).on(Block::from_usize(0)).is_empty());
}
fn shapes() -> Vec<&'static [&'static [usize]]> {
vec![
&[&[1], &[2], &[]],
&[&[1, 2], &[3], &[3], &[]],
&[&[1], &[1, 2], &[]],
&[&[1, 2], &[0], &[]],
&[&[1, 2], &[2], &[]],
&[&[1, 2, 3, 4], &[4], &[4], &[4], &[]],
&[&[1], &[2, 3], &[1], &[]],
&[&[1, 2], &[2], &[1, 3], &[]],
&[&[1], &[], &[1]],
&[&[1, 4], &[2, 3], &[4], &[4], &[]],
&[&[1, 2], &[1], &[]],
&[&[1, 2], &[1], &[3, 4], &[3], &[]],
]
}
}