rustc_mir_transform/coverage/counters/
node_flow.rs

1//! For each node in a control-flow graph, determines whether that node should
2//! have a physical counter, or a counter expression that is derived from the
3//! physical counters of other nodes.
4//!
5//! Based on the algorithm given in
6//! "Optimal measurement points for program frequency counts"
7//! (Knuth & Stevenson, 1973).
8
9use rustc_data_structures::graph;
10use rustc_index::bit_set::DenseBitSet;
11use rustc_index::{Idx, IndexSlice, IndexVec};
12pub(crate) use rustc_middle::mir::coverage::NodeFlowData;
13use rustc_middle::mir::coverage::Op;
14
15use crate::coverage::counters::union_find::UnionFind;
16
17#[cfg(test)]
18mod tests;
19
20/// Creates a "merged" view of an underlying graph.
21///
22/// The given graph is assumed to have [“balanced flow”](balanced-flow),
23/// though it does not necessarily have to be a `BalancedFlowGraph`.
24///
25/// [balanced-flow]: `crate::coverage::counters::balanced_flow::BalancedFlowGraph`.
26pub(crate) fn node_flow_data_for_balanced_graph<G>(graph: G) -> NodeFlowData<G::Node>
27where
28    G: graph::Successors,
29{
30    let mut supernodes = UnionFind::<G::Node>::new(graph.num_nodes());
31
32    // For each node, merge its successors into a single supernode, and
33    // arbitrarily choose one of those successors to represent all of them.
34    let successors = graph
35        .iter_nodes()
36        .map(|node| {
37            graph
38                .successors(node)
39                .reduce(|a, b| supernodes.unify(a, b))
40                .expect("each node in a balanced graph must have at least one out-edge")
41        })
42        .collect::<IndexVec<G::Node, G::Node>>();
43
44    // Now that unification is complete, take a snapshot of the supernode forest,
45    // and resolve each arbitrarily-chosen successor to its canonical root.
46    // (This avoids having to explicitly resolve them later.)
47    let supernodes = supernodes.snapshot();
48    let succ_supernodes = successors.into_iter().map(|succ| supernodes[succ]).collect();
49
50    NodeFlowData { supernodes, succ_supernodes }
51}
52
53/// Uses the graph information in `node_flow_data`, together with a given
54/// permutation of all nodes in the graph, to create physical counters and
55/// counter expressions for each node in the underlying graph.
56///
57/// The given list must contain exactly one copy of each node in the
58/// underlying balanced-flow graph. The order of nodes is used as a hint to
59/// influence counter allocation:
60/// - Earlier nodes are more likely to receive counter expressions.
61/// - Later nodes are more likely to receive physical counters.
62pub(crate) fn make_node_counters<Node: Idx>(
63    node_flow_data: &NodeFlowData<Node>,
64    priority_list: &[Node],
65) -> NodeCounters<Node> {
66    let mut builder = SpantreeBuilder::new(node_flow_data);
67
68    for &node in priority_list {
69        builder.visit_node(node);
70    }
71
72    NodeCounters { counter_terms: builder.finish() }
73}
74
75/// End result of allocating physical counters and counter expressions for the
76/// nodes of a graph.
77#[derive(Debug)]
78pub(crate) struct NodeCounters<Node: Idx> {
79    /// For the given node, returns the finished list of terms that represent
80    /// its physical counter or counter expression. Always non-empty.
81    ///
82    /// If a node was given a physical counter, the term list will contain
83    /// that counter as its sole element.
84    pub(crate) counter_terms: IndexVec<Node, Vec<CounterTerm<Node>>>,
85}
86
87#[derive(Debug)]
88struct SpantreeEdge<Node> {
89    /// If true, this edge in the spantree has been reversed an odd number of
90    /// times, so all physical counters added to its node's counter expression
91    /// need to be negated.
92    is_reversed: bool,
93    /// Each spantree edge is "claimed" by the (regular) node that caused it to
94    /// be created. When a node with a physical counter traverses this edge,
95    /// that counter is added to the claiming node's counter expression.
96    claiming_node: Node,
97    /// Supernode at the other end of this spantree edge. Transitively points
98    /// to the "root" of this supernode's spantree component.
99    span_parent: Node,
100}
101
102/// Part of a node's counter expression, which is a sum of counter terms.
103#[derive(Debug)]
104pub(crate) struct CounterTerm<Node> {
105    /// Whether to add or subtract the value of the node's physical counter.
106    pub(crate) op: Op,
107    /// The node whose physical counter is represented by this term.
108    pub(crate) node: Node,
109}
110
111#[derive(Debug)]
112struct SpantreeBuilder<'a, Node: Idx> {
113    supernodes: &'a IndexSlice<Node, Node>,
114    succ_supernodes: &'a IndexSlice<Node, Node>,
115
116    is_unvisited: DenseBitSet<Node>,
117    /// Links supernodes to each other, gradually forming a spanning tree of
118    /// the merged-flow graph.
119    ///
120    /// A supernode without a span edge is the root of its component of the
121    /// spantree. Nodes that aren't supernodes cannot have a spantree edge.
122    span_edges: IndexVec<Node, Option<SpantreeEdge<Node>>>,
123    /// Shared path buffer recycled by all calls to `yank_to_spantree_root`.
124    yank_buffer: Vec<Node>,
125    /// An in-progress counter expression for each node. Each expression is
126    /// initially empty, and will be filled in as relevant nodes are visited.
127    counter_terms: IndexVec<Node, Vec<CounterTerm<Node>>>,
128}
129
130impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
131    fn new(node_flow_data: &'a NodeFlowData<Node>) -> Self {
132        let NodeFlowData { supernodes, succ_supernodes } = node_flow_data;
133        let num_nodes = supernodes.len();
134        Self {
135            supernodes,
136            succ_supernodes,
137            is_unvisited: DenseBitSet::new_filled(num_nodes),
138            span_edges: IndexVec::from_fn_n(|_| None, num_nodes),
139            yank_buffer: vec![],
140            counter_terms: IndexVec::from_fn_n(|_| vec![], num_nodes),
141        }
142    }
143
144    fn is_supernode(&self, node: Node) -> bool {
145        self.supernodes[node] == node
146    }
147
148    /// Given a supernode, finds the supernode that is the "root" of its
149    /// spantree component. Two nodes that have the same spantree root are
150    /// connected in the spantree.
151    fn spantree_root(&self, this: Node) -> Node {
152        debug_assert!(self.is_supernode(this));
153
154        match self.span_edges[this] {
155            None => this,
156            Some(SpantreeEdge { span_parent, .. }) => self.spantree_root(span_parent),
157        }
158    }
159
160    /// Rotates edges in the spantree so that `this` is the root of its
161    /// spantree component.
162    fn yank_to_spantree_root(&mut self, this: Node) {
163        debug_assert!(self.is_supernode(this));
164
165        // The rotation is done iteratively, by first traversing from `this` to
166        // its root and storing the path in a buffer, and then traversing the
167        // path buffer backwards to reverse all the edges.
168
169        // Recycle the same path buffer for all calls to this method.
170        let path_buf = &mut self.yank_buffer;
171        path_buf.clear();
172        path_buf.push(this);
173
174        // Traverse the spantree until we reach a supernode that has no
175        // span-parent, which must be the root.
176        let mut curr = this;
177        while let &Some(SpantreeEdge { span_parent, .. }) = &self.span_edges[curr] {
178            path_buf.push(span_parent);
179            curr = span_parent;
180        }
181
182        // For each spantree edge `a -> b` in the path that was just traversed,
183        // reverse it to become `a <- b`, while preserving `claiming_node`.
184        for &[a, b] in path_buf.array_windows::<2>().rev() {
185            let SpantreeEdge { is_reversed, claiming_node, span_parent } = self.span_edges[a]
186                .take()
187                .expect("all nodes in the path (except the last) have a `span_parent`");
188            debug_assert_eq!(span_parent, b);
189            debug_assert!(self.span_edges[b].is_none());
190            self.span_edges[b] =
191                Some(SpantreeEdge { is_reversed: !is_reversed, claiming_node, span_parent: a });
192        }
193
194        // The result of the rotation is that `this` is now a spantree root.
195        debug_assert!(self.span_edges[this].is_none());
196    }
197
198    /// Must be called exactly once for each node in the balanced-flow graph.
199    fn visit_node(&mut self, this: Node) {
200        // Assert that this node was unvisited, and mark it visited.
201        assert!(self.is_unvisited.remove(this), "node has already been visited: {this:?}");
202
203        // Get the supernode containing `this`, and make it the root of its
204        // component of the spantree.
205        let this_supernode = self.supernodes[this];
206        self.yank_to_spantree_root(this_supernode);
207
208        // Get the supernode containing all of this's successors.
209        let succ_supernode = self.succ_supernodes[this];
210        debug_assert!(self.is_supernode(succ_supernode));
211
212        // If two supernodes are already connected in the spantree, they will
213        // have the same spantree root. (Each supernode is connected to itself.)
214        if this_supernode != self.spantree_root(succ_supernode) {
215            // Adding this node's flow edge to the spantree would cause two
216            // previously-disconnected supernodes to become connected, so add
217            // it. That spantree-edge is now "claimed" by this node.
218            //
219            // Claiming a spantree-edge means that this node will get a counter
220            // expression instead of a physical counter. That expression is
221            // currently empty, but will be built incrementally as the other
222            // nodes are visited.
223            self.span_edges[this_supernode] = Some(SpantreeEdge {
224                is_reversed: false,
225                claiming_node: this,
226                span_parent: succ_supernode,
227            });
228        } else {
229            // This node's flow edge would join two supernodes that are already
230            // connected in the spantree (or are the same supernode). That would
231            // create a cycle in the spantree, so don't add an edge.
232            //
233            // Instead, create a physical counter for this node, and add that
234            // counter to all expressions on the path from `succ_supernode` to
235            // `this_supernode`.
236
237            // Instead of setting `this.measure = true` as in the original paper,
238            // we just add the node's ID to its own list of terms.
239            self.counter_terms[this].push(CounterTerm { node: this, op: Op::Add });
240
241            // Walk the spantree from `this.successor` back to `this`. For each
242            // spantree edge along the way, add this node's physical counter to
243            // the counter expression of the node that claimed the spantree edge.
244            let mut curr = succ_supernode;
245            while curr != this_supernode {
246                let &SpantreeEdge { is_reversed, claiming_node, span_parent } =
247                    self.span_edges[curr].as_ref().unwrap();
248                let op = if is_reversed { Op::Subtract } else { Op::Add };
249                self.counter_terms[claiming_node].push(CounterTerm { node: this, op });
250
251                curr = span_parent;
252            }
253        }
254    }
255
256    /// Asserts that all nodes have been visited, and returns the computed
257    /// counter expressions (made up of physical counters) for each node.
258    fn finish(self) -> IndexVec<Node, Vec<CounterTerm<Node>>> {
259        let Self { ref span_edges, ref is_unvisited, ref counter_terms, .. } = self;
260        assert!(is_unvisited.is_empty(), "some nodes were never visited: {is_unvisited:?}");
261        debug_assert!(
262            span_edges
263                .iter_enumerated()
264                .all(|(node, span_edge)| { span_edge.is_some() <= self.is_supernode(node) }),
265            "only supernodes can have a span edge",
266        );
267        debug_assert!(
268            counter_terms.iter().all(|terms| !terms.is_empty()),
269            "after visiting all nodes, every node should have at least one term",
270        );
271
272        self.counter_terms
273    }
274}