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 and constant-time dominance intervals.
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}
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    /// Construct dominance intervals from an independently supplied idom tree.
69    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/// Post-dominance tree rooted at a synthetic common exit.
185#[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/// Complete analysis of one reachable dense CFG.
229#[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/// Forward-only CFG analysis used by SSA construction and machine backends.
246///
247/// Unlike [`ControlFlowGraph`], this does not construct postdominators or
248/// control dependence. SCC membership is retained because loop-sensitive
249/// placement needs to reject irreducible cycles without materializing the
250/// potentially dense reverse/control-dependence graphs.
251#[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    /// Analyze the forward properties of a graph without changing block IDs.
265    pub fn analyze(successors: Vec<Vec<usize>>, root: usize) -> Result<Self, CfgError> {
266        Self::analyze_impl(successors, root, true)
267    }
268
269    /// Analyze dominance, loops, and SCCs without constructing a dominance
270    /// frontier.
271    ///
272    /// This is for placement clients which issue dominance queries but do not
273    /// construct SSA. The returned frontier table has one empty entry per
274    /// block so accidental frontier use is explicit in tests and diagnostics.
275    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    /// Analyze a graph without changing the caller's block numbering.
345    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    /// Analyze dominance, post-dominance, loops, and SCCs without constructing
351    /// either dominance frontier or the potentially dense control-dependence
352    /// relation.
353    ///
354    /// Placement clients which only need legal region boundaries should use
355    /// this mode. Its graph tables remain linear in the input CFG size.
356    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
419/// Iterative DFS reverse postorder in the caller's block-number domain.
420pub 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
443/// Lengauer--Tarjan immediate dominators over a dense graph.
444fn 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}