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