Skip to content

Commit f1300c8

Browse files
committed
coverage: Completely overhaul counter assignment, using node-flow graphs
1 parent e70112c commit f1300c8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1924
-1967
lines changed

compiler/rustc_data_structures/src/graph/iterate/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ where
125125
pub fn visited(&self, node: G::Node) -> bool {
126126
self.visited.contains(node)
127127
}
128+
129+
/// Returns a reference to the set of nodes that have been visited, with
130+
/// the same caveats as [`Self::visited`].
131+
///
132+
/// When incorporating the visited nodes into another bitset, using bulk
133+
/// operations like `union` or `intersect` can be more efficient than
134+
/// processing each node individually.
135+
pub fn visited_set(&self) -> &DenseBitSet<G::Node> {
136+
&self.visited
137+
}
128138
}
129139

130140
impl<G> std::fmt::Debug for DepthFirstSearch<G>

compiler/rustc_mir_transform/src/coverage/counters.rs

+70-376
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//! A control-flow graph can be said to have “balanced flow” if the flow
2+
//! (execution count) of each node is equal to the sum of its in-edge flows,
3+
//! and also equal to the sum of its out-edge flows.
4+
//!
5+
//! Control-flow graphs typically have one or more nodes that don't satisfy the
6+
//! balanced-flow property, e.g.:
7+
//! - The start node has out-edges, but no in-edges.
8+
//! - Return nodes have in-edges, but no out-edges.
9+
//! - `Yield` nodes can have an out-flow that is less than their in-flow.
10+
//! - Inescapable loops cause the in-flow/out-flow relationship to break down.
11+
//!
12+
//! Balanced-flow graphs are nevertheless useful for analysis, so this module
13+
//! provides a wrapper type ([`BalancedFlowGraph`]) that imposes balanced flow
14+
//! on an underlying graph. This is done by non-destructively adding synthetic
15+
//! nodes and edges as necessary.
16+
17+
use rustc_data_structures::graph;
18+
use rustc_data_structures::graph::iterate::DepthFirstSearch;
19+
use rustc_data_structures::graph::reversed::ReversedGraph;
20+
use rustc_index::Idx;
21+
use rustc_index::bit_set::DenseBitSet;
22+
23+
use crate::coverage::counters::iter_nodes::IterNodes;
24+
25+
/// A view of an underlying graph that has been augmented to have “balanced flow”.
26+
/// This means that the flow (execution count) of each node is equal to the
27+
/// sum of its in-edge flows, and also equal to the sum of its out-edge flows.
28+
///
29+
/// To achieve this, a synthetic "sink" node is non-destructively added to the
30+
/// graph, with synthetic in-edges from these nodes:
31+
/// - Any node that has no out-edges.
32+
/// - Any node that explicitly requires a sink edge, as indicated by a
33+
/// caller-supplied `force_sink_edge` function.
34+
/// - Any node that would otherwise be unable to reach the sink, because it is
35+
/// part of an inescapable loop.
36+
///
37+
/// To make the graph fully balanced, there is also a synthetic edge from the
38+
/// sink node back to the start node.
39+
///
40+
/// ---
41+
/// The benefit of having a balanced-flow graph is that it can be subsequently
42+
/// transformed in ways that are guaranteed to preserve balanced flow
43+
/// (e.g. merging nodes together), which is useful for discovering relationships
44+
/// between the node flows of different nodes in the graph.
45+
pub(crate) struct BalancedFlowGraph<G: graph::DirectedGraph> {
46+
graph: G,
47+
sink_edge_nodes: DenseBitSet<G::Node>,
48+
pub(crate) sink: G::Node,
49+
}
50+
51+
impl<G: graph::DirectedGraph> BalancedFlowGraph<G> {
52+
/// Creates a balanced view of an underlying graph, by adding a synthetic
53+
/// sink node that has in-edges from nodes that need or request such an edge,
54+
/// and a single out-edge to the start node.
55+
///
56+
/// Assumes that all nodes in the underlying graph are reachable from the
57+
/// start node.
58+
pub(crate) fn for_graph(graph: G, force_sink_edge: impl Fn(G::Node) -> bool) -> Self
59+
where
60+
G: graph::ControlFlowGraph,
61+
{
62+
let mut sink_edge_nodes = DenseBitSet::new_empty(graph.num_nodes());
63+
let mut dfs = DepthFirstSearch::new(ReversedGraph::new(&graph));
64+
65+
// First, determine the set of nodes that explicitly request or require
66+
// an out-edge to the sink.
67+
for node in graph.iter_nodes() {
68+
if force_sink_edge(node) || graph.successors(node).next().is_none() {
69+
sink_edge_nodes.insert(node);
70+
dfs.push_start_node(node);
71+
}
72+
}
73+
74+
// Next, find all nodes that are currently not reverse-reachable from
75+
// `sink_edge_nodes`, and add them to the set as well.
76+
dfs.complete_search();
77+
sink_edge_nodes.union_not(dfs.visited_set());
78+
79+
// The sink node is 1 higher than the highest real node.
80+
let sink = G::Node::new(graph.num_nodes());
81+
82+
BalancedFlowGraph { graph, sink_edge_nodes, sink }
83+
}
84+
}
85+
86+
impl<G> graph::DirectedGraph for BalancedFlowGraph<G>
87+
where
88+
G: graph::DirectedGraph,
89+
{
90+
type Node = G::Node;
91+
92+
/// Returns the number of nodes in this balanced-flow graph, which is 1
93+
/// more than the number of nodes in the underlying graph, to account for
94+
/// the synthetic sink node.
95+
fn num_nodes(&self) -> usize {
96+
// The sink node's index is already the size of the underlying graph,
97+
// so just add 1 to that instead.
98+
self.sink.index() + 1
99+
}
100+
}
101+
102+
impl<G> graph::StartNode for BalancedFlowGraph<G>
103+
where
104+
G: graph::StartNode,
105+
{
106+
fn start_node(&self) -> Self::Node {
107+
self.graph.start_node()
108+
}
109+
}
110+
111+
impl<G> graph::Successors for BalancedFlowGraph<G>
112+
where
113+
G: graph::StartNode + graph::Successors,
114+
{
115+
fn successors(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> {
116+
let real_edges;
117+
let sink_edge;
118+
119+
if node == self.sink {
120+
// The sink node has no real out-edges, and one synthetic out-edge
121+
// to the start node.
122+
real_edges = None;
123+
sink_edge = Some(self.graph.start_node());
124+
} else {
125+
// Real nodes have their real out-edges, and possibly one synthetic
126+
// out-edge to the sink node.
127+
real_edges = Some(self.graph.successors(node));
128+
sink_edge = self.sink_edge_nodes.contains(node).then_some(self.sink);
129+
}
130+
131+
real_edges.into_iter().flatten().chain(sink_edge)
132+
}
133+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use rustc_data_structures::graph;
2+
use rustc_index::Idx;
3+
4+
pub(crate) trait IterNodes: graph::DirectedGraph {
5+
/// Iterates over all nodes of a graph in ascending numeric order.
6+
/// Assumes that nodes are densely numbered, i.e. every index in
7+
/// `0..num_nodes` is a valid node.
8+
///
9+
/// FIXME: Can this just be part of [`graph::DirectedGraph`]?
10+
fn iter_nodes(
11+
&self,
12+
) -> impl Iterator<Item = Self::Node> + DoubleEndedIterator + ExactSizeIterator {
13+
(0..self.num_nodes()).map(<Self::Node as Idx>::new)
14+
}
15+
}
16+
impl<G: graph::DirectedGraph> IterNodes for G {}

0 commit comments

Comments
 (0)