use std::collections::VecDeque;
use std::marker::PhantomData;
use crate::{
analysis::dataflow::{
framework::{AnalysisResults, DataFlowAnalysis, DataFlowCfg, Direction},
lattice::MeetSemiLattice,
},
graph::NodeId,
ir::function::SsaFunction,
target::Target,
};
pub struct DataFlowSolver<T: Target, A: DataFlowAnalysis<T>> {
analysis: A,
in_states: Vec<A::Lattice>,
out_states: Vec<A::Lattice>,
worklist: VecDeque<usize>,
in_worklist: Vec<bool>,
iterations: usize,
_phantom: PhantomData<T>,
}
impl<T: Target, A: DataFlowAnalysis<T>> DataFlowSolver<T, A> {
#[must_use]
pub fn new(analysis: A) -> Self {
Self {
analysis,
in_states: Vec::new(),
out_states: Vec::new(),
worklist: VecDeque::new(),
in_worklist: Vec::new(),
iterations: 0,
_phantom: PhantomData,
}
}
pub fn solve<C: DataFlowCfg>(
mut self,
ssa: &SsaFunction<T>,
cfg: &C,
) -> AnalysisResults<A::Lattice>
where
A::Lattice: Clone,
{
let num_blocks = ssa.block_count();
if num_blocks == 0 {
return AnalysisResults::new(Vec::new(), Vec::new());
}
self.initialize(ssa, cfg);
self.iterate(ssa, cfg);
self.analysis
.finalize(&self.in_states, &self.out_states, ssa);
AnalysisResults::new(self.in_states, self.out_states)
}
#[must_use]
pub const fn iterations(&self) -> usize {
self.iterations
}
fn initialize<C: DataFlowCfg>(&mut self, ssa: &SsaFunction<T>, cfg: &C)
where
A::Lattice: Clone,
{
let num_blocks = ssa.block_count();
let initial = self.analysis.initial(ssa);
let boundary = self.analysis.boundary(ssa);
self.in_states = vec![initial.clone(); num_blocks];
self.out_states = vec![initial; num_blocks];
self.in_worklist = vec![false; num_blocks];
match A::DIRECTION {
Direction::Forward => {
let entry = cfg.entry().index();
if let Some(slot) = self.in_states.get_mut(entry) {
*slot = boundary;
}
}
Direction::Backward => {
for exit in cfg.exits() {
let idx = exit.index();
if let Some(slot) = self.out_states.get_mut(idx) {
*slot = boundary.clone();
}
}
}
}
let order = match A::DIRECTION {
Direction::Forward => cfg.reverse_postorder(),
Direction::Backward => cfg.postorder(),
};
for node in order {
let idx = node.index();
if let Some(slot) = self.in_worklist.get_mut(idx) {
self.worklist.push_back(idx);
*slot = true;
}
}
}
fn iterate<C: DataFlowCfg>(&mut self, ssa: &SsaFunction<T>, cfg: &C)
where
A::Lattice: Clone,
{
while let Some(block_idx) = self.worklist.pop_front() {
if let Some(slot) = self.in_worklist.get_mut(block_idx) {
*slot = false;
}
self.iterations = self.iterations.saturating_add(1);
let changed = match A::DIRECTION {
Direction::Forward => self.process_forward(block_idx, ssa, cfg),
Direction::Backward => self.process_backward(block_idx, ssa, cfg),
};
if changed {
self.add_affected_to_worklist(block_idx, cfg);
}
}
}
fn process_forward<C: DataFlowCfg>(
&mut self,
block_idx: usize,
ssa: &SsaFunction<T>,
cfg: &C,
) -> bool
where
A::Lattice: Clone,
{
let node = NodeId::new(block_idx);
let Some(current_in) = self.in_states.get(block_idx).cloned() else {
return false;
};
let mut input = if cfg.predecessors(node).next().is_none() {
current_in.clone()
} else {
let mut result: Option<A::Lattice> = None;
for pred in cfg.predecessors(node) {
let Some(pred_out) = self.out_states.get(pred.index()) else {
continue;
};
result = Some(match result {
None => pred_out.clone(),
Some(acc) => acc.meet(pred_out),
});
}
result.unwrap_or_else(|| current_in.clone())
};
if node == cfg.entry() {
input = current_in.clone();
}
if let Some(slot) = self.in_states.get_mut(block_idx) {
*slot = input.clone();
}
let Some(block) = ssa.block(block_idx) else {
return false;
};
let output = self.analysis.transfer(block_idx, block, &input, ssa);
let Some(out_slot) = self.out_states.get_mut(block_idx) else {
return false;
};
let changed = output != *out_slot;
*out_slot = output;
changed
}
fn process_backward<C: DataFlowCfg>(
&mut self,
block_idx: usize,
ssa: &SsaFunction<T>,
cfg: &C,
) -> bool
where
A::Lattice: Clone,
{
let node = NodeId::new(block_idx);
let Some(current_out) = self.out_states.get(block_idx).cloned() else {
return false;
};
let mut output = if cfg.successors(node).next().is_none() {
current_out.clone()
} else {
let mut result: Option<A::Lattice> = None;
for succ in cfg.successors(node) {
let Some(succ_in) = self.in_states.get(succ.index()) else {
continue;
};
result = Some(match result {
None => succ_in.clone(),
Some(acc) => acc.meet(succ_in),
});
}
result.unwrap_or_else(|| current_out.clone())
};
if cfg.exits().contains(&node) {
output = current_out.clone();
}
if let Some(slot) = self.out_states.get_mut(block_idx) {
*slot = output.clone();
}
let Some(block) = ssa.block(block_idx) else {
return false;
};
let input = self.analysis.transfer(block_idx, block, &output, ssa);
let Some(in_slot) = self.in_states.get_mut(block_idx) else {
return false;
};
let changed = input != *in_slot;
*in_slot = input;
changed
}
fn add_affected_to_worklist<C: DataFlowCfg>(&mut self, block_idx: usize, cfg: &C) {
let node = NodeId::new(block_idx);
let enqueue = |idx: usize, list: &mut Vec<bool>, work: &mut VecDeque<usize>| {
if let Some(slot) = list.get_mut(idx) {
if !*slot {
work.push_back(idx);
*slot = true;
}
}
};
match A::DIRECTION {
Direction::Forward => {
for succ in cfg.successors(node) {
enqueue(succ.index(), &mut self.in_worklist, &mut self.worklist);
}
}
Direction::Backward => {
for pred in cfg.predecessors(node) {
enqueue(pred.index(), &mut self.in_worklist, &mut self.worklist);
}
}
}
}
}