1use alloc::{
2 collections::{BTreeMap, BTreeSet, VecDeque},
3 vec::Vec,
4};
5
6use crate::GlobalItemIndex;
7
8#[derive(Debug)]
11pub struct CycleError(BTreeSet<GlobalItemIndex>);
12
13impl CycleError {
14 pub fn new(nodes: impl IntoIterator<Item = GlobalItemIndex>) -> Self {
15 Self(nodes.into_iter().collect())
16 }
17
18 pub fn into_node_ids(self) -> impl ExactSizeIterator<Item = GlobalItemIndex> {
19 self.0.into_iter()
20 }
21}
22
23#[derive(Default, Clone)]
41pub struct CallGraph {
42 nodes: BTreeMap<GlobalItemIndex, Vec<GlobalItemIndex>>,
44}
45
46impl CallGraph {
47 pub fn out_edges(&self, gid: GlobalItemIndex) -> &[GlobalItemIndex] {
49 self.nodes.get(&gid).map(Vec::as_slice).unwrap_or(&[])
50 }
51
52 pub fn get_or_insert_node(&mut self, id: GlobalItemIndex) -> &mut Vec<GlobalItemIndex> {
57 self.nodes.entry(id).or_default()
58 }
59
60 pub fn add_edge(
69 &mut self,
70 caller: GlobalItemIndex,
71 callee: GlobalItemIndex,
72 ) -> Result<(), CycleError> {
73 if caller == callee {
74 return Err(CycleError::new([caller]));
75 }
76
77 self.get_or_insert_node(callee);
79 let callees = self.get_or_insert_node(caller);
81 if callees.contains(&callee) {
83 return Ok(());
84 }
85
86 callees.push(callee);
87 Ok(())
88 }
89
90 pub fn num_predecessors(&self, id: GlobalItemIndex) -> usize {
93 self.nodes.iter().filter(|(_, out_edges)| out_edges.contains(&id)).count()
94 }
95
96 pub fn toposort(&self) -> Result<Vec<GlobalItemIndex>, CycleError> {
102 if self.nodes.is_empty() {
103 return Ok(vec![]);
104 }
105
106 let num_nodes = self.nodes.len();
107 let mut output = Vec::with_capacity(num_nodes);
108
109 let mut in_degree: BTreeMap<GlobalItemIndex, usize> =
111 self.nodes.keys().map(|&k| (k, 0)).collect();
112 for out_edges in self.nodes.values() {
113 for &succ in out_edges {
114 *in_degree.entry(succ).or_default() += 1;
115 }
116 }
117
118 let mut queue: VecDeque<GlobalItemIndex> =
120 in_degree.iter().filter(|&(_, °)| deg == 0).map(|(&n, _)| n).collect();
121
122 while let Some(id) = queue.pop_front() {
124 output.push(id);
125 for &mid in self.out_edges(id) {
126 let deg = in_degree.get_mut(&mid).unwrap();
127 *deg -= 1;
128 if *deg == 0 {
129 queue.push_back(mid);
130 }
131 }
132 }
133
134 if output.len() != num_nodes {
136 let visited: BTreeSet<GlobalItemIndex> = output.iter().copied().collect();
137 let mut in_cycle = BTreeSet::default();
138 for (&n, out_edges) in self.nodes.iter() {
139 if visited.contains(&n) {
140 continue;
141 }
142 in_cycle.insert(n);
143 for &succ in out_edges {
144 if !visited.contains(&succ) {
145 in_cycle.insert(succ);
146 }
147 }
148 }
149 Err(CycleError(in_cycle))
150 } else {
151 Ok(output)
152 }
153 }
154
155 pub fn subgraph(&self, root: GlobalItemIndex) -> Self {
158 let mut worklist = VecDeque::from_iter([root]);
159 let mut graph = Self::default();
160 let mut visited = BTreeSet::default();
161
162 while let Some(gid) = worklist.pop_front() {
163 if !visited.insert(gid) {
164 continue;
165 }
166
167 let new_successors = graph.get_or_insert_node(gid);
168 let prev_successors = self.out_edges(gid);
169 worklist.extend(prev_successors.iter().cloned());
170 new_successors.extend_from_slice(prev_successors);
171 }
172
173 graph
174 }
175
176 fn reverse_reachable(&self, root: GlobalItemIndex) -> BTreeSet<GlobalItemIndex> {
178 let mut predecessors: BTreeMap<GlobalItemIndex, Vec<GlobalItemIndex>> =
180 self.nodes.keys().map(|&k| (k, Vec::new())).collect();
181 for (&node, out_edges) in self.nodes.iter() {
182 for &succ in out_edges {
183 predecessors.entry(succ).or_default().push(node);
184 }
185 }
186
187 let mut worklist = VecDeque::from_iter([root]);
189 let mut visited = BTreeSet::default();
190
191 while let Some(gid) = worklist.pop_front() {
192 if !visited.insert(gid) {
193 continue;
194 }
195
196 if let Some(preds) = predecessors.get(&gid) {
197 worklist.extend(preds.iter().copied());
198 }
199 }
200
201 visited
202 }
203
204 pub fn toposort_caller(
212 &self,
213 caller: GlobalItemIndex,
214 ) -> Result<Vec<GlobalItemIndex>, CycleError> {
215 let subgraph = self.subgraph(caller);
217 let num_nodes = subgraph.nodes.len();
218 let mut output = Vec::with_capacity(num_nodes);
219
220 let mut in_degree: BTreeMap<GlobalItemIndex, usize> =
222 subgraph.nodes.keys().map(|&k| (k, 0)).collect();
223 for out_edges in subgraph.nodes.values() {
224 for &succ in out_edges {
225 *in_degree.entry(succ).or_default() += 1;
226 }
227 }
228
229 let caller_has_predecessors = in_degree.get(&caller).copied().unwrap_or(0) > 0;
232
233 in_degree.insert(caller, 0);
236
237 let mut queue = VecDeque::from_iter([caller]);
239
240 while let Some(id) = queue.pop_front() {
242 output.push(id);
243 for &mid in subgraph.out_edges(id) {
244 if mid == caller {
246 continue;
247 }
248 let deg = in_degree.get_mut(&mid).unwrap();
249 *deg -= 1;
250 if *deg == 0 {
251 queue.push_back(mid);
252 }
253 }
254 }
255
256 let has_cycle = caller_has_predecessors || output.len() != num_nodes;
259 if has_cycle {
260 let visited: BTreeSet<GlobalItemIndex> = output.iter().copied().collect();
261 let mut in_cycle = BTreeSet::default();
262
263 for (&n, out_edges) in subgraph.nodes.iter() {
265 if !visited.contains(&n) {
266 in_cycle.insert(n);
267 for &succ in out_edges {
268 if !visited.contains(&succ) {
269 in_cycle.insert(succ);
270 }
271 }
272 }
273 }
274
275 if caller_has_predecessors {
278 in_cycle.extend(subgraph.reverse_reachable(caller));
279 }
280
281 Err(CycleError(in_cycle))
282 } else {
283 Ok(output)
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::{GlobalItemIndex, ModuleIndex, ast::ItemIndex};
292
293 const A: ModuleIndex = ModuleIndex::const_new(1);
294 const B: ModuleIndex = ModuleIndex::const_new(2);
295 const P1: ItemIndex = ItemIndex::const_new(1);
296 const P2: ItemIndex = ItemIndex::const_new(2);
297 const P3: ItemIndex = ItemIndex::const_new(3);
298 const A1: GlobalItemIndex = GlobalItemIndex { module: A, index: P1 };
299 const A2: GlobalItemIndex = GlobalItemIndex { module: A, index: P2 };
300 const A3: GlobalItemIndex = GlobalItemIndex { module: A, index: P3 };
301 const B1: GlobalItemIndex = GlobalItemIndex { module: B, index: P1 };
302 const B2: GlobalItemIndex = GlobalItemIndex { module: B, index: P2 };
303 const B3: GlobalItemIndex = GlobalItemIndex { module: B, index: P3 };
304
305 #[test]
306 fn callgraph_add_edge() {
307 let graph = callgraph_simple();
308
309 assert_eq!(graph.num_predecessors(A1), 0);
311 assert_eq!(graph.num_predecessors(B1), 0);
312 assert_eq!(graph.num_predecessors(A2), 1);
313 assert_eq!(graph.num_predecessors(B2), 2);
314 assert_eq!(graph.num_predecessors(B3), 1);
315 assert_eq!(graph.num_predecessors(A3), 2);
316
317 assert_eq!(graph.out_edges(A1), &[A2]);
318 assert_eq!(graph.out_edges(B1), &[B2]);
319 assert_eq!(graph.out_edges(A2), &[B2, A3]);
320 assert_eq!(graph.out_edges(B2), &[B3]);
321 assert_eq!(graph.out_edges(A3), &[]);
322 assert_eq!(graph.out_edges(B3), &[A3]);
323 }
324
325 #[test]
326 fn callgraph_add_edge_with_cycle() {
327 let graph = callgraph_cycle();
328
329 assert_eq!(graph.num_predecessors(A1), 0);
331 assert_eq!(graph.num_predecessors(B1), 0);
332 assert_eq!(graph.num_predecessors(A2), 2);
333 assert_eq!(graph.num_predecessors(B2), 2);
334 assert_eq!(graph.num_predecessors(B3), 1);
335 assert_eq!(graph.num_predecessors(A3), 1);
336
337 assert_eq!(graph.out_edges(A1), &[A2]);
338 assert_eq!(graph.out_edges(B1), &[B2]);
339 assert_eq!(graph.out_edges(A2), &[B2]);
340 assert_eq!(graph.out_edges(B2), &[B3]);
341 assert_eq!(graph.out_edges(A3), &[A2]);
342 assert_eq!(graph.out_edges(B3), &[A3]);
343 }
344
345 #[test]
346 fn callgraph_subgraph() {
347 let graph = callgraph_simple();
348 let subgraph = graph.subgraph(A2);
349
350 assert_eq!(subgraph.nodes.keys().copied().collect::<Vec<_>>(), vec![A2, A3, B2, B3]);
351 }
352
353 #[test]
354 fn callgraph_with_cycle_subgraph() {
355 let graph = callgraph_cycle();
356 let subgraph = graph.subgraph(A2);
357
358 assert_eq!(subgraph.nodes.keys().copied().collect::<Vec<_>>(), vec![A2, A3, B2, B3]);
359 }
360
361 #[test]
362 fn callgraph_toposort() {
363 let graph = callgraph_simple();
364
365 let sorted = graph.toposort().expect("expected valid topological ordering");
366 assert_eq!(sorted.as_slice(), &[A1, B1, A2, B2, B3, A3]);
367 }
368
369 #[test]
370 fn callgraph_toposort_caller() {
371 let graph = callgraph_simple();
372
373 let sorted = graph.toposort_caller(A2).expect("expected valid topological ordering");
374 assert_eq!(sorted.as_slice(), &[A2, B2, B3, A3]);
375 }
376
377 #[test]
378 fn callgraph_with_cycle_toposort() {
379 let graph = callgraph_cycle();
380
381 let err = graph.toposort().expect_err("expected topological sort to fail with cycle");
382 assert_eq!(err.0.into_iter().collect::<Vec<_>>(), &[A2, A3, B2, B3]);
383 }
384
385 #[test]
386 fn callgraph_toposort_caller_with_reachable_cycle() {
387 let graph = callgraph_cycle();
388
389 let err = graph
390 .toposort_caller(A1)
391 .expect_err("expected toposort_caller to fail when a reachable cycle exists");
392 assert_eq!(err.0.into_iter().collect::<Vec<_>>(), &[A2, A3, B2, B3]);
393 }
394
395 #[test]
396 fn callgraph_toposort_caller_root_closing_cycle() {
397 let graph = callgraph_cycle();
398
399 let err = graph
400 .toposort_caller(A2)
401 .expect_err("expected toposort_caller to detect cycle closing back into root");
402 assert_eq!(err.0.into_iter().collect::<Vec<_>>(), &[A2, A3, B2, B3]);
403 }
404
405 #[test]
406 fn callgraph_add_edge_with_self_cycle_is_error() {
407 let mut graph = CallGraph::default();
408
409 let err = graph.add_edge(A1, A1).expect_err("expected self-edge to be rejected");
410 assert_eq!(err.0.into_iter().collect::<Vec<_>>(), &[A1]);
411 }
412
413 #[test]
414 fn callgraph_rootless_cycle_toposort_is_error() {
415 let mut graph = CallGraph::default();
416 graph.add_edge(A1, B1).expect("A1 -> B1 must be accepted");
417 graph.add_edge(B1, A1).expect("B1 -> A1 must be accepted");
418
419 let err = graph.toposort().expect_err("expected topological sort to fail with cycle");
420 assert_eq!(err.0.into_iter().collect::<Vec<_>>(), &[A1, B1]);
421 }
422
423 #[test]
424 fn callgraph_toposort_whole_graph_cycle_without_roots() {
425 let graph = callgraph_cycle_without_roots();
426 let err = graph.toposort().expect_err(
427 "expected topological sort to fail when every node is blocked behind a cycle",
428 );
429 assert_eq!(err.0.into_iter().collect::<Vec<_>>(), &[A1, A2, A3]);
430 }
431
432 fn callgraph_simple() -> CallGraph {
437 let mut graph = CallGraph::default();
439 graph.add_edge(A1, A2).expect("A1 -> A2 must be accepted");
440 graph.add_edge(B1, B2).expect("B1 -> B2 must be accepted");
441 graph.add_edge(A2, B2).expect("A2 -> B2 must be accepted");
442 graph.add_edge(A2, A3).expect("A2 -> A3 must be accepted");
443 graph.add_edge(B2, B3).expect("B2 -> B3 must be accepted");
444 graph.add_edge(B3, A3).expect("B3 -> A3 must be accepted");
445
446 graph
447 }
448
449 fn callgraph_cycle() -> CallGraph {
454 let mut graph = CallGraph::default();
456 graph.add_edge(A1, A2).expect("A1 -> A2 must be accepted");
457 graph.add_edge(B1, B2).expect("B1 -> B2 must be accepted");
458 graph.add_edge(A2, B2).expect("A2 -> B2 must be accepted");
459 graph.add_edge(B2, B3).expect("B2 -> B3 must be accepted");
460 graph.add_edge(B3, A3).expect("B3 -> A3 must be accepted");
461 graph.add_edge(A3, A2).expect("A3 -> A2 must be accepted");
462
463 graph
464 }
465
466 fn callgraph_cycle_without_roots() -> CallGraph {
472 let mut graph = CallGraph::default();
473 graph.add_edge(A1, A2).expect("A1 -> A2 must be accepted");
474 graph.add_edge(A2, A3).expect("A2 -> A3 must be accepted");
475 graph.add_edge(A3, A1).expect("A3 -> A1 must be accepted");
476
477 graph
478 }
479}