rustc_mir_transform/coverage/
counters.rs

1use std::cmp::Ordering;
2
3use either::Either;
4use itertools::Itertools;
5use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
6use rustc_data_structures::graph::DirectedGraph;
7use rustc_index::IndexVec;
8use rustc_index::bit_set::DenseBitSet;
9use rustc_middle::mir::coverage::{CounterId, CovTerm, Expression, ExpressionId, Op};
10
11use crate::coverage::counters::balanced_flow::BalancedFlowGraph;
12use crate::coverage::counters::node_flow::{
13    CounterTerm, NodeCounters, NodeFlowData, node_flow_data_for_balanced_graph,
14};
15use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
16
17mod balanced_flow;
18pub(crate) mod node_flow;
19mod union_find;
20
21/// Struct containing the results of [`prepare_bcb_counters_data`].
22pub(crate) struct BcbCountersData {
23    pub(crate) node_flow_data: NodeFlowData<BasicCoverageBlock>,
24    pub(crate) priority_list: Vec<BasicCoverageBlock>,
25}
26
27/// Analyzes the coverage graph to create intermediate data structures that
28/// will later be used (during codegen) to create physical counters or counter
29/// expressions for each BCB node that needs one.
30pub(crate) fn prepare_bcb_counters_data(graph: &CoverageGraph) -> BcbCountersData {
31    // Create the derived graphs that are necessary for subsequent steps.
32    let balanced_graph = BalancedFlowGraph::for_graph(graph, |n| !graph[n].is_out_summable);
33    let node_flow_data = node_flow_data_for_balanced_graph(&balanced_graph);
34
35    // Also create a "priority list" of coverage graph nodes, to help determine
36    // which ones get physical counters or counter expressions. This needs to
37    // be done now, because the later parts of the counter-creation process
38    // won't have access to the original coverage graph.
39    let priority_list = make_node_flow_priority_list(graph, balanced_graph);
40
41    BcbCountersData { node_flow_data, priority_list }
42}
43
44/// Arranges the nodes in `balanced_graph` into a list, such that earlier nodes
45/// take priority in being given a counter expression instead of a physical counter.
46fn make_node_flow_priority_list(
47    graph: &CoverageGraph,
48    balanced_graph: BalancedFlowGraph<&CoverageGraph>,
49) -> Vec<BasicCoverageBlock> {
50    // A "reloop" node has exactly one out-edge, which jumps back to the top
51    // of an enclosing loop. Reloop nodes are typically visited more times
52    // than loop-exit nodes, so try to avoid giving them physical counters.
53    let is_reloop_node = IndexVec::from_fn_n(
54        |node| match graph.successors[node].as_slice() {
55            &[succ] => graph.dominates(succ, node),
56            _ => false,
57        },
58        graph.num_nodes(),
59    );
60
61    let mut nodes = balanced_graph.iter_nodes().rev().collect::<Vec<_>>();
62    // The first node is the sink, which must not get a physical counter.
63    assert_eq!(nodes[0], balanced_graph.sink);
64    // Sort the real nodes, such that earlier (lesser) nodes take priority
65    // in being given a counter expression instead of a physical counter.
66    nodes[1..].sort_by(|&a, &b| {
67        // Start with a dummy `Equal` to make the actual tests line up nicely.
68        Ordering::Equal
69            // Prefer a physical counter for return/yield nodes.
70            .then_with(|| Ord::cmp(&graph[a].is_out_summable, &graph[b].is_out_summable))
71            // Prefer an expression for reloop nodes (see definition above).
72            .then_with(|| Ord::cmp(&is_reloop_node[a], &is_reloop_node[b]).reverse())
73            // Otherwise, prefer a physical counter for dominating nodes.
74            .then_with(|| graph.cmp_in_dominator_order(a, b).reverse())
75    });
76    nodes
77}
78
79// Converts node counters into a form suitable for embedding into MIR.
80pub(crate) fn transcribe_counters(
81    old: &NodeCounters<BasicCoverageBlock>,
82    bcb_needs_counter: &DenseBitSet<BasicCoverageBlock>,
83    bcbs_seen: &DenseBitSet<BasicCoverageBlock>,
84) -> CoverageCounters {
85    let mut new = CoverageCounters::with_num_bcbs(bcb_needs_counter.domain_size());
86
87    for bcb in bcb_needs_counter.iter() {
88        if !bcbs_seen.contains(bcb) {
89            // This BCB's code was removed by MIR opts, so its counter is always zero.
90            new.set_node_counter(bcb, CovTerm::Zero);
91            continue;
92        }
93
94        // Our counter-creation algorithm doesn't guarantee that a node's list
95        // of terms starts or ends with a positive term, so partition the
96        // counters into "positive" and "negative" lists for easier handling.
97        let (mut pos, mut neg): (Vec<_>, Vec<_>) = old.counter_terms[bcb]
98            .iter()
99            // Filter out any BCBs that were removed by MIR opts;
100            // this treats them as having an execution count of 0.
101            .filter(|term| bcbs_seen.contains(term.node))
102            .partition_map(|&CounterTerm { node, op }| match op {
103                Op::Add => Either::Left(node),
104                Op::Subtract => Either::Right(node),
105            });
106
107        // These intermediate sorts are not strictly necessary, but were helpful
108        // in reducing churn when switching to the current counter-creation scheme.
109        // They also help to slightly decrease the overall size of the expression
110        // table, due to more subexpressions being shared.
111        pos.sort();
112        neg.sort();
113
114        let mut new_counters_for_sites = |sites: Vec<BasicCoverageBlock>| {
115            sites.into_iter().map(|node| new.ensure_phys_counter(node)).collect::<Vec<_>>()
116        };
117        let pos = new_counters_for_sites(pos);
118        let neg = new_counters_for_sites(neg);
119
120        let pos_counter = new.make_sum(&pos).unwrap_or(CovTerm::Zero);
121        let new_counter = new.make_subtracted_sum(pos_counter, &neg);
122        new.set_node_counter(bcb, new_counter);
123    }
124
125    new
126}
127
128/// Generates and stores coverage counter and coverage expression information
129/// associated with nodes in the coverage graph.
130pub(super) struct CoverageCounters {
131    /// List of places where a counter-increment statement should be injected
132    /// into MIR, each with its corresponding counter ID.
133    pub(crate) phys_counter_for_node: FxIndexMap<BasicCoverageBlock, CounterId>,
134    pub(crate) next_counter_id: CounterId,
135
136    /// Coverage counters/expressions that are associated with individual BCBs.
137    pub(crate) node_counters: IndexVec<BasicCoverageBlock, Option<CovTerm>>,
138
139    /// Table of expression data, associating each expression ID with its
140    /// corresponding operator (+ or -) and its LHS/RHS operands.
141    pub(crate) expressions: IndexVec<ExpressionId, Expression>,
142    /// Remember expressions that have already been created (or simplified),
143    /// so that we don't create unnecessary duplicates.
144    expressions_memo: FxHashMap<Expression, CovTerm>,
145}
146
147impl CoverageCounters {
148    fn with_num_bcbs(num_bcbs: usize) -> Self {
149        Self {
150            phys_counter_for_node: FxIndexMap::default(),
151            next_counter_id: CounterId::ZERO,
152            node_counters: IndexVec::from_elem_n(None, num_bcbs),
153            expressions: IndexVec::new(),
154            expressions_memo: FxHashMap::default(),
155        }
156    }
157
158    /// Returns the physical counter for the given node, creating it if necessary.
159    fn ensure_phys_counter(&mut self, bcb: BasicCoverageBlock) -> CovTerm {
160        let id = *self.phys_counter_for_node.entry(bcb).or_insert_with(|| {
161            let id = self.next_counter_id;
162            self.next_counter_id = id + 1;
163            id
164        });
165        CovTerm::Counter(id)
166    }
167
168    fn make_expression(&mut self, lhs: CovTerm, op: Op, rhs: CovTerm) -> CovTerm {
169        let new_expr = Expression { lhs, op, rhs };
170        *self.expressions_memo.entry(new_expr.clone()).or_insert_with(|| {
171            let id = self.expressions.push(new_expr);
172            CovTerm::Expression(id)
173        })
174    }
175
176    /// Creates a counter that is the sum of the given counters.
177    ///
178    /// Returns `None` if the given list of counters was empty.
179    fn make_sum(&mut self, counters: &[CovTerm]) -> Option<CovTerm> {
180        counters
181            .iter()
182            .copied()
183            .reduce(|accum, counter| self.make_expression(accum, Op::Add, counter))
184    }
185
186    /// Creates a counter whose value is `lhs - SUM(rhs)`.
187    fn make_subtracted_sum(&mut self, lhs: CovTerm, rhs: &[CovTerm]) -> CovTerm {
188        let Some(rhs_sum) = self.make_sum(rhs) else { return lhs };
189        self.make_expression(lhs, Op::Subtract, rhs_sum)
190    }
191
192    fn set_node_counter(&mut self, bcb: BasicCoverageBlock, counter: CovTerm) -> CovTerm {
193        let existing = self.node_counters[bcb].replace(counter);
194        assert!(
195            existing.is_none(),
196            "node {bcb:?} already has a counter: {existing:?} => {counter:?}"
197        );
198        counter
199    }
200}