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}