#[cfg(not(feature = "std"))]
use alloc::collections::HashMap;
#[cfg(feature = "std")]
use std::collections::HashMap;
use super::analysis::Analysis;
use super::cfg::Cfg;
use super::direction::{Backward, Forward};
use super::lattice::Lattice;
use super::solver::solve;
#[derive(Clone, PartialEq, Eq, Debug)]
struct Depth(usize);
impl Lattice for Depth {
fn top() -> Self {
Self(usize::MAX)
}
fn meet(&self, other: &Self) -> Self {
Self(self.0.min(other.0))
}
}
struct ForwardDepth {
deltas: HashMap<u32, isize>,
}
impl Analysis for ForwardDepth {
type Value = Depth;
type Node = u32;
type Dir = Forward;
fn boundary_value(&self) -> Depth {
Depth(0)
}
fn transfer(&self, node: &u32, input: &Depth) -> Depth {
if input.0 == usize::MAX {
return Depth(usize::MAX);
}
let delta = self.deltas.get(node).copied().unwrap_or(0);
Depth(input.0.saturating_add_signed(delta))
}
}
struct BackwardDepth {
deltas: HashMap<u32, isize>,
}
impl Analysis for BackwardDepth {
type Value = Depth;
type Node = u32;
type Dir = Backward;
fn boundary_value(&self) -> Depth {
Depth(0)
}
fn transfer(&self, node: &u32, input: &Depth) -> Depth {
if input.0 == usize::MAX {
return Depth(usize::MAX);
}
let delta = self.deltas.get(node).copied().unwrap_or(0);
Depth(input.0.saturating_add_signed(delta))
}
}
#[test]
fn forward_linear_chain() {
let cfg = Cfg::builder(0u32, 2).edge(0, 1).edge(1, 2).build();
let analysis = ForwardDepth {
deltas: [(0, 1_isize), (1, -1_isize)].into_iter().collect(),
};
let r = solve(&analysis, &cfg);
assert_eq!(r.before(&0).unwrap().0, 0, "entry: boundary seeds 0");
assert_eq!(r.after(&0).unwrap().0, 1, "entry: +1 delta");
assert_eq!(r.before(&1).unwrap().0, 1, "body: receives from entry");
assert_eq!(r.after(&1).unwrap().0, 0, "body: -1 delta");
assert_eq!(r.before(&2).unwrap().0, 0, "exit: receives from body");
assert_eq!(r.after(&2).unwrap().0, 0, "exit: no delta");
}
#[test]
fn forward_diamond_join() {
let cfg = Cfg::builder(0u32, 3)
.edge(0, 1)
.edge(0, 2)
.edge(1, 3)
.edge(2, 3)
.build();
let analysis = ForwardDepth {
deltas: [(1, 2_isize), (2, 5_isize)].into_iter().collect(),
};
let r = solve(&analysis, &cfg);
assert_eq!(r.after(&1).unwrap().0, 2);
assert_eq!(r.after(&2).unwrap().0, 5);
assert_eq!(
r.before(&3).unwrap().0,
2,
"exit: conservative meet of branches"
);
}
#[test]
fn forward_loop_converges() {
let cfg = Cfg::builder(0u32, 3)
.edge(0, 1)
.edge(1, 2)
.edge(1, 3)
.edge(2, 1) .build();
let analysis = ForwardDepth {
deltas: [(0, 1_isize)].into_iter().collect(),
};
let r = solve(&analysis, &cfg);
assert_eq!(r.after(&0).unwrap().0, 1);
assert_eq!(r.before(&1).unwrap().0, 1);
assert_eq!(r.before(&3).unwrap().0, 1, "exit receives stable depth");
}
#[test]
fn backward_linear_chain() {
let cfg = Cfg::builder(0u32, 2).edge(0, 1).edge(1, 2).build();
let analysis = BackwardDepth {
deltas: [(1, 1_isize)].into_iter().collect(),
};
let r = solve(&analysis, &cfg);
assert_eq!(r.after(&2).unwrap().0, 0, "exit boundary seeds 0");
assert_eq!(r.before(&1).unwrap().0, 1);
assert_eq!(r.before(&0).unwrap().0, 1);
}
#[test]
fn cfg_no_edges_single_node() {
let cfg = Cfg::builder(0u32, 0).build();
let analysis = ForwardDepth {
deltas: HashMap::new(),
};
let r = solve(&analysis, &cfg);
assert_eq!(r.before(&0).unwrap().0, 0);
assert_eq!(r.after(&0).unwrap().0, 0);
}
#[test]
fn lattice_meet_identity() {
let x = Depth(42);
let t = Depth::top();
assert_eq!(t.meet(&x), x);
assert_eq!(x.meet(&t), x);
}
#[test]
fn cfg_builder_deduplicates_nodes_and_edges() {
let cfg = Cfg::builder(0u32, 2)
.edge(0, 1)
.edge(0, 1) .edge(1, 2)
.build();
assert_eq!(cfg.nodes().len(), 3);
assert_eq!(cfg.successors(&0).len(), 1);
assert_eq!(cfg.predecessors(&1).len(), 1);
}