1use std::collections::BTreeSet;
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum CfgError {
9 Empty,
10 InvalidRoot {
11 root: usize,
12 blocks: usize,
13 },
14 EdgeOutOfRange {
15 source: usize,
16 target: usize,
17 blocks: usize,
18 },
19 Unreachable(Vec<usize>),
20 InvalidGraph(&'static str),
21}
22
23impl fmt::Display for CfgError {
24 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::Empty => formatter.write_str("control-flow graph is empty"),
27 Self::InvalidRoot { root, blocks } => {
28 write!(formatter, "CFG root {root} is outside {blocks} blocks")
29 }
30 Self::EdgeOutOfRange {
31 source,
32 target,
33 blocks,
34 } => write!(
35 formatter,
36 "CFG edge {source} -> {target} is outside {blocks} blocks"
37 ),
38 Self::Unreachable(blocks) => {
39 formatter.write_str("CFG contains unreachable blocks:")?;
40 for block in blocks {
41 write!(formatter, " {block}")?;
42 }
43 Ok(())
44 }
45 Self::InvalidGraph(message) => write!(formatter, "invalid CFG: {message}"),
46 }
47 }
48}
49
50impl std::error::Error for CfgError {}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct DominatorTree {
55 pub idom: Vec<Option<usize>>,
56 pub children: Vec<Vec<usize>>,
57 enter: Vec<usize>,
58 exit: Vec<usize>,
59 depth: Vec<usize>,
60}
61
62impl DominatorTree {
63 fn compute(successors: &[Vec<usize>], root: usize) -> Result<Self, CfgError> {
64 let idom = lengauer_tarjan(successors, root)?;
65 Self::from_idom(idom, root)
66 }
67
68 pub fn from_idom(idom: Vec<Option<usize>>, root: usize) -> Result<Self, CfgError> {
70 if root >= idom.len() {
71 return Err(CfgError::InvalidRoot {
72 root,
73 blocks: idom.len(),
74 });
75 }
76 let mut children = vec![Vec::new(); idom.len()];
77 for (block, parent) in idom.iter().copied().enumerate() {
78 if block == root {
79 if parent.is_some() {
80 return Err(CfgError::InvalidGraph(
81 "dominator root has an immediate dominator",
82 ));
83 }
84 continue;
85 }
86 if let Some(parent) = parent {
87 let Some(parent_children) = children.get_mut(parent) else {
88 return Err(CfgError::InvalidGraph(
89 "immediate dominator is out of range",
90 ));
91 };
92 parent_children.push(block);
93 }
94 }
95 for block_children in &mut children {
96 block_children.sort_unstable();
97 }
98
99 enum Event {
100 Enter(usize, usize),
101 Exit(usize),
102 }
103 let mut enter = vec![usize::MAX; idom.len()];
104 let mut exit = vec![usize::MAX; idom.len()];
105 let mut depth = vec![usize::MAX; idom.len()];
106 let mut time = 0usize;
107 let mut events = vec![Event::Enter(root, 0)];
108 while let Some(event) = events.pop() {
109 match event {
110 Event::Enter(block, block_depth) => {
111 if enter[block] != usize::MAX {
112 return Err(CfgError::InvalidGraph(
113 "immediate-dominator links contain a cycle",
114 ));
115 }
116 enter[block] = time;
117 depth[block] = block_depth;
118 time += 1;
119 events.push(Event::Exit(block));
120 events.extend(
121 children[block]
122 .iter()
123 .rev()
124 .copied()
125 .map(|child| Event::Enter(child, block_depth + 1)),
126 );
127 }
128 Event::Exit(block) => {
129 exit[block] = time;
130 time += 1;
131 }
132 }
133 }
134 Ok(Self {
135 idom,
136 children,
137 enter,
138 exit,
139 depth,
140 })
141 }
142
143 #[must_use]
144 pub fn dominates(&self, dominator: usize, block: usize) -> bool {
145 let (Some(&dominator_enter), Some(&dominator_exit), Some(&block_enter), Some(&block_exit)) = (
146 self.enter.get(dominator),
147 self.exit.get(dominator),
148 self.enter.get(block),
149 self.exit.get(block),
150 ) else {
151 return false;
152 };
153 dominator_enter != usize::MAX
154 && block_enter != usize::MAX
155 && dominator_enter <= block_enter
156 && block_exit <= dominator_exit
157 }
158
159 #[must_use]
160 pub fn lca(&self, left: usize, right: usize) -> Option<usize> {
161 let (Some(&left_depth), Some(&right_depth)) = (self.depth.get(left), self.depth.get(right))
162 else {
163 return None;
164 };
165 if left_depth == usize::MAX || right_depth == usize::MAX {
166 return None;
167 }
168 let mut left = left;
169 let mut right = right;
170 while self.depth[left] > self.depth[right] {
171 left = self.idom[left]?;
172 }
173 while self.depth[right] > self.depth[left] {
174 right = self.idom[right]?;
175 }
176 while left != right {
177 left = self.idom[left]?;
178 right = self.idom[right]?;
179 }
180 Some(left)
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct PostDominatorTree {
187 tree: DominatorTree,
188 virtual_exit: usize,
189 original_blocks: usize,
190}
191
192impl PostDominatorTree {
193 #[must_use]
194 pub fn postdominates(&self, postdominator: usize, block: usize) -> bool {
195 postdominator < self.original_blocks
196 && block < self.original_blocks
197 && self.tree.dominates(postdominator, block)
198 }
199
200 #[must_use]
201 pub fn common_postdominator(&self, left: usize, right: usize) -> Option<usize> {
202 let candidate = self.tree.lca(left, right)?;
203 (candidate != self.virtual_exit && candidate < self.original_blocks).then_some(candidate)
204 }
205
206 #[must_use]
207 pub fn immediate_postdominator(&self, block: usize) -> Option<usize> {
208 let parent = *self.tree.idom.get(block)?.as_ref()?;
209 (parent != self.virtual_exit && parent < self.original_blocks).then_some(parent)
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct StronglyConnectedRegion {
215 pub blocks: Vec<usize>,
216 pub entries: Vec<usize>,
217 pub cyclic: bool,
218 pub reducible_header: Option<usize>,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub struct NaturalLoop {
223 pub header: usize,
224 pub blocks: BTreeSet<usize>,
225 pub parent: Option<usize>,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct ControlFlowGraph {
231 pub root: usize,
232 pub predecessors: Vec<Vec<usize>>,
233 pub successors: Vec<Vec<usize>>,
234 pub dominators: DominatorTree,
235 pub dominance_frontier: Vec<Vec<usize>>,
236 pub postdominators: PostDominatorTree,
237 pub postdominance_frontier: Vec<Vec<usize>>,
238 pub controllers: Vec<Vec<usize>>,
239 pub control_dependents: Vec<Vec<usize>>,
240 pub sccs: Vec<StronglyConnectedRegion>,
241 pub scc_for_block: Vec<usize>,
242 pub loops: Vec<NaturalLoop>,
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct ForwardControlFlowGraph {
253 pub root: usize,
254 pub predecessors: Vec<Vec<usize>>,
255 pub successors: Vec<Vec<usize>>,
256 pub dominators: DominatorTree,
257 pub dominance_frontier: Vec<Vec<usize>>,
258 pub sccs: Vec<StronglyConnectedRegion>,
259 pub scc_for_block: Vec<usize>,
260 pub loops: Vec<NaturalLoop>,
261}
262
263impl ForwardControlFlowGraph {
264 pub fn analyze(successors: Vec<Vec<usize>>, root: usize) -> Result<Self, CfgError> {
266 Self::analyze_impl(successors, root, true)
267 }
268
269 pub fn analyze_structure(successors: Vec<Vec<usize>>, root: usize) -> Result<Self, CfgError> {
276 Self::analyze_impl(successors, root, false)
277 }
278
279 fn analyze_impl(
280 mut successors: Vec<Vec<usize>>,
281 root: usize,
282 include_frontier: bool,
283 ) -> Result<Self, CfgError> {
284 validate_edges(&successors, root)?;
285 for outgoing in &mut successors {
286 let mut seen = BTreeSet::new();
287 outgoing.retain(|successor| seen.insert(*successor));
288 }
289 let order = reverse_postorder(&successors, root)?;
290 if order.len() != successors.len() {
291 let reached = order.into_iter().collect::<BTreeSet<_>>();
292 return Err(CfgError::Unreachable(
293 (0..successors.len())
294 .filter(|block| !reached.contains(block))
295 .collect(),
296 ));
297 }
298
299 let mut predecessors = vec![Vec::new(); successors.len()];
300 for (block, outgoing) in successors.iter().enumerate() {
301 for &successor in outgoing {
302 predecessors[successor].push(block);
303 }
304 }
305 for incoming in &mut predecessors {
306 incoming.sort_unstable();
307 incoming.dedup();
308 }
309
310 let dominators = DominatorTree::compute(&successors, root)?;
311 if dominators
312 .idom
313 .iter()
314 .enumerate()
315 .any(|(block, parent)| block != root && parent.is_none())
316 {
317 return Err(CfgError::InvalidGraph(
318 "reachable block has no immediate dominator",
319 ));
320 }
321 let dominance_frontier = if include_frontier {
322 dominance_frontiers(&successors, &dominators, root)
323 } else {
324 vec![Vec::new(); successors.len()]
325 };
326 let loops = natural_loops(&predecessors, &successors, &dominators)?;
327 let (sccs, scc_for_block) =
328 strongly_connected_regions(&predecessors, &successors, &dominators, root);
329
330 Ok(Self {
331 root,
332 predecessors,
333 successors,
334 dominators,
335 dominance_frontier,
336 sccs,
337 scc_for_block,
338 loops,
339 })
340 }
341}
342
343impl ControlFlowGraph {
344 pub fn analyze(successors: Vec<Vec<usize>>, root: usize) -> Result<Self, CfgError> {
346 let forward = ForwardControlFlowGraph::analyze(successors, root)?;
347 Self::finish(forward, true)
348 }
349
350 pub fn analyze_structure(successors: Vec<Vec<usize>>, root: usize) -> Result<Self, CfgError> {
357 let forward = ForwardControlFlowGraph::analyze_structure(successors, root)?;
358 Self::finish(forward, false)
359 }
360
361 fn finish(forward: ForwardControlFlowGraph, include_frontiers: bool) -> Result<Self, CfgError> {
362 let (postdominators, postdominance_frontier) = build_postdominators(
363 &forward.predecessors,
364 &forward.successors,
365 include_frontiers,
366 )?;
367 let controllers = postdominance_frontier.clone();
368 let mut control_dependents = vec![Vec::new(); forward.successors.len()];
369 if include_frontiers {
370 for (dependent, dependent_controllers) in controllers.iter().enumerate() {
371 for &controller in dependent_controllers {
372 control_dependents[controller].push(dependent);
373 }
374 }
375 for dependents in &mut control_dependents {
376 dependents.sort_unstable();
377 dependents.dedup();
378 }
379 }
380 Ok(Self {
381 root: forward.root,
382 predecessors: forward.predecessors,
383 successors: forward.successors,
384 dominators: forward.dominators,
385 dominance_frontier: forward.dominance_frontier,
386 postdominators,
387 postdominance_frontier,
388 controllers,
389 control_dependents,
390 sccs: forward.sccs,
391 scc_for_block: forward.scc_for_block,
392 loops: forward.loops,
393 })
394 }
395}
396
397fn validate_edges(successors: &[Vec<usize>], root: usize) -> Result<(), CfgError> {
398 if successors.is_empty() {
399 return Err(CfgError::Empty);
400 }
401 if root >= successors.len() {
402 return Err(CfgError::InvalidRoot {
403 root,
404 blocks: successors.len(),
405 });
406 }
407 for (source, outgoing) in successors.iter().enumerate() {
408 if let Some(&target) = outgoing.iter().find(|&&target| target >= successors.len()) {
409 return Err(CfgError::EdgeOutOfRange {
410 source,
411 target,
412 blocks: successors.len(),
413 });
414 }
415 }
416 Ok(())
417}
418
419pub fn reverse_postorder(successors: &[Vec<usize>], root: usize) -> Result<Vec<usize>, CfgError> {
421 validate_edges(successors, root)?;
422 let mut visited = vec![false; successors.len()];
423 let mut postorder = Vec::with_capacity(successors.len());
424 visited[root] = true;
425 let mut stack = vec![(root, 0usize)];
426 while let Some((block, next_successor)) = stack.last_mut() {
427 if *next_successor == successors[*block].len() {
428 postorder.push(*block);
429 stack.pop();
430 continue;
431 }
432 let successor = successors[*block][*next_successor];
433 *next_successor += 1;
434 if !visited[successor] {
435 visited[successor] = true;
436 stack.push((successor, 0));
437 }
438 }
439 postorder.reverse();
440 Ok(postorder)
441}
442
443fn lengauer_tarjan(successors: &[Vec<usize>], root: usize) -> Result<Vec<Option<usize>>, CfgError> {
445 validate_edges(successors, root)?;
446 let mut dfs_number = vec![0usize; successors.len()];
447 let mut vertex = vec![usize::MAX];
448 let mut parent = vec![0usize; successors.len() + 1];
449 dfs_number[root] = 1;
450 vertex.push(root);
451 let mut stack = vec![(root, 0usize)];
452 while let Some((block, next_successor)) = stack.last_mut() {
453 if *next_successor == successors[*block].len() {
454 stack.pop();
455 continue;
456 }
457 let successor = successors[*block][*next_successor];
458 *next_successor += 1;
459 if dfs_number[successor] == 0 {
460 let number = vertex.len();
461 dfs_number[successor] = number;
462 vertex.push(successor);
463 parent[number] = dfs_number[*block];
464 stack.push((successor, 0));
465 }
466 }
467
468 let reachable = vertex.len() - 1;
469 let mut predecessors = vec![Vec::new(); reachable + 1];
470 for (source, outgoing) in successors.iter().enumerate() {
471 let source_number = dfs_number[source];
472 if source_number == 0 {
473 continue;
474 }
475 for &target in outgoing {
476 let target_number = dfs_number[target];
477 if target_number != 0 {
478 predecessors[target_number].push(source_number);
479 }
480 }
481 }
482
483 let mut semi = (0..=reachable).collect::<Vec<_>>();
484 let mut idom_number = vec![0usize; reachable + 1];
485 let mut ancestor = vec![0usize; reachable + 1];
486 let mut label = (0..=reachable).collect::<Vec<_>>();
487 let mut bucket = vec![Vec::<usize>::new(); reachable + 1];
488
489 fn eval(value: usize, ancestor: &mut [usize], label: &mut [usize], semi: &[usize]) -> usize {
490 if ancestor[value] == 0 {
491 return label[value];
492 }
493 let mut path = Vec::new();
494 let mut current = value;
495 while ancestor[current] != 0 && ancestor[ancestor[current]] != 0 {
496 path.push(current);
497 current = ancestor[current];
498 }
499 for node in path.into_iter().rev() {
500 let parent = ancestor[node];
501 if semi[label[parent]] < semi[label[node]] {
502 label[node] = label[parent];
503 }
504 ancestor[node] = ancestor[parent];
505 }
506 label[value]
507 }
508
509 for block in (2..=reachable).rev() {
510 for &predecessor in &predecessors[block] {
511 let representative = eval(predecessor, &mut ancestor, &mut label, &semi);
512 semi[block] = semi[block].min(semi[representative]);
513 }
514 bucket[semi[block]].push(block);
515 let block_parent = parent[block];
516 if block_parent == 0 {
517 return Err(CfgError::InvalidGraph("non-root DFS node has no parent"));
518 }
519 ancestor[block] = block_parent;
520 let pending = std::mem::take(&mut bucket[block_parent]);
521 for candidate in pending {
522 let representative = eval(candidate, &mut ancestor, &mut label, &semi);
523 idom_number[candidate] = if semi[representative] < semi[candidate] {
524 representative
525 } else {
526 block_parent
527 };
528 }
529 }
530 for block in 2..=reachable {
531 if idom_number[block] != semi[block] {
532 let parent = idom_number[block];
533 if parent == 0 {
534 return Err(CfgError::InvalidGraph(
535 "dominator correction references no parent",
536 ));
537 }
538 idom_number[block] = idom_number[parent];
539 }
540 }
541
542 let mut result = vec![None; successors.len()];
543 for block in 2..=reachable {
544 let parent = idom_number[block];
545 if parent == 0 || parent >= vertex.len() {
546 return Err(CfgError::InvalidGraph(
547 "computed immediate dominator is out of range",
548 ));
549 }
550 result[vertex[block]] = Some(vertex[parent]);
551 }
552 Ok(result)
553}
554
555fn dominance_frontiers(
556 successors: &[Vec<usize>],
557 dominators: &DominatorTree,
558 root: usize,
559) -> Vec<Vec<usize>> {
560 let mut frontiers = vec![BTreeSet::<usize>::new(); successors.len()];
561 let mut tree_postorder = Vec::with_capacity(successors.len());
562 let mut stack = vec![(root, false)];
563 while let Some((block, expanded)) = stack.pop() {
564 if expanded {
565 tree_postorder.push(block);
566 continue;
567 }
568 stack.push((block, true));
569 stack.extend(
570 dominators.children[block]
571 .iter()
572 .rev()
573 .copied()
574 .map(|child| (child, false)),
575 );
576 }
577 for block in tree_postorder {
578 for &successor in &successors[block] {
579 if dominators.idom[successor] != Some(block) {
580 frontiers[block].insert(successor);
581 }
582 }
583 for &child in &dominators.children[block] {
584 let child_frontier = frontiers[child].iter().copied().collect::<Vec<_>>();
585 for member in child_frontier {
586 if dominators.idom[member] != Some(block) {
587 frontiers[block].insert(member);
588 }
589 }
590 }
591 }
592 frontiers
593 .into_iter()
594 .map(|frontier| frontier.into_iter().collect())
595 .collect()
596}
597
598fn build_postdominators(
599 predecessors: &[Vec<usize>],
600 successors: &[Vec<usize>],
601 include_frontier: bool,
602) -> Result<(PostDominatorTree, Vec<Vec<usize>>), CfgError> {
603 let original_blocks = successors.len();
604 let virtual_exit = original_blocks;
605 let mut reverse_successors = vec![Vec::new(); original_blocks + 1];
606 reverse_successors[virtual_exit] = successors
607 .iter()
608 .enumerate()
609 .filter_map(|(block, outgoing)| outgoing.is_empty().then_some(block))
610 .collect();
611 for (block, incoming) in predecessors.iter().enumerate() {
612 reverse_successors[block] = incoming.clone();
613 }
614 let tree = DominatorTree::compute(&reverse_successors, virtual_exit)?;
615 let frontiers = if include_frontier {
616 let mut frontiers = dominance_frontiers(&reverse_successors, &tree, virtual_exit);
617 frontiers.truncate(original_blocks);
618 for frontier in &mut frontiers {
619 frontier.retain(|block| *block < original_blocks);
620 }
621 frontiers
622 } else {
623 vec![Vec::new(); original_blocks]
624 };
625 Ok((
626 PostDominatorTree {
627 tree,
628 virtual_exit,
629 original_blocks,
630 },
631 frontiers,
632 ))
633}
634
635fn strongly_connected_regions(
636 predecessors: &[Vec<usize>],
637 successors: &[Vec<usize>],
638 dominators: &DominatorTree,
639 root: usize,
640) -> (Vec<StronglyConnectedRegion>, Vec<usize>) {
641 let mut visited = vec![false; successors.len()];
642 let mut postorder = Vec::with_capacity(successors.len());
643 for seed in std::iter::once(root).chain((0..successors.len()).filter(|block| *block != root)) {
644 if visited[seed] {
645 continue;
646 }
647 visited[seed] = true;
648 let mut stack = vec![(seed, 0usize)];
649 while let Some((block, next_successor)) = stack.last_mut() {
650 if *next_successor == successors[*block].len() {
651 postorder.push(*block);
652 stack.pop();
653 continue;
654 }
655 let successor = successors[*block][*next_successor];
656 *next_successor += 1;
657 if !visited[successor] {
658 visited[successor] = true;
659 stack.push((successor, 0));
660 }
661 }
662 }
663
664 let mut component = vec![usize::MAX; successors.len()];
665 let mut raw_components = Vec::<Vec<usize>>::new();
666 for seed in postorder.into_iter().rev() {
667 if component[seed] != usize::MAX {
668 continue;
669 }
670 let component_id = raw_components.len();
671 component[seed] = component_id;
672 let mut members = Vec::new();
673 let mut stack = vec![seed];
674 while let Some(block) = stack.pop() {
675 members.push(block);
676 for &predecessor in predecessors[block].iter().rev() {
677 if component[predecessor] == usize::MAX {
678 component[predecessor] = component_id;
679 stack.push(predecessor);
680 }
681 }
682 }
683 members.sort_unstable();
684 raw_components.push(members);
685 }
686
687 let regions = raw_components
688 .iter()
689 .enumerate()
690 .map(|(component_id, members)| {
691 let mut entries = BTreeSet::new();
692 for &block in members {
693 if block == root
694 || predecessors[block]
695 .iter()
696 .any(|predecessor| component[*predecessor] != component_id)
697 {
698 entries.insert(block);
699 }
700 }
701 let cyclic = members.len() > 1
702 || members
703 .first()
704 .is_some_and(|block| successors[*block].contains(block));
705 let reducible_header = if entries.len() == 1 {
706 entries.iter().next().copied().filter(|header| {
707 members
708 .iter()
709 .all(|block| dominators.dominates(*header, *block))
710 })
711 } else {
712 None
713 };
714 StronglyConnectedRegion {
715 blocks: members.clone(),
716 entries: entries.into_iter().collect(),
717 cyclic,
718 reducible_header,
719 }
720 })
721 .collect();
722 (regions, component)
723}
724
725fn natural_loops(
726 predecessors: &[Vec<usize>],
727 successors: &[Vec<usize>],
728 dominators: &DominatorTree,
729) -> Result<Vec<NaturalLoop>, CfgError> {
730 let mut by_header = vec![None::<BTreeSet<usize>>; successors.len()];
731 for (tail, outgoing) in successors.iter().enumerate() {
732 for &header in outgoing {
733 if !dominators.dominates(header, tail) {
734 continue;
735 }
736 let blocks = by_header[header].get_or_insert_with(BTreeSet::new);
737 blocks.insert(header);
738 let mut stack = vec![tail];
739 while let Some(block) = stack.pop() {
740 if blocks.insert(block) {
741 stack.extend(predecessors[block].iter().copied());
742 }
743 }
744 }
745 }
746 let mut loops = by_header
747 .into_iter()
748 .enumerate()
749 .filter_map(|(header, blocks)| {
750 blocks.map(|blocks| NaturalLoop {
751 header,
752 blocks,
753 parent: None,
754 })
755 })
756 .collect::<Vec<_>>();
757 loops.sort_by_key(|natural_loop| (natural_loop.blocks.len(), natural_loop.header));
758
759 let mut innermost_for_block = vec![None::<usize>; successors.len()];
760 for child in (0..loops.len()).rev() {
761 let parent = innermost_for_block[loops[child].header];
762 if parent.is_some_and(|parent| !loops[parent].blocks.is_superset(&loops[child].blocks)) {
763 return Err(CfgError::InvalidGraph(
764 "natural loops overlap without nesting",
765 ));
766 }
767 loops[child].parent = parent;
768 for &block in &loops[child].blocks {
769 innermost_for_block[block] = Some(child);
770 }
771 }
772 Ok(loops)
773}
774
775#[cfg(test)]
776mod tests {
777 use super::*;
778
779 #[test]
780 fn linear_graph() {
781 let cfg = ControlFlowGraph::analyze(vec![vec![1], vec![2], vec![]], 0).unwrap();
782 assert_eq!(cfg.dominators.idom, vec![None, Some(0), Some(1)]);
783 assert!(cfg.dominators.dominates(0, 2));
784 assert!(cfg.postdominators.postdominates(2, 0));
785 }
786
787 #[test]
788 fn diamond_frontier_and_control_dependence() {
789 let cfg = ControlFlowGraph::analyze(vec![vec![1, 2], vec![3], vec![3], vec![]], 0).unwrap();
790 assert_eq!(cfg.dominance_frontier[1], vec![3]);
791 assert_eq!(cfg.dominance_frontier[2], vec![3]);
792 assert_eq!(cfg.controllers[1], vec![0]);
793 assert_eq!(cfg.controllers[2], vec![0]);
794 assert_eq!(cfg.control_dependents[0], vec![1, 2]);
795 }
796
797 #[test]
798 fn multiple_exits_use_virtual_postdominator() {
799 let cfg = ControlFlowGraph::analyze(vec![vec![1, 2], vec![], vec![]], 0).unwrap();
800 assert_eq!(cfg.postdominators.common_postdominator(1, 2), None);
801 assert!(!cfg.postdominators.postdominates(1, 0));
802 }
803
804 #[test]
805 fn natural_and_irreducible_regions() {
806 let natural =
807 ControlFlowGraph::analyze(vec![vec![1], vec![2, 3], vec![1], vec![]], 0).unwrap();
808 assert_eq!(natural.loops.len(), 1);
809 assert_eq!(natural.loops[0].header, 1);
810
811 let irreducible =
812 ControlFlowGraph::analyze(vec![vec![1, 2], vec![2], vec![1, 3], vec![]], 0).unwrap();
813 let region = irreducible
814 .sccs
815 .iter()
816 .find(|region| region.cyclic && region.blocks.len() == 2)
817 .unwrap();
818 assert_eq!(region.entries, vec![1, 2]);
819 assert_eq!(region.reducible_header, None);
820 }
821
822 #[test]
823 fn forward_analysis_retains_sccs_without_control_dependence() {
824 let successors = vec![vec![1, 2], vec![2], vec![1, 3], vec![]];
825 let full = ControlFlowGraph::analyze(successors.clone(), 0).unwrap();
826 let forward = ForwardControlFlowGraph::analyze(successors.clone(), 0).unwrap();
827 let structure = ForwardControlFlowGraph::analyze_structure(successors, 0).unwrap();
828 assert_eq!(forward.dominators, full.dominators);
829 assert_eq!(forward.dominance_frontier, full.dominance_frontier);
830 assert_eq!(forward.sccs, full.sccs);
831 assert_eq!(forward.scc_for_block, full.scc_for_block);
832 assert_eq!(forward.loops, full.loops);
833 assert_eq!(structure.dominators, full.dominators);
834 assert_eq!(structure.sccs, full.sccs);
835 assert!(structure.dominance_frontier.iter().all(Vec::is_empty));
836 }
837
838 #[test]
839 fn bidirectional_structure_retains_postdominators_without_control_dependence() {
840 let successors = vec![vec![1, 2], vec![3], vec![3], vec![]];
841 let full = ControlFlowGraph::analyze(successors.clone(), 0).unwrap();
842 let structure = ControlFlowGraph::analyze_structure(successors, 0).unwrap();
843 assert_eq!(structure.dominators, full.dominators);
844 assert_eq!(structure.postdominators, full.postdominators);
845 assert_eq!(structure.sccs, full.sccs);
846 assert!(structure.dominance_frontier.iter().all(Vec::is_empty));
847 assert!(structure.postdominance_frontier.iter().all(Vec::is_empty));
848 assert!(structure.controllers.iter().all(Vec::is_empty));
849 assert!(structure.control_dependents.iter().all(Vec::is_empty));
850 }
851
852 #[test]
853 fn multiple_backedges_to_one_header_form_their_union() {
854 let cfg =
855 ControlFlowGraph::analyze(vec![vec![1], vec![2, 3], vec![1], vec![1]], 0).unwrap();
856
857 assert_eq!(cfg.loops.len(), 1);
858 assert_eq!(cfg.loops[0].header, 1);
859 assert_eq!(cfg.loops[0].blocks, BTreeSet::from([1, 2, 3]));
860 assert_eq!(cfg.loops[0].parent, None);
861 }
862
863 #[test]
864 fn deeply_nested_loop_forest_has_direct_parents() {
865 const DEPTH: usize = 128;
866 const BLOCKS: usize = DEPTH + 1;
867 let mut successors = vec![Vec::new(); BLOCKS];
868 for (block, outgoing) in successors.iter_mut().enumerate().take(DEPTH) {
869 outgoing.push(block + 1);
870 }
871 for header in 1..=DEPTH {
872 successors[DEPTH].push(header);
873 }
874
875 let cfg = ControlFlowGraph::analyze(successors, 0).unwrap();
876
877 assert_eq!(cfg.loops.len(), DEPTH);
878 for (child, loop_info) in cfg.loops.iter().enumerate().take(DEPTH - 1) {
879 assert_eq!(loop_info.parent, Some(child + 1));
880 assert_eq!(loop_info.header, DEPTH - child);
881 }
882 assert_eq!(cfg.loops[DEPTH - 1].header, 1);
883 assert_eq!(cfg.loops[DEPTH - 1].parent, None);
884 }
885
886 #[test]
887 fn reports_bad_edges_and_unreachable_blocks() {
888 assert_eq!(
889 ControlFlowGraph::analyze(vec![vec![2], vec![]], 0).unwrap_err(),
890 CfgError::EdgeOutOfRange {
891 source: 0,
892 target: 2,
893 blocks: 2,
894 }
895 );
896 assert_eq!(
897 ControlFlowGraph::analyze(vec![vec![], vec![]], 0).unwrap_err(),
898 CfgError::Unreachable(vec![1])
899 );
900 }
901
902 #[test]
903 fn deep_graph_is_iterative() {
904 const BLOCKS: usize = 20_000;
905 let mut successors = vec![Vec::new(); BLOCKS];
906 for (block, outgoing) in successors.iter_mut().enumerate().take(BLOCKS - 1) {
907 outgoing.push(block + 1);
908 }
909 let cfg = ControlFlowGraph::analyze(successors, 0).unwrap();
910 assert!(cfg.dominators.dominates(0, BLOCKS - 1));
911 assert!(cfg.postdominators.postdominates(BLOCKS - 1, 0));
912 }
913}