use crate::mir::{BlockId, Function};
use solar_data_structures::map::{FxHashMap, FxHashSet};
#[derive(Clone, Debug)]
pub struct CfgInfo {
successors: Vec<Vec<BlockId>>,
reachable: FxHashSet<BlockId>,
rpo: Vec<BlockId>,
dominators: DominatorTree,
reachability: Option<FxHashMap<BlockId, FxHashSet<BlockId>>>,
}
impl CfgInfo {
#[must_use]
pub fn new(func: &Function) -> Self {
let successors = collect_successors(func);
let (reachable, postorder, rpo) = reachable_orders(func, &successors);
let dominators = DominatorTree::compute(func, &postorder);
Self { successors, reachable, rpo, dominators, reachability: None }
}
#[must_use]
pub fn successors(&self, block: BlockId) -> &[BlockId] {
&self.successors[block.index()]
}
#[must_use]
pub fn reachable(&self) -> &FxHashSet<BlockId> {
&self.reachable
}
#[must_use]
pub fn is_reachable(&self, block: BlockId) -> bool {
self.reachable.contains(&block)
}
#[must_use]
pub fn rpo(&self) -> &[BlockId] {
&self.rpo
}
#[must_use]
pub fn dominators(&self) -> &DominatorTree {
&self.dominators
}
pub fn transitive_reachability(&mut self) -> &FxHashMap<BlockId, FxHashSet<BlockId>> {
self.reachability.get_or_insert_with(|| compute_transitive_reachability(&self.successors))
}
}
#[derive(Clone, Debug)]
pub struct DominatorTree {
idoms: Vec<Option<BlockId>>,
children: Vec<Vec<BlockId>>,
}
impl DominatorTree {
fn compute(func: &Function, postorder: &[BlockId]) -> Self {
let block_count = func.blocks.len();
let mut rpo_numbers = vec![usize::MAX; block_count];
for (number, &block) in postorder.iter().rev().enumerate() {
rpo_numbers[block.index()] = number;
}
let mut idoms = vec![None; block_count];
idoms[func.entry_block.index()] = Some(func.entry_block);
let mut changed = true;
while changed {
changed = false;
for &block in postorder.iter().rev() {
if block == func.entry_block {
continue;
}
let mut new_idom: Option<BlockId> = None;
for &pred in &func.blocks[block].predecessors {
if idoms[pred.index()].is_none() {
continue;
}
new_idom = Some(match new_idom {
None => pred,
Some(current) => Self::intersect(&idoms, &rpo_numbers, pred, current),
});
}
if let Some(new_idom) = new_idom
&& idoms[block.index()] != Some(new_idom)
{
idoms[block.index()] = Some(new_idom);
changed = true;
}
}
}
let mut children = vec![Vec::new(); block_count];
for (block_index, idom) in idoms.iter().copied().enumerate() {
let block = BlockId::from_usize(block_index);
if block == func.entry_block {
continue;
}
if let Some(idom) = idom {
children[idom.index()].push(block);
}
}
for children in &mut children {
children.sort_by_key(|block| block.index());
}
Self { idoms, children }
}
fn intersect(
idoms: &[Option<BlockId>],
rpo_numbers: &[usize],
a: BlockId,
b: BlockId,
) -> BlockId {
let (mut a, mut b) = (a, b);
while a != b {
while rpo_numbers[a.index()] > rpo_numbers[b.index()] {
a = idoms[a.index()].expect("processed block has an immediate dominator");
}
while rpo_numbers[b.index()] > rpo_numbers[a.index()] {
b = idoms[b.index()].expect("processed block has an immediate dominator");
}
}
a
}
#[must_use]
pub fn idom(&self, block: BlockId) -> Option<BlockId> {
self.idoms.get(block.index()).copied().flatten()
}
#[must_use]
pub fn dominates(&self, dominator: BlockId, block: BlockId) -> bool {
let mut current = block;
loop {
if current == dominator {
return true;
}
match self.idom(current) {
Some(idom) if idom != current => current = idom,
_ => return false,
}
}
}
#[must_use]
pub fn children(&self, block: BlockId) -> &[BlockId] {
self.children.get(block.index()).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn self_and_dominators(&self, block: BlockId) -> Vec<BlockId> {
let mut out = Vec::new();
let mut current = Some(block);
while let Some(block) = current {
out.push(block);
current = self.idom(block).filter(|&idom| idom != block);
}
out
}
}
fn compute_transitive_reachability(
successors: &[Vec<BlockId>],
) -> FxHashMap<BlockId, FxHashSet<BlockId>> {
let mut reachability = FxHashMap::default();
for block_index in 0..successors.len() {
let block_id = BlockId::from_usize(block_index);
let mut reachable = FxHashSet::default();
let mut stack = successors[block_id.index()].clone();
while let Some(block) = stack.pop() {
if reachable.insert(block) {
stack.extend(successors[block.index()].iter().copied());
}
}
reachability.insert(block_id, reachable);
}
reachability
}
fn collect_successors(func: &Function) -> Vec<Vec<BlockId>> {
func.blocks
.iter()
.map(|block| {
block
.terminator
.as_ref()
.map(|term| term.successors().into_iter().collect())
.unwrap_or_default()
})
.collect()
}
fn reachable_orders(
func: &Function,
successors: &[Vec<BlockId>],
) -> (FxHashSet<BlockId>, Vec<BlockId>, Vec<BlockId>) {
let mut reachable = FxHashSet::default();
let mut postorder = Vec::with_capacity(func.blocks.len());
let mut stack = vec![(func.entry_block, 0usize)];
reachable.insert(func.entry_block);
while let Some((block, next)) = stack.last_mut() {
if let Some(&succ) = successors[block.index()].get(*next) {
*next += 1;
if reachable.insert(succ) {
stack.push((succ, 0));
}
} else {
postorder.push(*block);
stack.pop();
}
}
let mut rpo = postorder.clone();
rpo.reverse();
(reachable, postorder, rpo)
}