use std::collections::VecDeque;
use anyhow::{Result, ensure};
use crate::{
category::{ALL, Category, CategorySet, Termination},
graph::{FuncId, Graph},
model::{Body, CallSite, Guard, UnwindOrigin},
};
#[derive(Debug, Clone, Copy)]
pub struct Policy {
pub suppressed: CategorySet,
pub follow_inexact: bool,
}
impl Default for Policy {
fn default() -> Self {
Self {
suppressed: CategorySet::oom(),
follow_inexact: true,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NodeState {
pub enabled: CategorySet,
pub unwinds: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Activity {
pub sites: Vec<bool>,
pub calls: Vec<bool>,
}
impl Activity {
#[must_use]
pub fn site(&self, index: usize) -> bool {
self.sites.get(index).copied().unwrap_or(false)
}
#[must_use]
pub fn call(&self, index: usize) -> bool {
self.calls.get(index).copied().unwrap_or(false)
}
}
struct Eval<'a> {
graph: &'a Graph,
policy: Policy,
states: &'a [NodeState],
}
impl Eval<'_> {
const fn unreadable(&self, category: Category) -> NodeState {
let enabled =
CategorySet::single(category).difference(self.policy.suppressed);
NodeState {
enabled,
unwinds: !enabled.is_empty(),
}
}
fn evaluate(&self, id: FuncId) -> NodeState {
let body = self.graph.body(id);
if body.opaque {
return self.unreadable(body.unreadable());
}
let activity = self.activity(body);
let mut state = NodeState::default();
for (i, site) in body.sites.iter().enumerate() {
if !activity.sites[i]
|| self.policy.suppressed.contains(site.category)
{
continue;
}
state.enabled.insert(site.category);
state.unwinds |= site.termination == Termination::Unwind;
}
for (i, call) in body.calls.iter().enumerate() {
if !activity.calls[i] || !self.follows(call) {
continue;
}
let callee = self.callee_state(call);
state.enabled = state.enabled.union(callee.enabled);
state.unwinds |= callee.unwinds;
}
state
}
fn activity(&self, body: &Body) -> Activity {
let mut act = Activity {
sites: vec![false; body.sites.len()],
calls: vec![false; body.calls.len()],
};
let rounds = body.sites.len() + body.calls.len();
for _ in 0..=rounds {
let mut changed = false;
for i in 0..body.sites.len() {
if !act.sites[i]
&& self.guard_live(&body.sites[i].guard, body, &act)
{
act.sites[i] = true;
changed = true;
}
}
for i in 0..body.calls.len() {
if !act.calls[i]
&& self.guard_live(&body.calls[i].guard, body, &act)
{
act.calls[i] = true;
changed = true;
}
}
if !changed {
break;
}
}
act
}
fn guard_live(&self, guard: &Guard, body: &Body, act: &Activity) -> bool {
if guard.normal {
return true;
}
guard.origins.iter().any(|origin| match *origin {
UnwindOrigin::Site(i) => {
let i = i as usize;
body.sites.get(i).is_some_and(|site| {
act.sites[i]
&& site.termination == Termination::Unwind
&& !self.policy.suppressed.contains(site.category)
})
}
UnwindOrigin::Call(i) => {
let i = i as usize;
body.calls.get(i).is_some_and(|call| {
act.calls[i]
&& self.follows(call)
&& self.callee_state(call).unwinds
})
}
})
}
const fn follows(&self, call: &CallSite) -> bool {
self.policy.follow_inexact || call.kind.is_exact()
}
fn callee_state(&self, call: &CallSite) -> NodeState {
let Some(key) = &call.callee else {
return self.unreadable(Category::Unknown);
};
self.graph
.id_of(key)
.map_or_else(NodeState::default, |id| self.states[id.index()])
}
}
#[derive(Debug)]
pub struct Solution {
states: Vec<NodeState>,
policy: Policy,
}
impl Solution {
#[must_use]
pub fn enabled(&self, id: FuncId) -> CategorySet {
self.states[id.index()].enabled
}
#[must_use]
pub fn unwinds(&self, id: FuncId) -> bool {
self.states[id.index()].unwinds
}
#[must_use]
pub fn is_clean(&self, id: FuncId) -> bool {
self.states[id.index()].enabled.is_empty()
}
#[must_use]
pub const fn policy(&self) -> Policy {
self.policy
}
#[must_use]
pub fn activity(&self, graph: &Graph, id: FuncId) -> Activity {
let eval = Eval {
graph,
policy: self.policy,
states: &self.states,
};
eval.activity(graph.body(id))
}
#[must_use]
pub const fn follows(&self, call: &CallSite) -> bool {
self.policy.follow_inexact || call.kind.is_exact()
}
pub fn cleared_by_suppression(&self, graph: &Graph) -> Result<usize> {
let bare = Solver::new(
graph,
Policy {
suppressed: CategorySet::EMPTY,
follow_inexact: self.policy.follow_inexact,
},
)
.solve()?;
Ok(graph
.iter()
.filter(|(_, body)| body.local && !body.opaque)
.filter(|(id, _)| self.is_clean(*id) && !bare.is_clean(*id))
.count())
}
}
pub struct Solver<'g> {
graph: &'g Graph,
policy: Policy,
states: Vec<NodeState>,
}
impl<'g> Solver<'g> {
#[must_use]
pub fn new(graph: &'g Graph, policy: Policy) -> Self {
Self {
graph,
policy,
states: vec![NodeState::default(); graph.len()],
}
}
pub fn solve(mut self) -> Result<Solution> {
let n = self.graph.len();
let mut queued = vec![true; n];
let mut queue: VecDeque<FuncId> =
(0..n).map(FuncId::from_index).collect();
let bound = n.saturating_mul(ALL.len() + 1).saturating_add(1);
let mut updates = 0usize;
while let Some(id) = queue.pop_front() {
queued[id.index()] = false;
let next = {
let eval = Eval {
graph: self.graph,
policy: self.policy,
states: &self.states,
};
eval.evaluate(id)
};
if next == self.states[id.index()] {
continue;
}
self.states[id.index()] = next;
updates += 1;
ensure!(
updates <= bound,
"panic propagation failed to converge after {updates} \
updates over {n} functions"
);
for &caller in self.graph.callers(id) {
if !queued[caller.index()] {
queued[caller.index()] = true;
queue.push_back(caller);
}
}
}
Ok(Solution {
states: self.states,
policy: self.policy,
})
}
}