2024-11-29 12:58:35 +11:00
|
|
|
use std::cmp::Ordering;
|
2024-03-20 18:25:06 +11:00
|
|
|
|
2025-01-12 21:36:07 +11:00
|
|
|
use either::Either;
|
|
|
|
|
use itertools::Itertools;
|
2024-01-25 13:35:40 +11:00
|
|
|
use rustc_data_structures::captures::Captures;
|
2025-01-17 15:25:24 +11:00
|
|
|
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
2024-04-14 15:15:03 +00:00
|
|
|
use rustc_data_structures::graph::DirectedGraph;
|
2023-06-29 16:50:52 +10:00
|
|
|
use rustc_index::IndexVec;
|
2025-01-07 15:19:05 +00:00
|
|
|
use rustc_index::bit_set::DenseBitSet;
|
2024-03-20 18:25:06 +11:00
|
|
|
use rustc_middle::mir::coverage::{CounterId, CovTerm, Expression, ExpressionId, Op};
|
2020-10-23 00:45:07 -07:00
|
|
|
|
2025-01-12 21:36:07 +11:00
|
|
|
use crate::coverage::counters::balanced_flow::BalancedFlowGraph;
|
2025-01-21 11:25:10 +11:00
|
|
|
use crate::coverage::counters::node_flow::{
|
|
|
|
|
CounterTerm, NodeCounters, make_node_counters, node_flow_data_for_balanced_graph,
|
|
|
|
|
};
|
2025-01-12 21:36:07 +11:00
|
|
|
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
|
2023-07-08 13:43:29 +10:00
|
|
|
|
2025-01-12 21:36:07 +11:00
|
|
|
mod balanced_flow;
|
|
|
|
|
mod node_flow;
|
|
|
|
|
mod union_find;
|
2024-11-29 12:58:35 +11:00
|
|
|
|
2025-01-17 22:41:50 +11:00
|
|
|
/// Ensures that each BCB node needing a counter has one, by creating physical
|
|
|
|
|
/// counters or counter expressions for nodes as required.
|
|
|
|
|
pub(super) fn make_bcb_counters(
|
|
|
|
|
graph: &CoverageGraph,
|
|
|
|
|
bcb_needs_counter: &DenseBitSet<BasicCoverageBlock>,
|
|
|
|
|
) -> CoverageCounters {
|
2025-01-18 22:12:30 +11:00
|
|
|
// Create the derived graphs that are necessary for subsequent steps.
|
2025-01-17 22:41:50 +11:00
|
|
|
let balanced_graph = BalancedFlowGraph::for_graph(graph, |n| !graph[n].is_out_summable);
|
2025-01-21 11:25:10 +11:00
|
|
|
let node_flow_data = node_flow_data_for_balanced_graph(&balanced_graph);
|
2025-01-17 22:41:50 +11:00
|
|
|
|
2025-01-18 22:12:30 +11:00
|
|
|
// Use those graphs to determine which nodes get physical counters, and how
|
|
|
|
|
// to compute the execution counts of other nodes from those counters.
|
2025-01-21 11:25:10 +11:00
|
|
|
let priority_list = make_node_flow_priority_list(graph, balanced_graph);
|
|
|
|
|
let node_counters = make_node_counters(&node_flow_data, &priority_list);
|
2025-01-17 22:41:50 +11:00
|
|
|
|
2025-01-18 22:12:30 +11:00
|
|
|
// Convert the counters into a form suitable for embedding into MIR.
|
2025-01-17 22:41:50 +11:00
|
|
|
transcribe_counters(&node_counters, bcb_needs_counter)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-18 22:12:30 +11:00
|
|
|
/// Arranges the nodes in `balanced_graph` into a list, such that earlier nodes
|
|
|
|
|
/// take priority in being given a counter expression instead of a physical counter.
|
2025-01-21 11:25:10 +11:00
|
|
|
fn make_node_flow_priority_list(
|
2025-01-17 22:41:50 +11:00
|
|
|
graph: &CoverageGraph,
|
|
|
|
|
balanced_graph: BalancedFlowGraph<&CoverageGraph>,
|
|
|
|
|
) -> Vec<BasicCoverageBlock> {
|
|
|
|
|
// A "reloop" node has exactly one out-edge, which jumps back to the top
|
|
|
|
|
// of an enclosing loop. Reloop nodes are typically visited more times
|
|
|
|
|
// than loop-exit nodes, so try to avoid giving them physical counters.
|
|
|
|
|
let is_reloop_node = IndexVec::from_fn_n(
|
|
|
|
|
|node| match graph.successors[node].as_slice() {
|
|
|
|
|
&[succ] => graph.dominates(succ, node),
|
|
|
|
|
_ => false,
|
|
|
|
|
},
|
|
|
|
|
graph.num_nodes(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mut nodes = balanced_graph.iter_nodes().rev().collect::<Vec<_>>();
|
|
|
|
|
// The first node is the sink, which must not get a physical counter.
|
|
|
|
|
assert_eq!(nodes[0], balanced_graph.sink);
|
|
|
|
|
// Sort the real nodes, such that earlier (lesser) nodes take priority
|
|
|
|
|
// in being given a counter expression instead of a physical counter.
|
|
|
|
|
nodes[1..].sort_by(|&a, &b| {
|
|
|
|
|
// Start with a dummy `Equal` to make the actual tests line up nicely.
|
|
|
|
|
Ordering::Equal
|
|
|
|
|
// Prefer a physical counter for return/yield nodes.
|
|
|
|
|
.then_with(|| Ord::cmp(&graph[a].is_out_summable, &graph[b].is_out_summable))
|
|
|
|
|
// Prefer an expression for reloop nodes (see definition above).
|
|
|
|
|
.then_with(|| Ord::cmp(&is_reloop_node[a], &is_reloop_node[b]).reverse())
|
|
|
|
|
// Otherwise, prefer a physical counter for dominating nodes.
|
|
|
|
|
.then_with(|| graph.cmp_in_dominator_order(a, b).reverse())
|
|
|
|
|
});
|
|
|
|
|
nodes
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-18 22:12:30 +11:00
|
|
|
// Converts node counters into a form suitable for embedding into MIR.
|
2025-01-17 22:41:50 +11:00
|
|
|
fn transcribe_counters(
|
|
|
|
|
old: &NodeCounters<BasicCoverageBlock>,
|
|
|
|
|
bcb_needs_counter: &DenseBitSet<BasicCoverageBlock>,
|
|
|
|
|
) -> CoverageCounters {
|
|
|
|
|
let mut new = CoverageCounters::with_num_bcbs(bcb_needs_counter.domain_size());
|
|
|
|
|
|
|
|
|
|
for bcb in bcb_needs_counter.iter() {
|
2025-01-21 20:34:52 +11:00
|
|
|
// Our counter-creation algorithm doesn't guarantee that a node's list
|
|
|
|
|
// of terms starts or ends with a positive term, so partition the
|
2025-01-18 22:12:30 +11:00
|
|
|
// counters into "positive" and "negative" lists for easier handling.
|
2025-01-17 22:41:50 +11:00
|
|
|
let (mut pos, mut neg): (Vec<_>, Vec<_>) =
|
2025-01-21 20:34:52 +11:00
|
|
|
old.counter_terms[bcb].iter().partition_map(|&CounterTerm { node, op }| match op {
|
2025-01-17 22:41:50 +11:00
|
|
|
Op::Add => Either::Left(node),
|
|
|
|
|
Op::Subtract => Either::Right(node),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if pos.is_empty() {
|
|
|
|
|
// If we somehow end up with no positive terms, fall back to
|
|
|
|
|
// creating a physical counter. There's no known way for this
|
|
|
|
|
// to happen, but we can avoid an ICE if it does.
|
|
|
|
|
debug_assert!(false, "{bcb:?} has no positive counter terms");
|
|
|
|
|
pos = vec![bcb];
|
|
|
|
|
neg = vec![];
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-18 22:12:30 +11:00
|
|
|
// These intermediate sorts are not strictly necessary, but were helpful
|
|
|
|
|
// in reducing churn when switching to the current counter-creation scheme.
|
|
|
|
|
// They also help to slightly decrease the overall size of the expression
|
|
|
|
|
// table, due to more subexpressions being shared.
|
2025-01-17 22:41:50 +11:00
|
|
|
pos.sort();
|
|
|
|
|
neg.sort();
|
|
|
|
|
|
|
|
|
|
let mut new_counters_for_sites = |sites: Vec<BasicCoverageBlock>| {
|
|
|
|
|
sites.into_iter().map(|node| new.ensure_phys_counter(node)).collect::<Vec<_>>()
|
|
|
|
|
};
|
|
|
|
|
let mut pos = new_counters_for_sites(pos);
|
|
|
|
|
let mut neg = new_counters_for_sites(neg);
|
|
|
|
|
|
2025-01-18 22:12:30 +11:00
|
|
|
// These sorts are also not strictly necessary; see above.
|
2025-01-17 22:41:50 +11:00
|
|
|
pos.sort();
|
|
|
|
|
neg.sort();
|
|
|
|
|
|
|
|
|
|
let pos_counter = new.make_sum(&pos).expect("`pos` should not be empty");
|
|
|
|
|
let new_counter = new.make_subtracted_sum(pos_counter, &neg);
|
|
|
|
|
new.set_node_counter(bcb, new_counter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-29 16:50:52 +10:00
|
|
|
/// Generates and stores coverage counter and coverage expression information
|
2025-01-17 14:43:12 +11:00
|
|
|
/// associated with nodes in the coverage graph.
|
2020-11-02 21:32:48 -08:00
|
|
|
pub(super) struct CoverageCounters {
|
2024-01-25 13:35:40 +11:00
|
|
|
/// List of places where a counter-increment statement should be injected
|
|
|
|
|
/// into MIR, each with its corresponding counter ID.
|
2025-01-17 15:25:24 +11:00
|
|
|
phys_counter_for_node: FxIndexMap<BasicCoverageBlock, CounterId>,
|
|
|
|
|
next_counter_id: CounterId,
|
2023-06-29 14:25:28 +10:00
|
|
|
|
2023-06-29 16:50:52 +10:00
|
|
|
/// Coverage counters/expressions that are associated with individual BCBs.
|
2025-01-17 21:34:57 +11:00
|
|
|
node_counters: IndexVec<BasicCoverageBlock, Option<CovTerm>>,
|
2024-04-21 18:11:57 +10:00
|
|
|
|
2023-09-13 13:20:13 +10:00
|
|
|
/// Table of expression data, associating each expression ID with its
|
|
|
|
|
/// corresponding operator (+ or -) and its LHS/RHS operands.
|
2025-01-17 21:34:57 +11:00
|
|
|
expressions: IndexVec<ExpressionId, Expression>,
|
2024-04-21 18:11:57 +10:00
|
|
|
/// Remember expressions that have already been created (or simplified),
|
|
|
|
|
/// so that we don't create unnecessary duplicates.
|
2025-01-17 21:34:57 +11:00
|
|
|
expressions_memo: FxHashMap<Expression, CovTerm>,
|
2020-10-23 00:45:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CoverageCounters {
|
2024-10-05 17:08:07 +10:00
|
|
|
fn with_num_bcbs(num_bcbs: usize) -> Self {
|
|
|
|
|
Self {
|
2025-01-17 15:25:24 +11:00
|
|
|
phys_counter_for_node: FxIndexMap::default(),
|
|
|
|
|
next_counter_id: CounterId::ZERO,
|
2024-10-19 18:02:03 +11:00
|
|
|
node_counters: IndexVec::from_elem_n(None, num_bcbs),
|
2023-09-13 13:20:13 +10:00
|
|
|
expressions: IndexVec::new(),
|
2024-04-21 18:11:57 +10:00
|
|
|
expressions_memo: FxHashMap::default(),
|
2024-10-05 17:08:07 +10:00
|
|
|
}
|
2020-10-22 14:30:03 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-17 15:25:24 +11:00
|
|
|
/// Returns the physical counter for the given node, creating it if necessary.
|
2025-01-17 21:34:57 +11:00
|
|
|
fn ensure_phys_counter(&mut self, bcb: BasicCoverageBlock) -> CovTerm {
|
2025-01-17 15:25:24 +11:00
|
|
|
let id = *self.phys_counter_for_node.entry(bcb).or_insert_with(|| {
|
|
|
|
|
let id = self.next_counter_id;
|
|
|
|
|
self.next_counter_id = id + 1;
|
|
|
|
|
id
|
|
|
|
|
});
|
2025-01-17 21:34:57 +11:00
|
|
|
CovTerm::Counter(id)
|
2020-10-23 00:45:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-17 21:34:57 +11:00
|
|
|
fn make_expression(&mut self, lhs: CovTerm, op: Op, rhs: CovTerm) -> CovTerm {
|
|
|
|
|
let new_expr = Expression { lhs, op, rhs };
|
|
|
|
|
*self.expressions_memo.entry(new_expr.clone()).or_insert_with(|| {
|
2024-12-01 15:50:57 +11:00
|
|
|
let id = self.expressions.push(new_expr);
|
2025-01-17 21:34:57 +11:00
|
|
|
CovTerm::Expression(id)
|
2024-12-01 15:50:57 +11:00
|
|
|
})
|
2020-10-23 00:45:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-11 17:24:31 +10:00
|
|
|
/// Creates a counter that is the sum of the given counters.
|
2023-10-31 17:44:26 +11:00
|
|
|
///
|
2024-09-11 17:24:31 +10:00
|
|
|
/// Returns `None` if the given list of counters was empty.
|
2025-01-17 21:34:57 +11:00
|
|
|
fn make_sum(&mut self, counters: &[CovTerm]) -> Option<CovTerm> {
|
2024-09-11 17:24:31 +10:00
|
|
|
counters
|
|
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.reduce(|accum, counter| self.make_expression(accum, Op::Add, counter))
|
2023-10-31 17:44:26 +11:00
|
|
|
}
|
|
|
|
|
|
2024-10-16 21:25:09 +11:00
|
|
|
/// Creates a counter whose value is `lhs - SUM(rhs)`.
|
2025-01-17 21:34:57 +11:00
|
|
|
fn make_subtracted_sum(&mut self, lhs: CovTerm, rhs: &[CovTerm]) -> CovTerm {
|
2024-10-16 21:25:09 +11:00
|
|
|
let Some(rhs_sum) = self.make_sum(rhs) else { return lhs };
|
|
|
|
|
self.make_expression(lhs, Op::Subtract, rhs_sum)
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 12:15:40 +10:00
|
|
|
pub(super) fn num_counters(&self) -> usize {
|
2025-01-17 15:25:24 +11:00
|
|
|
let num_counters = self.phys_counter_for_node.len();
|
|
|
|
|
assert_eq!(num_counters, self.next_counter_id.as_usize());
|
|
|
|
|
num_counters
|
2023-09-13 12:15:40 +10:00
|
|
|
}
|
|
|
|
|
|
2025-01-17 21:34:57 +11:00
|
|
|
fn set_node_counter(&mut self, bcb: BasicCoverageBlock, counter: CovTerm) -> CovTerm {
|
2024-10-19 18:02:03 +11:00
|
|
|
let existing = self.node_counters[bcb].replace(counter);
|
|
|
|
|
assert!(
|
|
|
|
|
existing.is_none(),
|
|
|
|
|
"node {bcb:?} already has a counter: {existing:?} => {counter:?}"
|
|
|
|
|
);
|
|
|
|
|
counter
|
2023-06-29 16:50:52 +10:00
|
|
|
}
|
|
|
|
|
|
2024-10-06 20:59:24 +11:00
|
|
|
pub(super) fn term_for_bcb(&self, bcb: BasicCoverageBlock) -> Option<CovTerm> {
|
2025-01-17 21:34:57 +11:00
|
|
|
self.node_counters[bcb]
|
2023-06-29 16:50:52 +10:00
|
|
|
}
|
|
|
|
|
|
2025-01-17 14:43:12 +11:00
|
|
|
/// Returns an iterator over all the nodes in the coverage graph that
|
2024-01-25 13:35:40 +11:00
|
|
|
/// should have a counter-increment statement injected into MIR, along with
|
|
|
|
|
/// each site's corresponding counter ID.
|
|
|
|
|
pub(super) fn counter_increment_sites(
|
2023-09-22 17:36:01 +10:00
|
|
|
&self,
|
2025-01-17 14:43:12 +11:00
|
|
|
) -> impl Iterator<Item = (CounterId, BasicCoverageBlock)> + Captures<'_> {
|
2025-01-17 15:25:24 +11:00
|
|
|
self.phys_counter_for_node.iter().map(|(&site, &id)| (id, site))
|
2023-09-22 17:36:01 +10:00
|
|
|
}
|
|
|
|
|
|
2024-01-25 13:35:40 +11:00
|
|
|
/// Returns an iterator over the subset of BCB nodes that have been associated
|
|
|
|
|
/// with a counter *expression*, along with the ID of that expression.
|
|
|
|
|
pub(super) fn bcb_nodes_with_coverage_expressions(
|
2023-09-22 17:36:01 +10:00
|
|
|
&self,
|
2024-01-25 13:35:40 +11:00
|
|
|
) -> impl Iterator<Item = (BasicCoverageBlock, ExpressionId)> + Captures<'_> {
|
2024-10-19 18:02:03 +11:00
|
|
|
self.node_counters.iter_enumerated().filter_map(|(bcb, &counter)| match counter {
|
2024-01-25 13:35:40 +11:00
|
|
|
// Yield the BCB along with its associated expression ID.
|
2025-01-17 21:34:57 +11:00
|
|
|
Some(CovTerm::Expression(id)) => Some((bcb, id)),
|
2024-01-25 13:35:40 +11:00
|
|
|
// This BCB is associated with a counter or nothing, so skip it.
|
2025-01-17 21:34:57 +11:00
|
|
|
Some(CovTerm::Counter { .. } | CovTerm::Zero) | None => None,
|
2024-01-25 13:35:40 +11:00
|
|
|
})
|
2023-06-29 16:50:52 +10:00
|
|
|
}
|
2023-09-13 13:20:13 +10:00
|
|
|
|
2023-12-30 22:36:11 +11:00
|
|
|
pub(super) fn into_expressions(self) -> IndexVec<ExpressionId, Expression> {
|
2025-01-17 21:34:57 +11:00
|
|
|
self.expressions
|
2023-09-13 13:20:13 +10:00
|
|
|
}
|
2020-10-23 00:45:07 -07:00
|
|
|
}
|