Skip to main content

celox_analysis/
cfg.rs

1//! Deterministic control-flow analysis over dense, IR-independent block IDs.
2
3use std::collections::BTreeSet;
4use std::fmt;
5
6/// A malformed dense control-flow graph.
7#[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/// Immediate dominators, constant-time dominance, and logarithmic LCA queries.
53#[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    /// Compute dominance without constructing loop, SCC, or frontier tables.
65    /// Vertices unreachable from `root` have no dominance interval.
66    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    /// Construct dominance intervals from an independently supplied idom tree.
72    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        // Heavy paths need one word per block. A root-to-leaf path contains
138        // at most logarithmically many light edges, bounding LCA walks without
139        // a binary-lifting table for every ancestor level.
140        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    /// Distance from the root, or None for an unreachable or unknown block.
180    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/// Post-dominance tree rooted at a synthetic common exit.
222#[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/// Complete analysis of one reachable dense CFG.
266#[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/// Forward-only CFG analysis used by SSA construction and machine backends.
283///
284/// Unlike [`ControlFlowGraph`], this does not construct postdominators or
285/// control dependence. SCC membership is retained because loop-sensitive
286/// placement needs to reject irreducible cycles without materializing the
287/// potentially dense reverse/control-dependence graphs.
288#[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    /// Analyze the forward properties of a graph without changing block IDs.
302    pub fn analyze(successors: Vec<Vec<usize>>, root: usize) -> Result<Self, CfgError> {
303        Self::analyze_impl(successors, root, true)
304    }
305
306    /// Analyze dominance, loops, and SCCs without constructing a dominance
307    /// frontier.
308    ///
309    /// This is for placement clients which issue dominance queries but do not
310    /// construct SSA. The returned frontier table has one empty entry per
311    /// block so accidental frontier use is explicit in tests and diagnostics.
312    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    /// Analyze a graph without changing the caller's block numbering.
382    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    /// Analyze dominance, post-dominance, loops, and SCCs without constructing
388    /// either dominance frontier or the potentially dense control-dependence
389    /// relation.
390    ///
391    /// Placement clients which only need legal region boundaries should use
392    /// this mode. Its graph tables remain linear in the input CFG size.
393    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
456/// Iterative DFS reverse postorder in the caller's block-number domain.
457pub 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
480/// Lengauer--Tarjan immediate dominators over a dense graph.
481fn 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}