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