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}