use crate::{
graph::{iter::Walker, Edge, Node},
map::{ExtKeyMap, IntKeyMap, Map},
FrozenGraph,
};
use alloc::vec::Vec;
use core::{cmp, fmt::Debug, marker::PhantomData};
pub trait Scc<I>: Clone + Eq {
fn with_entry(entry: I) -> Self;
}
pub trait WithScc<I> {
type Ty: Scc<I>;
fn scc(&self) -> Option<&Self::Ty>;
fn set_scc(&mut self, scc: Self::Ty);
}
struct State<NI, E, EF>
where
EF: for<'a> FnMut(&'a E) -> bool,
{
stack: Vec<NI>,
index: u32,
edge_filter: EF,
_marker: PhantomData<E>,
}
fn recurse<N, E, NI, EI, NM, EM, SM, EF>(
graph: &mut FrozenGraph<N, E, NI, EI, NM, EM>,
block: NI,
indices: &mut SM,
state: &mut State<NI, E, EF>,
) -> (u32, u32)
where
N: WithScc<NI>,
NI: Copy + Eq + Debug + 'static,
EI: Copy + Eq + Debug + 'static,
NM: Map<Node<N, EI>, Key = NI>,
EM: IntKeyMap<Edge<E, NI, EI>, Key = EI>,
SM: ExtKeyMap<(u32, u32), Key = NI>,
EF: FnMut(&E) -> bool,
{
let cur_index = state.index;
let mut low_link = cur_index;
state.index = cur_index + 1;
indices
.try_insert(block, (cur_index, low_link))
.expect("index for block shouldn't be set");
state.stack.push(block);
let mut walker = graph.walk_outputs(block);
while let Some((_, edge)) = walker.walk_next(graph) {
if !(state.edge_filter)(edge.weight()) {
continue;
}
let succ = edge.to();
match indices.get(succ) {
None => {
let (_, succ_low_link) = recurse(graph, succ, indices, state);
low_link = cmp::min(low_link, succ_low_link);
if let Some((_, v)) = indices.get_mut(block) {
*v = low_link;
}
}
Some(&(succ_index, _)) if state.stack.contains(&succ) => {
low_link = cmp::min(low_link, succ_index);
if let Some((_, v)) = indices.get_mut(block) {
*v = low_link;
}
}
_ => (),
}
}
if low_link == cur_index {
let mut cur_block_opt = state.stack.pop();
if let Some(entry) = cur_block_opt {
if entry != block
|| graph
.successors(entry)
.any(|(succ_idx, _)| succ_idx == entry)
{
let scc = N::Ty::with_entry(entry);
while let Some(cur_block) = cur_block_opt {
graph
.node_weight_mut(cur_block)
.expect("invalid node index in stack")
.set_scc(scc.clone());
if cur_block == block {
break;
}
cur_block_opt = state.stack.pop();
}
}
}
}
(cur_index, low_link)
}
pub fn mark_sccs<N, E, NI, EI, NM, EM, SM>(
graph: &mut FrozenGraph<N, E, NI, EI, NM, EM>,
start_node_index: NI,
secondary_map: &mut SM,
) where
N: WithScc<NI>,
NI: Copy + Eq + Debug + 'static,
EI: Copy + Eq + Debug + 'static,
NM: Map<Node<N, EI>, Key = NI>,
EM: IntKeyMap<Edge<E, NI, EI>, Key = EI>,
SM: ExtKeyMap<(u32, u32), Key = NI>,
{
let mut state = State {
stack: Vec::new(),
index: 0,
edge_filter: |_| true,
_marker: PhantomData,
};
secondary_map.clear();
recurse(graph, start_node_index, secondary_map, &mut state);
}
pub fn mark_sccs_with_filter<N, E, NI, EI, NM, EM, SM, EF>(
graph: &mut FrozenGraph<N, E, NI, EI, NM, EM>,
start_node_index: NI,
secondary_map: &mut SM,
edge_filter: EF,
) where
N: WithScc<NI>,
NI: Copy + Eq + Debug + 'static,
EI: Copy + Eq + Debug + 'static,
NM: Map<Node<N, EI>, Key = NI>,
EM: IntKeyMap<Edge<E, NI, EI>, Key = EI>,
SM: ExtKeyMap<(u32, u32), Key = NI>,
EF: FnMut(&E) -> bool,
{
let mut state = State {
stack: Vec::new(),
index: 0,
edge_filter,
_marker: PhantomData,
};
secondary_map.clear();
recurse(graph, start_node_index, secondary_map, &mut state);
}
#[cfg(all(test, feature = "slotmap"))]
mod tests {
use super::*;
use crate::{
aliases::{SecondarySlotMap, SlotMapGraph},
map::slotmap::NodeIndex,
};
use alloc::rc::Rc;
#[derive(Clone, Eq, Debug)]
struct TestScc(Rc<NodeIndex>);
impl PartialEq for TestScc {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl Scc<NodeIndex> for TestScc {
fn with_entry(entry: NodeIndex) -> Self {
Self(Rc::new(entry))
}
}
#[derive(Default, Debug)]
struct TestNodeWeight(Option<TestScc>);
impl WithScc<NodeIndex> for TestNodeWeight {
type Ty = TestScc;
fn scc(&self) -> Option<&Self::Ty> {
self.0.as_ref()
}
fn set_scc(&mut self, scc: Self::Ty) {
self.0 = Some(scc);
}
}
#[test]
fn test_simple_loop() {
let mut graph = SlotMapGraph::<TestNodeWeight, ()>::default();
let entry_node = graph.add_default_node();
let node1 = graph.add_default_node();
let node2 = graph.add_default_node();
let exit_node = graph.add_default_node();
graph.add_edge((), entry_node, node1).unwrap();
graph.add_edge((), node1, node2).unwrap();
graph.add_edge((), node2, exit_node).unwrap();
graph.add_edge((), node2, entry_node).unwrap();
let mut secondary_map = SecondarySlotMap::default();
mark_sccs(&mut graph, entry_node, &mut secondary_map);
let scc = graph.node_weight(entry_node).unwrap().scc().unwrap();
assert_eq!(scc, graph.node_weight(node1).unwrap().scc().unwrap());
assert_eq!(scc, graph.node_weight(node2).unwrap().scc().unwrap());
assert!(graph.node_weight(exit_node).unwrap().scc().is_none());
}
#[test]
fn test_two_loops() {
let mut graph = SlotMapGraph::<TestNodeWeight, ()>::default();
let entry_node = graph.add_default_node();
let left_node1 = graph.add_default_node();
let left_node2 = graph.add_default_node();
let right_node1 = graph.add_default_node();
let right_node2 = graph.add_default_node();
let exit_node = graph.add_default_node();
graph.add_edge((), entry_node, left_node1).unwrap();
graph.add_edge((), left_node1, left_node2).unwrap();
graph.add_edge((), left_node2, left_node1).unwrap();
graph.add_edge((), left_node2, exit_node).unwrap();
graph.add_edge((), entry_node, right_node1).unwrap();
graph.add_edge((), right_node1, right_node2).unwrap();
graph.add_edge((), right_node2, right_node1).unwrap();
graph.add_edge((), right_node2, exit_node).unwrap();
let mut secondary_map = SecondarySlotMap::default();
mark_sccs(&mut graph, entry_node, &mut secondary_map);
let left_scc = graph.node_weight(left_node1).unwrap().scc().unwrap();
let right_scc = graph.node_weight(right_node1).unwrap().scc().unwrap();
assert!(graph.node_weight(exit_node).unwrap().scc().is_none());
assert_eq!(
left_scc,
graph.node_weight(left_node2).unwrap().scc().unwrap()
);
assert_eq!(
right_scc,
graph.node_weight(right_node2).unwrap().scc().unwrap()
);
assert_ne!(left_scc, right_scc);
assert!(graph.node_weight(exit_node).unwrap().scc().is_none());
}
#[test]
fn test_irreducible() {
let mut graph = SlotMapGraph::<TestNodeWeight, ()>::default();
let entry_node = graph.add_default_node();
let cond_node = graph.add_default_node();
let node1 = graph.add_default_node();
let node2 = graph.add_default_node();
let node3 = graph.add_default_node();
graph.add_edge((), entry_node, cond_node).unwrap();
graph.add_edge((), cond_node, node1).unwrap();
graph.add_edge((), cond_node, node2).unwrap();
graph.add_edge((), node1, node2).unwrap();
graph.add_edge((), node2, node3).unwrap();
graph.add_edge((), node3, node1).unwrap();
let mut secondary_map = SecondarySlotMap::default();
mark_sccs(&mut graph, entry_node, &mut secondary_map);
let scc = graph.node_weight(node1).unwrap().scc().unwrap();
assert!(graph.node_weight(entry_node).unwrap().scc().is_none());
assert!(graph.node_weight(cond_node).unwrap().scc().is_none());
assert_eq!(scc, graph.node_weight(node2).unwrap().scc().unwrap());
assert_eq!(scc, graph.node_weight(node3).unwrap().scc().unwrap());
}
}