Skip to content

Commit 2bdc67a

Browse files
committed
coverage: Treat the "merged node flow graph" as a plain data struct
By removing all methods from this struct and treating it as a collection of data fields, we make it easier for a future PR to store that data in a query result, without having to move all of its methods into `rustc_middle`.
1 parent 4b20a27 commit 2bdc67a

File tree

3 files changed

+89
-83
lines changed

3 files changed

+89
-83
lines changed

compiler/rustc_mir_transform/src/coverage/counters.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use rustc_middle::mir::coverage::{CounterId, CovTerm, Expression, ExpressionId,
1111

1212
use crate::coverage::counters::balanced_flow::BalancedFlowGraph;
1313
use crate::coverage::counters::iter_nodes::IterNodes;
14-
use crate::coverage::counters::node_flow::{CounterTerm, MergedNodeFlowGraph, NodeCounters};
14+
use crate::coverage::counters::node_flow::{
15+
CounterTerm, NodeCounters, make_node_counters, node_flow_data_for_balanced_graph,
16+
};
1517
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
1618

1719
mod balanced_flow;
@@ -27,20 +29,20 @@ pub(super) fn make_bcb_counters(
2729
) -> CoverageCounters {
2830
// Create the derived graphs that are necessary for subsequent steps.
2931
let balanced_graph = BalancedFlowGraph::for_graph(graph, |n| !graph[n].is_out_summable);
30-
let merged_graph = MergedNodeFlowGraph::for_balanced_graph(&balanced_graph);
32+
let node_flow_data = node_flow_data_for_balanced_graph(&balanced_graph);
3133

3234
// Use those graphs to determine which nodes get physical counters, and how
3335
// to compute the execution counts of other nodes from those counters.
34-
let nodes = make_node_counter_priority_list(graph, balanced_graph);
35-
let node_counters = merged_graph.make_node_counters(&nodes);
36+
let priority_list = make_node_flow_priority_list(graph, balanced_graph);
37+
let node_counters = make_node_counters(&node_flow_data, &priority_list);
3638

3739
// Convert the counters into a form suitable for embedding into MIR.
3840
transcribe_counters(&node_counters, bcb_needs_counter)
3941
}
4042

4143
/// Arranges the nodes in `balanced_graph` into a list, such that earlier nodes
4244
/// take priority in being given a counter expression instead of a physical counter.
43-
fn make_node_counter_priority_list(
45+
fn make_node_flow_priority_list(
4446
graph: &CoverageGraph,
4547
balanced_graph: BalancedFlowGraph<&CoverageGraph>,
4648
) -> Vec<BasicCoverageBlock> {

compiler/rustc_mir_transform/src/coverage/counters/node_flow.rs

+75-73
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
99
use rustc_data_structures::graph;
1010
use rustc_index::bit_set::DenseBitSet;
11-
use rustc_index::{Idx, IndexVec};
11+
use rustc_index::{Idx, IndexSlice, IndexVec};
1212
use rustc_middle::mir::coverage::Op;
1313

1414
use crate::coverage::counters::iter_nodes::IterNodes;
@@ -17,8 +17,8 @@ use crate::coverage::counters::union_find::UnionFind;
1717
#[cfg(test)]
1818
mod tests;
1919

20-
/// View of some underlying graph, in which each node's successors have been
21-
/// merged into a single "supernode".
20+
/// Data representing a view of some underlying graph, in which each node's
21+
/// successors have been merged into a single "supernode".
2222
///
2323
/// The resulting supernodes have no obvious meaning on their own.
2424
/// However, merging successor nodes means that a node's out-edges can all
@@ -29,7 +29,7 @@ mod tests;
2929
/// in the merged graph, it becomes possible to analyze the original node flows
3030
/// using techniques for analyzing edge flows.
3131
#[derive(Debug)]
32-
pub(crate) struct MergedNodeFlowGraph<Node: Idx> {
32+
pub(crate) struct NodeFlowData<Node: Idx> {
3333
/// Maps each node to the supernode that contains it, indicated by some
3434
/// arbitrary "root" node that is part of that supernode.
3535
supernodes: IndexVec<Node, Node>,
@@ -41,66 +41,59 @@ pub(crate) struct MergedNodeFlowGraph<Node: Idx> {
4141
succ_supernodes: IndexVec<Node, Node>,
4242
}
4343

44-
impl<Node: Idx> MergedNodeFlowGraph<Node> {
45-
/// Creates a "merged" view of an underlying graph.
46-
///
47-
/// The given graph is assumed to have [“balanced flow”](balanced-flow),
48-
/// though it does not necessarily have to be a `BalancedFlowGraph`.
49-
///
50-
/// [balanced-flow]: `crate::coverage::counters::balanced_flow::BalancedFlowGraph`.
51-
pub(crate) fn for_balanced_graph<G>(graph: G) -> Self
52-
where
53-
G: graph::DirectedGraph<Node = Node> + graph::Successors,
54-
{
55-
let mut supernodes = UnionFind::<G::Node>::new(graph.num_nodes());
56-
57-
// For each node, merge its successors into a single supernode, and
58-
// arbitrarily choose one of those successors to represent all of them.
59-
let successors = graph
60-
.iter_nodes()
61-
.map(|node| {
62-
graph
63-
.successors(node)
64-
.reduce(|a, b| supernodes.unify(a, b))
65-
.expect("each node in a balanced graph must have at least one out-edge")
66-
})
67-
.collect::<IndexVec<G::Node, G::Node>>();
68-
69-
// Now that unification is complete, take a snapshot of the supernode forest,
70-
// and resolve each arbitrarily-chosen successor to its canonical root.
71-
// (This avoids having to explicitly resolve them later.)
72-
let supernodes = supernodes.snapshot();
73-
let succ_supernodes = successors.into_iter().map(|succ| supernodes[succ]).collect();
74-
75-
Self { supernodes, succ_supernodes }
76-
}
77-
78-
fn num_nodes(&self) -> usize {
79-
self.succ_supernodes.len()
80-
}
44+
/// Creates a "merged" view of an underlying graph.
45+
///
46+
/// The given graph is assumed to have [“balanced flow”](balanced-flow),
47+
/// though it does not necessarily have to be a `BalancedFlowGraph`.
48+
///
49+
/// [balanced-flow]: `crate::coverage::counters::balanced_flow::BalancedFlowGraph`.
50+
pub(crate) fn node_flow_data_for_balanced_graph<G>(graph: G) -> NodeFlowData<G::Node>
51+
where
52+
G: graph::Successors,
53+
{
54+
let mut supernodes = UnionFind::<G::Node>::new(graph.num_nodes());
55+
56+
// For each node, merge its successors into a single supernode, and
57+
// arbitrarily choose one of those successors to represent all of them.
58+
let successors = graph
59+
.iter_nodes()
60+
.map(|node| {
61+
graph
62+
.successors(node)
63+
.reduce(|a, b| supernodes.unify(a, b))
64+
.expect("each node in a balanced graph must have at least one out-edge")
65+
})
66+
.collect::<IndexVec<G::Node, G::Node>>();
67+
68+
// Now that unification is complete, take a snapshot of the supernode forest,
69+
// and resolve each arbitrarily-chosen successor to its canonical root.
70+
// (This avoids having to explicitly resolve them later.)
71+
let supernodes = supernodes.snapshot();
72+
let succ_supernodes = successors.into_iter().map(|succ| supernodes[succ]).collect();
73+
74+
NodeFlowData { supernodes, succ_supernodes }
75+
}
8176

82-
fn is_supernode(&self, node: Node) -> bool {
83-
self.supernodes[node] == node
77+
/// Uses the graph information in `node_flow_data`, together with a given
78+
/// permutation of all nodes in the graph, to create physical counters and
79+
/// counter expressions for each node in the underlying graph.
80+
///
81+
/// The given list must contain exactly one copy of each node in the
82+
/// underlying balanced-flow graph. The order of nodes is used as a hint to
83+
/// influence counter allocation:
84+
/// - Earlier nodes are more likely to receive counter expressions.
85+
/// - Later nodes are more likely to receive physical counters.
86+
pub(crate) fn make_node_counters<Node: Idx>(
87+
node_flow_data: &NodeFlowData<Node>,
88+
priority_list: &[Node],
89+
) -> NodeCounters<Node> {
90+
let mut builder = SpantreeBuilder::new(node_flow_data);
91+
92+
for &node in priority_list {
93+
builder.visit_node(node);
8494
}
8595

86-
/// Using the information in this merged graph, together with a given
87-
/// permutation of all nodes in the graph, to create physical counters and
88-
/// counter expressions for each node in the underlying graph.
89-
///
90-
/// The given list must contain exactly one copy of each node in the
91-
/// underlying balanced-flow graph. The order of nodes is used as a hint to
92-
/// influence counter allocation:
93-
/// - Earlier nodes are more likely to receive counter expressions.
94-
/// - Later nodes are more likely to receive physical counters.
95-
pub(crate) fn make_node_counters(&self, all_nodes_permutation: &[Node]) -> NodeCounters<Node> {
96-
let mut builder = SpantreeBuilder::new(self);
97-
98-
for &node in all_nodes_permutation {
99-
builder.visit_node(node);
100-
}
101-
102-
NodeCounters { counter_terms: builder.finish() }
103-
}
96+
NodeCounters { counter_terms: builder.finish() }
10497
}
10598

10699
/// End result of allocating physical counters and counter expressions for the
@@ -141,7 +134,9 @@ pub(crate) struct CounterTerm<Node> {
141134

142135
#[derive(Debug)]
143136
struct SpantreeBuilder<'a, Node: Idx> {
144-
graph: &'a MergedNodeFlowGraph<Node>,
137+
supernodes: &'a IndexSlice<Node, Node>,
138+
succ_supernodes: &'a IndexSlice<Node, Node>,
139+
145140
is_unvisited: DenseBitSet<Node>,
146141
/// Links supernodes to each other, gradually forming a spanning tree of
147142
/// the merged-flow graph.
@@ -157,22 +152,28 @@ struct SpantreeBuilder<'a, Node: Idx> {
157152
}
158153

159154
impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
160-
fn new(graph: &'a MergedNodeFlowGraph<Node>) -> Self {
161-
let num_nodes = graph.num_nodes();
155+
fn new(node_flow_data: &'a NodeFlowData<Node>) -> Self {
156+
let NodeFlowData { supernodes, succ_supernodes } = node_flow_data;
157+
let num_nodes = supernodes.len();
162158
Self {
163-
graph,
159+
supernodes,
160+
succ_supernodes,
164161
is_unvisited: DenseBitSet::new_filled(num_nodes),
165162
span_edges: IndexVec::from_fn_n(|_| None, num_nodes),
166163
yank_buffer: vec![],
167164
counter_terms: IndexVec::from_fn_n(|_| vec![], num_nodes),
168165
}
169166
}
170167

168+
fn is_supernode(&self, node: Node) -> bool {
169+
self.supernodes[node] == node
170+
}
171+
171172
/// Given a supernode, finds the supernode that is the "root" of its
172173
/// spantree component. Two nodes that have the same spantree root are
173174
/// connected in the spantree.
174175
fn spantree_root(&self, this: Node) -> Node {
175-
debug_assert!(self.graph.is_supernode(this));
176+
debug_assert!(self.is_supernode(this));
176177

177178
match self.span_edges[this] {
178179
None => this,
@@ -183,7 +184,7 @@ impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
183184
/// Rotates edges in the spantree so that `this` is the root of its
184185
/// spantree component.
185186
fn yank_to_spantree_root(&mut self, this: Node) {
186-
debug_assert!(self.graph.is_supernode(this));
187+
debug_assert!(self.is_supernode(this));
187188

188189
// The rotation is done iteratively, by first traversing from `this` to
189190
// its root and storing the path in a buffer, and then traversing the
@@ -225,12 +226,12 @@ impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
225226

226227
// Get the supernode containing `this`, and make it the root of its
227228
// component of the spantree.
228-
let this_supernode = self.graph.supernodes[this];
229+
let this_supernode = self.supernodes[this];
229230
self.yank_to_spantree_root(this_supernode);
230231

231232
// Get the supernode containing all of this's successors.
232-
let succ_supernode = self.graph.succ_supernodes[this];
233-
debug_assert!(self.graph.is_supernode(succ_supernode));
233+
let succ_supernode = self.succ_supernodes[this];
234+
debug_assert!(self.is_supernode(succ_supernode));
234235

235236
// If two supernodes are already connected in the spantree, they will
236237
// have the same spantree root. (Each supernode is connected to itself.)
@@ -279,18 +280,19 @@ impl<'a, Node: Idx> SpantreeBuilder<'a, Node> {
279280
/// Asserts that all nodes have been visited, and returns the computed
280281
/// counter expressions (made up of physical counters) for each node.
281282
fn finish(self) -> IndexVec<Node, Vec<CounterTerm<Node>>> {
282-
let Self { graph, is_unvisited, span_edges, yank_buffer: _, counter_terms } = self;
283+
let Self { ref span_edges, ref is_unvisited, ref counter_terms, .. } = self;
283284
assert!(is_unvisited.is_empty(), "some nodes were never visited: {is_unvisited:?}");
284285
debug_assert!(
285286
span_edges
286287
.iter_enumerated()
287-
.all(|(node, span_edge)| { span_edge.is_some() <= graph.is_supernode(node) }),
288+
.all(|(node, span_edge)| { span_edge.is_some() <= self.is_supernode(node) }),
288289
"only supernodes can have a span edge",
289290
);
290291
debug_assert!(
291292
counter_terms.iter().all(|terms| !terms.is_empty()),
292293
"after visiting all nodes, every node should have at least one term",
293294
);
294-
counter_terms
295+
296+
self.counter_terms
295297
}
296298
}

compiler/rustc_mir_transform/src/coverage/counters/node_flow/tests.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ use rustc_data_structures::graph::vec_graph::VecGraph;
44
use rustc_index::Idx;
55
use rustc_middle::mir::coverage::Op;
66

7-
use super::{CounterTerm, MergedNodeFlowGraph, NodeCounters};
7+
use crate::coverage::counters::node_flow::{
8+
CounterTerm, NodeCounters, NodeFlowData, make_node_counters, node_flow_data_for_balanced_graph,
9+
};
810

9-
fn merged_node_flow_graph<G: graph::Successors>(graph: G) -> MergedNodeFlowGraph<G::Node> {
10-
MergedNodeFlowGraph::for_balanced_graph(graph)
11+
fn node_flow_data<G: graph::Successors>(graph: G) -> NodeFlowData<G::Node> {
12+
node_flow_data_for_balanced_graph(graph)
1113
}
1214

1315
fn make_graph<Node: Idx + Ord>(num_nodes: usize, edge_pairs: Vec<(Node, Node)>) -> VecGraph<Node> {
@@ -30,8 +32,8 @@ fn example_driver() {
3032
(4, 0),
3133
]);
3234

33-
let merged = merged_node_flow_graph(&graph);
34-
let counters = merged.make_node_counters(&[3, 1, 2, 0, 4]);
35+
let node_flow_data = node_flow_data(&graph);
36+
let counters = make_node_counters(&node_flow_data, &[3, 1, 2, 0, 4]);
3537

3638
assert_eq!(format_counter_expressions(&counters), &[
3739
// (comment to force vertical formatting for clarity)

0 commit comments

Comments
 (0)