rustc_mir_transform/coverage/
graph.rs

1use std::cmp::Ordering;
2use std::ops::{Index, IndexMut};
3use std::{mem, slice};
4
5use rustc_data_structures::captures::Captures;
6use rustc_data_structures::fx::FxHashSet;
7use rustc_data_structures::graph::dominators::Dominators;
8use rustc_data_structures::graph::{self, DirectedGraph, StartNode};
9use rustc_index::IndexVec;
10use rustc_index::bit_set::DenseBitSet;
11pub(crate) use rustc_middle::mir::coverage::{BasicCoverageBlock, START_BCB};
12use rustc_middle::mir::{self, BasicBlock, Terminator, TerminatorKind};
13use tracing::debug;
14
15/// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s
16/// nodes are `BasicCoverageBlock`s, which encompass one or more MIR `BasicBlock`s.
17#[derive(Debug)]
18pub(crate) struct CoverageGraph {
19    bcbs: IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
20    bb_to_bcb: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
21    pub(crate) successors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
22    pub(crate) predecessors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
23
24    dominators: Option<Dominators<BasicCoverageBlock>>,
25    /// Allows nodes to be compared in some total order such that _if_
26    /// `a` dominates `b`, then `a < b`. If neither node dominates the other,
27    /// their relative order is consistent but arbitrary.
28    dominator_order_rank: IndexVec<BasicCoverageBlock, u32>,
29    /// A loop header is a node that dominates one or more of its predecessors.
30    is_loop_header: DenseBitSet<BasicCoverageBlock>,
31    /// For each node, the loop header node of its nearest enclosing loop.
32    /// This forms a linked list that can be traversed to find all enclosing loops.
33    enclosing_loop_header: IndexVec<BasicCoverageBlock, Option<BasicCoverageBlock>>,
34}
35
36impl CoverageGraph {
37    pub(crate) fn from_mir(mir_body: &mir::Body<'_>) -> Self {
38        let (bcbs, bb_to_bcb) = Self::compute_basic_coverage_blocks(mir_body);
39
40        // Pre-transform MIR `BasicBlock` successors and predecessors into the BasicCoverageBlock
41        // equivalents. Note that since the BasicCoverageBlock graph has been fully simplified, the
42        // each predecessor of a BCB leader_bb should be in a unique BCB. It is possible for a
43        // `SwitchInt` to have multiple targets to the same destination `BasicBlock`, so
44        // de-duplication is required. This is done without reordering the successors.
45
46        let successors = IndexVec::from_fn_n(
47            |bcb| {
48                let mut seen_bcbs = FxHashSet::default();
49                let terminator = mir_body[bcbs[bcb].last_bb()].terminator();
50                bcb_filtered_successors(terminator)
51                    .into_iter()
52                    .filter_map(|successor_bb| bb_to_bcb[successor_bb])
53                    // Remove duplicate successor BCBs, keeping only the first.
54                    .filter(|&successor_bcb| seen_bcbs.insert(successor_bcb))
55                    .collect::<Vec<_>>()
56            },
57            bcbs.len(),
58        );
59
60        let mut predecessors = IndexVec::from_elem(Vec::new(), &bcbs);
61        for (bcb, bcb_successors) in successors.iter_enumerated() {
62            for &successor in bcb_successors {
63                predecessors[successor].push(bcb);
64            }
65        }
66
67        let num_nodes = bcbs.len();
68        let mut this = Self {
69            bcbs,
70            bb_to_bcb,
71            successors,
72            predecessors,
73            dominators: None,
74            dominator_order_rank: IndexVec::from_elem_n(0, num_nodes),
75            is_loop_header: DenseBitSet::new_empty(num_nodes),
76            enclosing_loop_header: IndexVec::from_elem_n(None, num_nodes),
77        };
78        assert_eq!(num_nodes, this.num_nodes());
79
80        // Set the dominators first, because later init steps rely on them.
81        this.dominators = Some(graph::dominators::dominators(&this));
82
83        // Iterate over all nodes, such that dominating nodes are visited before
84        // the nodes they dominate. Either preorder or reverse postorder is fine.
85        let dominator_order = graph::iterate::reverse_post_order(&this, this.start_node());
86        // The coverage graph is created by traversal, so all nodes are reachable.
87        assert_eq!(dominator_order.len(), this.num_nodes());
88        for (rank, bcb) in (0u32..).zip(dominator_order) {
89            // The dominator rank of each node is its index in a dominator-order traversal.
90            this.dominator_order_rank[bcb] = rank;
91
92            // A node is a loop header if it dominates any of its predecessors.
93            if this.reloop_predecessors(bcb).next().is_some() {
94                this.is_loop_header.insert(bcb);
95            }
96
97            // If the immediate dominator is a loop header, that's our enclosing loop.
98            // Otherwise, inherit the immediate dominator's enclosing loop.
99            // (Dominator order ensures that we already processed the dominator.)
100            if let Some(dom) = this.dominators().immediate_dominator(bcb) {
101                this.enclosing_loop_header[bcb] = this
102                    .is_loop_header
103                    .contains(dom)
104                    .then_some(dom)
105                    .or_else(|| this.enclosing_loop_header[dom]);
106            }
107        }
108
109        // The coverage graph's entry-point node (bcb0) always starts with bb0,
110        // which never has predecessors. Any other blocks merged into bcb0 can't
111        // have multiple (coverage-relevant) predecessors, so bcb0 always has
112        // zero in-edges.
113        assert!(this[START_BCB].leader_bb() == mir::START_BLOCK);
114        assert!(this.predecessors[START_BCB].is_empty());
115
116        this
117    }
118
119    fn compute_basic_coverage_blocks(
120        mir_body: &mir::Body<'_>,
121    ) -> (
122        IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
123        IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
124    ) {
125        let num_basic_blocks = mir_body.basic_blocks.len();
126        let mut bcbs = IndexVec::<BasicCoverageBlock, _>::with_capacity(num_basic_blocks);
127        let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks);
128
129        let mut flush_chain_into_new_bcb = |current_chain: &mut Vec<BasicBlock>| {
130            // Take the accumulated list of blocks, leaving the vector empty
131            // to be used by subsequent BCBs.
132            let basic_blocks = mem::take(current_chain);
133
134            let bcb = bcbs.next_index();
135            for &bb in basic_blocks.iter() {
136                bb_to_bcb[bb] = Some(bcb);
137            }
138
139            let is_out_summable = basic_blocks.last().is_some_and(|&bb| {
140                bcb_filtered_successors(mir_body[bb].terminator()).is_out_summable()
141            });
142            let bcb_data = BasicCoverageBlockData { basic_blocks, is_out_summable };
143            debug!("adding {bcb:?}: {bcb_data:?}");
144            bcbs.push(bcb_data);
145        };
146
147        // Traverse the MIR control-flow graph, accumulating chains of blocks
148        // that can be combined into a single node in the coverage graph.
149        // A depth-first search ensures that if two nodes can be chained
150        // together, they will be adjacent in the traversal order.
151
152        // Accumulates a chain of blocks that will be combined into one BCB.
153        let mut current_chain = vec![];
154
155        let subgraph = CoverageRelevantSubgraph::new(&mir_body.basic_blocks);
156        for bb in graph::depth_first_search(subgraph, mir::START_BLOCK)
157            .filter(|&bb| mir_body[bb].terminator().kind != TerminatorKind::Unreachable)
158        {
159            if let Some(&prev) = current_chain.last() {
160                // Adding a block to a non-empty chain is allowed if the
161                // previous block permits chaining, and the current block has
162                // `prev` as its sole predecessor.
163                let can_chain = subgraph.coverage_successors(prev).is_out_chainable()
164                    && mir_body.basic_blocks.predecessors()[bb].as_slice() == &[prev];
165                if !can_chain {
166                    // The current block can't be added to the existing chain, so
167                    // flush that chain into a new BCB, and start a new chain.
168                    flush_chain_into_new_bcb(&mut current_chain);
169                }
170            }
171
172            current_chain.push(bb);
173        }
174
175        if !current_chain.is_empty() {
176            debug!("flushing accumulated blocks into one last BCB");
177            flush_chain_into_new_bcb(&mut current_chain);
178        }
179
180        (bcbs, bb_to_bcb)
181    }
182
183    #[inline(always)]
184    pub(crate) fn iter_enumerated(
185        &self,
186    ) -> impl Iterator<Item = (BasicCoverageBlock, &BasicCoverageBlockData)> {
187        self.bcbs.iter_enumerated()
188    }
189
190    #[inline(always)]
191    pub(crate) fn bcb_from_bb(&self, bb: BasicBlock) -> Option<BasicCoverageBlock> {
192        if bb.index() < self.bb_to_bcb.len() { self.bb_to_bcb[bb] } else { None }
193    }
194
195    #[inline(always)]
196    fn dominators(&self) -> &Dominators<BasicCoverageBlock> {
197        self.dominators.as_ref().unwrap()
198    }
199
200    #[inline(always)]
201    pub(crate) fn dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool {
202        self.dominators().dominates(dom, node)
203    }
204
205    #[inline(always)]
206    pub(crate) fn cmp_in_dominator_order(
207        &self,
208        a: BasicCoverageBlock,
209        b: BasicCoverageBlock,
210    ) -> Ordering {
211        self.dominator_order_rank[a].cmp(&self.dominator_order_rank[b])
212    }
213
214    /// For the given node, yields the subset of its predecessor nodes that
215    /// it dominates. If that subset is non-empty, the node is a "loop header",
216    /// and each of those predecessors represents an in-edge that jumps back to
217    /// the top of its loop.
218    pub(crate) fn reloop_predecessors(
219        &self,
220        to_bcb: BasicCoverageBlock,
221    ) -> impl Iterator<Item = BasicCoverageBlock> + Captures<'_> {
222        self.predecessors[to_bcb].iter().copied().filter(move |&pred| self.dominates(to_bcb, pred))
223    }
224}
225
226impl Index<BasicCoverageBlock> for CoverageGraph {
227    type Output = BasicCoverageBlockData;
228
229    #[inline]
230    fn index(&self, index: BasicCoverageBlock) -> &BasicCoverageBlockData {
231        &self.bcbs[index]
232    }
233}
234
235impl IndexMut<BasicCoverageBlock> for CoverageGraph {
236    #[inline]
237    fn index_mut(&mut self, index: BasicCoverageBlock) -> &mut BasicCoverageBlockData {
238        &mut self.bcbs[index]
239    }
240}
241
242impl graph::DirectedGraph for CoverageGraph {
243    type Node = BasicCoverageBlock;
244
245    #[inline]
246    fn num_nodes(&self) -> usize {
247        self.bcbs.len()
248    }
249}
250
251impl graph::StartNode for CoverageGraph {
252    #[inline]
253    fn start_node(&self) -> Self::Node {
254        self.bcb_from_bb(mir::START_BLOCK)
255            .expect("mir::START_BLOCK should be in a BasicCoverageBlock")
256    }
257}
258
259impl graph::Successors for CoverageGraph {
260    #[inline]
261    fn successors(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> {
262        self.successors[node].iter().copied()
263    }
264}
265
266impl graph::Predecessors for CoverageGraph {
267    #[inline]
268    fn predecessors(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> {
269        self.predecessors[node].iter().copied()
270    }
271}
272
273/// `BasicCoverageBlockData` holds the data indexed by a `BasicCoverageBlock`.
274///
275/// A `BasicCoverageBlock` (BCB) represents the maximal-length sequence of MIR `BasicBlock`s without
276/// conditional branches, and form a new, simplified, coverage-specific Control Flow Graph, without
277/// altering the original MIR CFG.
278///
279/// Note that running the MIR `SimplifyCfg` transform is not sufficient (and therefore not
280/// necessary). The BCB-based CFG is a more aggressive simplification. For example:
281///
282///   * The BCB CFG ignores (trims) branches not relevant to coverage, such as unwind-related code,
283///     that is injected by the Rust compiler but has no physical source code to count. This also
284///     means a BasicBlock with a `Call` terminator can be merged into its primary successor target
285///     block, in the same BCB. (But, note: Issue #78544: "MIR InstrumentCoverage: Improve coverage
286///     of `#[should_panic]` tests and `catch_unwind()` handlers")
287///   * Some BasicBlock terminators support Rust-specific concerns--like borrow-checking--that are
288///     not relevant to coverage analysis. `FalseUnwind`, for example, can be treated the same as
289///     a `Goto`, and merged with its successor into the same BCB.
290///
291/// Each BCB with at least one computed coverage span will have no more than one `Counter`.
292/// In some cases, a BCB's execution count can be computed by `Expression`. Additional
293/// disjoint coverage spans in a BCB can also be counted by `Expression` (by adding `ZERO`
294/// to the BCB's primary counter or expression).
295///
296/// The BCB CFG is critical to simplifying the coverage analysis by ensuring graph path-based
297/// queries (`dominates()`, `predecessors`, `successors`, etc.) have branch (control flow)
298/// significance.
299#[derive(Debug, Clone)]
300pub(crate) struct BasicCoverageBlockData {
301    pub(crate) basic_blocks: Vec<BasicBlock>,
302
303    /// If true, this node's execution count can be assumed to be the sum of the
304    /// execution counts of all of its **out-edges** (assuming no panics).
305    ///
306    /// Notably, this is false for a node ending with [`TerminatorKind::Yield`],
307    /// because the yielding coroutine might not be resumed.
308    pub(crate) is_out_summable: bool,
309}
310
311impl BasicCoverageBlockData {
312    #[inline(always)]
313    pub(crate) fn leader_bb(&self) -> BasicBlock {
314        self.basic_blocks[0]
315    }
316
317    #[inline(always)]
318    pub(crate) fn last_bb(&self) -> BasicBlock {
319        *self.basic_blocks.last().unwrap()
320    }
321}
322
323/// Holds the coverage-relevant successors of a basic block's terminator, and
324/// indicates whether that block can potentially be combined into the same BCB
325/// as its sole successor.
326#[derive(Clone, Copy, Debug)]
327struct CoverageSuccessors<'a> {
328    /// Coverage-relevant successors of the corresponding terminator.
329    /// There might be 0, 1, or multiple targets.
330    targets: &'a [BasicBlock],
331    /// `Yield` terminators are not chainable, because their sole out-edge is
332    /// only followed if/when the generator is resumed after the yield.
333    is_yield: bool,
334}
335
336impl CoverageSuccessors<'_> {
337    /// If `false`, this terminator cannot be chained into another block when
338    /// building the coverage graph.
339    fn is_out_chainable(&self) -> bool {
340        // If a terminator is out-summable and has exactly one out-edge, then
341        // it is eligible to be chained into its successor block.
342        self.is_out_summable() && self.targets.len() == 1
343    }
344
345    /// Returns true if the terminator itself is assumed to have the same
346    /// execution count as the sum of its out-edges (assuming no panics).
347    fn is_out_summable(&self) -> bool {
348        !self.is_yield && !self.targets.is_empty()
349    }
350}
351
352impl IntoIterator for CoverageSuccessors<'_> {
353    type Item = BasicBlock;
354    type IntoIter = impl DoubleEndedIterator<Item = Self::Item>;
355
356    fn into_iter(self) -> Self::IntoIter {
357        self.targets.iter().copied()
358    }
359}
360
361// Returns the subset of a block's successors that are relevant to the coverage
362// graph, i.e. those that do not represent unwinds or false edges.
363// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
364// `catch_unwind()` handlers.
365fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> CoverageSuccessors<'a> {
366    use TerminatorKind::*;
367    let mut is_yield = false;
368    let targets = match &terminator.kind {
369        // A switch terminator can have many coverage-relevant successors.
370        SwitchInt { targets, .. } => targets.all_targets(),
371
372        // A yield terminator has exactly 1 successor, but should not be chained,
373        // because its resume edge has a different execution count.
374        Yield { resume, .. } => {
375            is_yield = true;
376            slice::from_ref(resume)
377        }
378
379        // These terminators have exactly one coverage-relevant successor,
380        // and can be chained into it.
381        Assert { target, .. }
382        | Drop { target, .. }
383        | FalseEdge { real_target: target, .. }
384        | FalseUnwind { real_target: target, .. }
385        | Goto { target } => slice::from_ref(target),
386
387        // A call terminator can normally be chained, except when it has no
388        // successor because it is known to diverge.
389        Call { target: maybe_target, .. } => maybe_target.as_slice(),
390
391        // An inline asm terminator can normally be chained, except when it
392        // diverges or uses asm goto.
393        InlineAsm { targets, .. } => &targets,
394
395        // These terminators have no coverage-relevant successors.
396        CoroutineDrop
397        | Return
398        | TailCall { .. }
399        | Unreachable
400        | UnwindResume
401        | UnwindTerminate(_) => &[],
402    };
403
404    CoverageSuccessors { targets, is_yield }
405}
406
407/// Wrapper around a [`mir::BasicBlocks`] graph that restricts each node's
408/// successors to only the ones considered "relevant" when building a coverage
409/// graph.
410#[derive(Clone, Copy)]
411struct CoverageRelevantSubgraph<'a, 'tcx> {
412    basic_blocks: &'a mir::BasicBlocks<'tcx>,
413}
414impl<'a, 'tcx> CoverageRelevantSubgraph<'a, 'tcx> {
415    fn new(basic_blocks: &'a mir::BasicBlocks<'tcx>) -> Self {
416        Self { basic_blocks }
417    }
418
419    fn coverage_successors(&self, bb: BasicBlock) -> CoverageSuccessors<'_> {
420        bcb_filtered_successors(self.basic_blocks[bb].terminator())
421    }
422}
423impl<'a, 'tcx> graph::DirectedGraph for CoverageRelevantSubgraph<'a, 'tcx> {
424    type Node = BasicBlock;
425
426    fn num_nodes(&self) -> usize {
427        self.basic_blocks.num_nodes()
428    }
429}
430impl<'a, 'tcx> graph::Successors for CoverageRelevantSubgraph<'a, 'tcx> {
431    fn successors(&self, bb: Self::Node) -> impl Iterator<Item = Self::Node> {
432        self.coverage_successors(bb).into_iter()
433    }
434}