Skip to content

Commit ab5d0f4

Browse files
committed
Introduce new SCC graph module
This module uses Tarjan's SCC algorithm to walk a graph's SCCs in postorder and build a condensation graph.
1 parent 335e5ef commit ab5d0f4

3 files changed

Lines changed: 718 additions & 0 deletions

File tree

crates/graphwalk/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate alloc;
55
use core::ops::ControlFlow;
66

77
pub mod dfs;
8+
pub mod scc;
89

910
pub trait Graph {
1011
type Node: Copy;

crates/graphwalk/src/scc.rs

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
use core::{
2+
cmp::{self, Ordering},
3+
ops::ControlFlow,
4+
};
5+
6+
use alloc::vec::Vec;
7+
8+
use cranelift_entity::{
9+
EntityList, EntityRef, ListPool, PrimaryMap, SecondaryMap,
10+
packed_option::{PackedOption, ReservedValue},
11+
};
12+
use entity_utils::{define_param_entity, set::DenseEntitySet};
13+
use smallvec::SmallVec;
14+
15+
use crate::{
16+
Graph, PredGraph,
17+
dfs::{PostOrderContext, VisitTracker, WalkPhase},
18+
};
19+
20+
pub struct SccWalkContext<N: EntityRef> {
21+
postorder: PostOrderContext<N>,
22+
node_info: SecondaryMap<N, NodeWalkInfo>,
23+
next_preorder_num: u32,
24+
scc_stack: Vec<N>,
25+
scc_stack_set: DenseEntitySet<N>,
26+
}
27+
28+
impl<N: EntityRef> SccWalkContext<N> {
29+
pub fn new() -> Self {
30+
Self {
31+
postorder: PostOrderContext::new(),
32+
node_info: SecondaryMap::new(),
33+
next_preorder_num: 0,
34+
scc_stack: Vec::new(),
35+
scc_stack_set: DenseEntitySet::new(),
36+
}
37+
}
38+
39+
pub fn reset(&mut self, roots: impl IntoIterator<Item = N>) {
40+
self.scc_stack.clear();
41+
self.scc_stack_set.clear();
42+
self.node_info.clear();
43+
self.next_preorder_num = 0;
44+
45+
self.postorder.reset(roots);
46+
}
47+
48+
pub fn next<'s>(
49+
&mut self,
50+
graph: impl Graph<Node = N>,
51+
scratch: &'s mut Vec<N>,
52+
) -> Option<&'s [N]> {
53+
while let Some((phase, node)) = self.postorder.next_event(
54+
&graph,
55+
&mut WalkVisitTracker::new(&mut self.node_info, &mut self.next_preorder_num),
56+
) {
57+
match phase {
58+
WalkPhase::Pre => {
59+
self.scc_stack.push(node);
60+
self.scc_stack_set.insert(node);
61+
}
62+
WalkPhase::Post => {
63+
let node_num = self.node_info[node].preorder_num;
64+
65+
graph.successors(node, |succ| {
66+
let succ_num = self.node_info[succ].preorder_num;
67+
match succ_num.cmp(&node_num) {
68+
Ordering::Greater => {
69+
// This successor is a proper descendent of ours; any lowlink
70+
// reachable through it is also reachable through us.
71+
self.node_info[node].lowlink = cmp::min(
72+
self.node_info[node].lowlink,
73+
self.node_info[succ].lowlink,
74+
);
75+
}
76+
Ordering::Equal => {
77+
// This is a self-loop; no special treatment is necessary.
78+
}
79+
Ordering::Less => {
80+
// This is either a backedge or a cross-edge. If the successor is
81+
// still on the stack, it belongs to this node's SCC and must be
82+
// taken into account in `lowlink`. Note that we use Tarjan's
83+
// original definition of `lowlink`, which allows exactly one
84+
// cross- or back-edge.
85+
if self.scc_stack_set.contains(succ) {
86+
self.node_info[node].lowlink = cmp::min(
87+
self.node_info[node].lowlink,
88+
self.node_info[succ].preorder_num,
89+
);
90+
}
91+
}
92+
}
93+
});
94+
95+
// Check whether `node` is an SCC root now that we have its final `lowlink`
96+
// value, and collect its SCC if so.
97+
if self.node_info[node].lowlink == node_num {
98+
scratch.clear();
99+
100+
// The newly-completed SCC comprises everything above `node` on the stack,
101+
// including itself; pop them all now.
102+
loop {
103+
let member = self.scc_stack.pop().unwrap();
104+
debug_assert!(self.node_info[member].preorder_num >= node_num);
105+
self.scc_stack_set.remove(member);
106+
scratch.push(member);
107+
if member == node {
108+
break;
109+
}
110+
}
111+
112+
return Some(&scratch[..]);
113+
}
114+
}
115+
}
116+
}
117+
118+
None
119+
}
120+
}
121+
122+
impl<N: EntityRef> Default for SccWalkContext<N> {
123+
fn default() -> Self {
124+
Self::new()
125+
}
126+
}
127+
128+
const UNSET_PREORDER_NUM: u32 = 0;
129+
130+
#[derive(Clone, Copy, Default)]
131+
struct NodeWalkInfo {
132+
preorder_num: u32,
133+
lowlink: u32,
134+
}
135+
136+
struct WalkVisitTracker<'a, N: EntityRef> {
137+
node_info: &'a mut SecondaryMap<N, NodeWalkInfo>,
138+
next_preorder_num: &'a mut u32,
139+
}
140+
141+
impl<'a, N: EntityRef> WalkVisitTracker<'a, N> {
142+
fn new(
143+
node_info: &'a mut SecondaryMap<N, NodeWalkInfo>,
144+
next_preorder_num: &'a mut u32,
145+
) -> Self {
146+
Self {
147+
node_info,
148+
next_preorder_num,
149+
}
150+
}
151+
}
152+
153+
impl<'a, N: EntityRef> VisitTracker<N> for WalkVisitTracker<'a, N> {
154+
fn is_visited(&self, node: N) -> bool {
155+
self.node_info[node].preorder_num != UNSET_PREORDER_NUM
156+
}
157+
158+
fn mark_visited(&mut self, node: N) {
159+
*self.next_preorder_num += 1;
160+
self.node_info[node] = NodeWalkInfo {
161+
preorder_num: *self.next_preorder_num,
162+
lowlink: *self.next_preorder_num,
163+
};
164+
}
165+
}
166+
167+
pub struct SccWalk<G>
168+
where
169+
G: Graph,
170+
G::Node: EntityRef,
171+
{
172+
graph: G,
173+
ctx: SccWalkContext<G::Node>,
174+
}
175+
176+
impl<G> SccWalk<G>
177+
where
178+
G: Graph,
179+
G::Node: EntityRef,
180+
{
181+
pub fn new(graph: G, roots: impl IntoIterator<Item = G::Node>) -> Self {
182+
let mut ctx = SccWalkContext::new();
183+
ctx.reset(roots);
184+
Self { graph, ctx }
185+
}
186+
187+
pub fn next<'s>(&mut self, scratch: &'s mut Vec<G::Node>) -> Option<&'s [G::Node]> {
188+
self.ctx.next(&self.graph, scratch)
189+
}
190+
}
191+
192+
define_param_entity!(Scc<N>, "scc");
193+
194+
struct SccData<N: EntityRef + ReservedValue> {
195+
members: EntityList<N>,
196+
preds: EntityList<Scc<N>>,
197+
succs: EntityList<Scc<N>>,
198+
}
199+
200+
pub struct Condensation<N: EntityRef + ReservedValue> {
201+
sccs: PrimaryMap<Scc<N>, SccData<N>>,
202+
scc_member_pool: ListPool<N>,
203+
scc_link_pool: ListPool<Scc<N>>,
204+
node_sccs: SecondaryMap<N, PackedOption<Scc<N>>>,
205+
}
206+
207+
impl<N: EntityRef + ReservedValue> Condensation<N> {
208+
pub fn compute(graph: impl Graph<Node = N>, roots: impl IntoIterator<Item = N>) -> Self {
209+
let mut members = Vec::new();
210+
let mut walk = SccWalk::new(graph, roots);
211+
212+
let mut condensation = Self {
213+
sccs: PrimaryMap::new(),
214+
scc_member_pool: ListPool::new(),
215+
scc_link_pool: ListPool::new(),
216+
node_sccs: SecondaryMap::new(),
217+
};
218+
219+
while let Some(members) = walk.next(&mut members) {
220+
let interned_members =
221+
EntityList::from_slice(members, &mut condensation.scc_member_pool);
222+
223+
let scc = condensation.sccs.push(SccData {
224+
members: interned_members,
225+
preds: EntityList::new(),
226+
succs: EntityList::new(),
227+
});
228+
229+
for &member in members {
230+
condensation.node_sccs[member] = scc.into();
231+
}
232+
}
233+
234+
let graph = walk.graph;
235+
236+
for (node, scc) in condensation.node_sccs.iter() {
237+
let Some(scc) = scc.expand() else {
238+
continue;
239+
};
240+
241+
graph.successors(node, |succ| {
242+
let succ_scc = condensation.node_sccs[succ].unwrap();
243+
if succ_scc != scc {
244+
condensation.sccs[scc]
245+
.succs
246+
.push(succ_scc, &mut condensation.scc_link_pool);
247+
condensation.sccs[succ_scc]
248+
.preds
249+
.push(scc, &mut condensation.scc_link_pool);
250+
}
251+
});
252+
}
253+
254+
let mut dedup_scratch = SmallVec::new();
255+
for scc_data in condensation.sccs.values_mut() {
256+
dedup_entity_list(
257+
&mut scc_data.preds,
258+
&mut condensation.scc_link_pool,
259+
&mut dedup_scratch,
260+
);
261+
dedup_entity_list(
262+
&mut scc_data.succs,
263+
&mut condensation.scc_link_pool,
264+
&mut dedup_scratch,
265+
);
266+
}
267+
268+
condensation
269+
}
270+
271+
pub fn scc_postorder(&self) -> impl DoubleEndedIterator<Item = Scc<N>> {
272+
self.sccs.keys()
273+
}
274+
275+
pub fn node_scc(&self, node: N) -> Option<Scc<N>> {
276+
self.node_sccs[node].expand()
277+
}
278+
279+
pub fn scc_members(&self, scc: Scc<N>) -> &[N] {
280+
self.sccs[scc].members.as_slice(&self.scc_member_pool)
281+
}
282+
283+
pub fn scc_preds(&self, scc: Scc<N>) -> &[Scc<N>] {
284+
self.sccs[scc].preds.as_slice(&self.scc_link_pool)
285+
}
286+
287+
pub fn scc_succs(&self, scc: Scc<N>) -> &[Scc<N>] {
288+
self.sccs[scc].succs.as_slice(&self.scc_link_pool)
289+
}
290+
}
291+
292+
impl<N: EntityRef + ReservedValue> Graph for Condensation<N> {
293+
type Node = Scc<N>;
294+
295+
fn try_successors(
296+
&self,
297+
node: Self::Node,
298+
f: impl FnMut(Self::Node) -> ControlFlow<()>,
299+
) -> ControlFlow<()> {
300+
self.scc_succs(node).iter().copied().try_for_each(f)
301+
}
302+
}
303+
304+
impl<N: EntityRef + ReservedValue> PredGraph for Condensation<N> {
305+
fn try_predecessors(
306+
&self,
307+
node: Self::Node,
308+
f: impl FnMut(Self::Node) -> ControlFlow<()>,
309+
) -> ControlFlow<()> {
310+
self.scc_preds(node).iter().copied().try_for_each(f)
311+
}
312+
}
313+
314+
fn dedup_entity_list<E: EntityRef + ReservedValue>(
315+
list: &mut EntityList<E>,
316+
pool: &mut ListPool<E>,
317+
scratch: &mut SmallVec<[E; 4]>,
318+
) {
319+
scratch.clear();
320+
scratch.extend_from_slice(list.as_slice(pool));
321+
scratch.sort_unstable_by_key(|entity| entity.index());
322+
323+
let orig_len = scratch.len();
324+
scratch.dedup();
325+
if scratch.len() < orig_len {
326+
list.truncate(scratch.len(), pool);
327+
list.as_mut_slice(pool).copy_from_slice(scratch);
328+
}
329+
}

0 commit comments

Comments
 (0)