use itertools::Itertools;
use crate::analysis::core::{DataflowAnalyzer, Direction, Edge};
use crate::{BlockEnd, BlockId, Lowered};
pub struct ForwardDataflowAnalysis<'db, 'a, TAnalyzer: DataflowAnalyzer<'db, 'a>> {
lowered: &'a Lowered<'db>,
pub analyzer: TAnalyzer,
predecessor_counts: Vec<usize>,
incoming: Vec<Option<TAnalyzer::Info>>,
}
impl<'db, 'a, TAnalyzer: DataflowAnalyzer<'db, 'a>> ForwardDataflowAnalysis<'db, 'a, TAnalyzer> {
pub fn new(lowered: &'a Lowered<'db>, analyzer: TAnalyzer) -> Self {
debug_assert!(
TAnalyzer::DIRECTION == Direction::Forward,
"FwdAnalysis requires an analyzer with DIRECTION == Forward"
);
let predecessor_counts = compute_predecessor_counts(lowered);
let incoming = vec![None; lowered.blocks.len()];
Self { lowered, analyzer, predecessor_counts, incoming }
}
pub fn run(&mut self) -> Vec<Option<TAnalyzer::Info>> {
let n_blocks = self.lowered.blocks.len();
let mut block_info: Vec<Option<TAnalyzer::Info>> = vec![None; n_blocks];
let root_id = BlockId::root();
let mut ready =
vec![(root_id, self.analyzer.initial_info(root_id, &self.lowered.blocks[root_id].end))];
while let Some((block_id, mut info)) = ready.pop() {
let block = &self.lowered.blocks[block_id];
self.analyzer.visit_block_start(&mut info, block_id, block);
self.analyzer.transfer_block(&mut info, block_id, block);
self.propagate_to_successors(block_id, &info, &mut ready);
block_info[block_id.0] = Some(info);
}
assert!(
self.incoming.iter().all(Option::is_none),
"Post run, there should be no-incoming. Actual: {:?}",
self.incoming.iter().map(Option::is_some).zip(&self.predecessor_counts).collect_vec()
);
block_info
}
fn propagate_to_successors(
&mut self,
block_id: BlockId,
info: &TAnalyzer::Info,
ready: &mut Vec<(BlockId, TAnalyzer::Info)>,
) {
let block = &self.lowered.blocks[block_id];
match &block.end {
BlockEnd::Goto(target, remapping) => {
let edge = Edge::Goto { target: *target, remapping };
let target_info = self.analyzer.transfer_edge(info, &edge);
self.add_and_maybe_ready(*target, target_info, ready);
}
BlockEnd::Match { info: match_info } => {
for arm in match_info.arms() {
let edge = Edge::MatchArm { arm, match_info };
let arm_info = self.analyzer.transfer_edge(info, &edge);
self.add_and_maybe_ready(arm.block_id, arm_info, ready);
}
}
BlockEnd::Return(..) | BlockEnd::Panic(_) => {
}
BlockEnd::NotSet => unreachable!("Block end not set"),
}
}
fn add_and_maybe_ready(
&mut self,
target: BlockId,
info: TAnalyzer::Info,
ready: &mut Vec<(BlockId, TAnalyzer::Info)>,
) {
let merged_info = match self.incoming[target.0].take() {
Some(existing) => self.analyzer.merge(self.lowered, (target, 0), existing, info),
None => info,
};
self.predecessor_counts[target.0] -= 1;
if self.predecessor_counts[target.0] == 0 {
ready.push((target, merged_info));
} else {
self.incoming[target.0] = Some(merged_info);
}
}
}
fn compute_predecessor_counts(lowered: &Lowered<'_>) -> Vec<usize> {
let n_blocks = lowered.blocks.len();
let mut counts = vec![0usize; n_blocks];
let mut stack = vec![BlockId::root()];
while let Some(b) = stack.pop() {
let mut handle_target = |target: BlockId| {
if counts[target.0] == 0 {
stack.push(target);
}
counts[target.0] += 1;
};
match &lowered.blocks[b].end {
BlockEnd::Goto(target, _) => handle_target(*target),
BlockEnd::Match { info } => {
info.arms().iter().for_each(|arm| handle_target(arm.block_id));
}
BlockEnd::Return(..) | BlockEnd::Panic(_) | BlockEnd::NotSet => {}
}
}
counts
}