use {
std::collections::{HashSet, VecDeque},
thunderdome::Index,
};
use crate::cfg::{Cfg, Ir};
#[derive(Debug, Clone)]
pub struct FlowAnalysis {
passable: HashSet<Index>,
haltable: HashSet<Index>,
}
impl FlowAnalysis {
pub fn is_passable(&self, node: Index) -> bool {
self.passable.contains(&node)
}
}
pub fn analyze(cfg: &Cfg) -> FlowAnalysis {
Solver::new(cfg).solve()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Constraint {
Passable(Index),
Haltable(Index),
BreakableTo {
breaks_from: Index,
breaks_to: Index,
},
}
pub type Conjunction = Vec<Constraint>;
#[derive(Debug, Clone)]
pub struct Dnf(pub Vec<Conjunction>);
impl Dnf {
pub fn trivially_false() -> Self {
Dnf(vec![])
}
pub fn trivially_true() -> Self {
Dnf(vec![vec![]])
}
pub fn is_trivially_true(&self) -> bool {
self.0.iter().any(|c| c.is_empty())
}
pub fn only_if(conj: Conjunction) -> Self {
Dnf(vec![conj])
}
}
#[derive(Debug)]
pub struct Implication {
pub preconditions: Dnf,
pub consequent: Constraint,
}
#[derive(Debug, Clone, Copy)]
pub enum Validity {
Trivial,
Progress,
NoProgress,
}
#[derive(Debug)]
pub struct Solver<'a> {
cfg: &'a Cfg,
passable: HashSet<Index>,
haltable: HashSet<Index>,
breakable_to: HashSet<(Index, Index)>,
progress: usize,
queue: VecDeque<Implication>,
queried: HashSet<Constraint>,
}
impl<'a> Solver<'a> {
pub fn new(cfg: &'a Cfg) -> Self {
let mut this = Self {
cfg,
passable: HashSet::new(),
haltable: HashSet::new(),
breakable_to: HashSet::new(),
progress: 0,
queue: VecDeque::new(),
queried: HashSet::new(),
};
for (index, _) in cfg.iter() {
this.push(Constraint::Passable(index));
}
this
}
fn insert_fact(&mut self, constraint: Constraint) {
match constraint {
Constraint::Passable(node) => self.passable.insert(node),
Constraint::Haltable(node) => self.haltable.insert(node),
Constraint::BreakableTo {
breaks_from,
breaks_to,
..
} => self.breakable_to.insert((breaks_from, breaks_to)),
};
}
fn push(&mut self, constraint: Constraint) -> bool {
if self.queried.insert(constraint) {
let dnf = preconditions(self.cfg, constraint);
if dnf.is_trivially_true() {
self.insert_fact(constraint);
} else {
self.queue.push_back(Implication {
preconditions: dnf,
consequent: constraint,
});
}
true
} else {
false
}
}
fn pop(&mut self) -> Option<Implication> {
if self.progress > self.queue.len() {
None
} else {
self.progress += 1;
self.queue.pop_front()
}
}
fn constraint_holds(&self, constraint: &Constraint) -> bool {
match constraint {
Constraint::Passable(node) => self.passable.contains(node),
Constraint::Haltable(node) => self.haltable.contains(node),
Constraint::BreakableTo {
breaks_from,
breaks_to,
} => self.breakable_to.contains(&(*breaks_from, *breaks_to)),
}
}
fn try_prove(&mut self, dnf: &Dnf) -> Validity {
if dnf
.0
.iter()
.any(|cj| cj.iter().all(|c| self.constraint_holds(c)))
{
Validity::Trivial
} else {
let mut progress = false;
for &constraint in dnf.0.iter().flatten() {
progress |= self.push(constraint);
}
if progress {
Validity::Progress
} else {
Validity::NoProgress
}
}
}
pub fn solve(mut self) -> FlowAnalysis {
while let Some(implication) = self.pop() {
let validity = self.try_prove(&implication.preconditions);
if let Validity::Trivial = validity {
self.insert_fact(implication.consequent);
} else {
self.queue.push_back(implication);
}
if let Validity::Trivial | Validity::Progress = validity {
self.progress = 0;
}
}
FlowAnalysis {
passable: self.passable,
haltable: self.haltable,
}
}
}
fn preconditions(cfg: &Cfg, constraint: Constraint) -> Dnf {
return match constraint {
Constraint::Passable(node) => passable_preconditions(cfg, node),
Constraint::Haltable(node) => haltable_preconditions(cfg, node),
Constraint::BreakableTo {
breaks_from,
breaks_to,
} => breakable_to_preconditions(cfg, breaks_from, breaks_to),
};
fn passable_preconditions(cfg: &Cfg, node: Index) -> Dnf {
match &cfg[node].expr {
Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error | Ir::Call(None) => {
Dnf::trivially_true()
}
Ir::Break(_) | Ir::Continue(_) => Dnf::trivially_false(),
Ir::Call(Some(callee)) => Dnf::only_if(vec![Constraint::Haltable(*callee)]),
Ir::Split { tx_only, rx_only } => {
let arms = tx_only.iter().chain(rx_only);
Dnf::only_if(arms.map(|&arm| Constraint::Haltable(arm)).collect())
}
Ir::Offer(choices) | Ir::Choose(choices) => Dnf(choices
.iter()
.filter_map(Option::as_ref)
.map(|&c| vec![Constraint::Passable(c)])
.collect()),
Ir::Loop(body) => Dnf::only_if(vec![Constraint::BreakableTo {
breaks_from: body
.expect("loop bodies should always be `Some` after scope resolution"),
breaks_to: node,
}]),
}
}
fn haltable_preconditions(cfg: &Cfg, node_index: Index) -> Dnf {
let node = &cfg[node_index];
match &node.expr {
Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error => match node.next {
Some(cont) => Dnf::only_if(vec![Constraint::Haltable(cont)]),
None => Dnf::trivially_true(),
},
Ir::Call(_) | Ir::Split { .. } | Ir::Loop(_) => {
let mut conj = vec![Constraint::Passable(node_index)];
conj.extend(node.next.map(Constraint::Haltable));
Dnf::only_if(conj)
}
Ir::Choose(choices) | Ir::Offer(choices) => Dnf(choices
.iter()
.map(|&c| c.map(Constraint::Haltable).into_iter().collect())
.chain(node.next.map(|c| vec![Constraint::Haltable(c)]))
.collect()),
Ir::Continue(of_loop) => Dnf::only_if(vec![Constraint::Haltable(*of_loop)]),
Ir::Break(of_loop) => match cfg[*of_loop].next {
Some(cont) => Dnf::only_if(vec![Constraint::Haltable(cont)]),
None => Dnf::trivially_true(),
},
}
}
fn breakable_to_preconditions(cfg: &Cfg, breaks_from: Index, breaks_to: Index) -> Dnf {
let node = &cfg[breaks_from];
match &node.expr {
Ir::Send(_) | Ir::Recv(_) | Ir::Type(_) | Ir::Error => match node.next {
Some(cont) => Dnf::only_if(vec![Constraint::BreakableTo {
breaks_from: cont,
breaks_to,
}]),
None => Dnf::trivially_false(),
},
Ir::Call(_) | Ir::Split { .. } => {
let mut conj = vec![Constraint::Passable(breaks_from)];
conj.extend(node.next.map(|cont| Constraint::BreakableTo {
breaks_from: cont,
breaks_to,
}));
Dnf::only_if(conj)
}
Ir::Choose(choices) | Ir::Offer(choices) => Dnf(choices
.iter()
.filter_map(Option::as_ref)
.chain(node.next.as_ref())
.map(|&c| {
vec![Constraint::BreakableTo {
breaks_from: c,
breaks_to,
}]
})
.collect()),
Ir::Loop(body) => {
let mut disj = vec![vec![Constraint::BreakableTo {
breaks_from: body
.expect("loop bodies should be nonempty after scope resolution"),
breaks_to,
}]];
if let Some(cont) = node.next {
disj.push(vec![
Constraint::Passable(breaks_from),
Constraint::BreakableTo {
breaks_from: cont,
breaks_to,
},
]);
}
Dnf(disj)
}
Ir::Break(of_loop) if *of_loop == breaks_to => Dnf::trivially_true(),
Ir::Break(_) | Ir::Continue(_) => Dnf::trivially_false(),
}
}
}