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