blob: 91ed54b8b59f5fea4c4a5948484bacae35386123 [file] [log] [blame]
//! 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
}
}