Allow `IndexSlice` to be indexed by ranges. This comes with some annoyances as the index type can no longer inferred from indexing expressions. The biggest offender for this is `IndexVec::from_fn_n(|idx| ..., n)` where the index type won't be inferred from the call site or any index expressions inside the closure. My main use case for this is mapping a `Place` to `Range<Idx>` for value tracking where the range represents all the values the place contains.
201 lines
8.5 KiB
Rust
201 lines
8.5 KiB
Rust
use std::cmp::Ordering;
|
|
|
|
use either::Either;
|
|
use itertools::Itertools;
|
|
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
|
use rustc_data_structures::graph::DirectedGraph;
|
|
use rustc_index::IndexVec;
|
|
use rustc_index::bit_set::DenseBitSet;
|
|
use rustc_middle::mir::coverage::{CounterId, CovTerm, Expression, ExpressionId, Op};
|
|
|
|
use crate::coverage::counters::balanced_flow::BalancedFlowGraph;
|
|
use crate::coverage::counters::node_flow::{
|
|
CounterTerm, NodeCounters, NodeFlowData, node_flow_data_for_balanced_graph,
|
|
};
|
|
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
|
|
|
|
mod balanced_flow;
|
|
pub(crate) mod node_flow;
|
|
mod union_find;
|
|
|
|
/// Struct containing the results of [`prepare_bcb_counters_data`].
|
|
pub(crate) struct BcbCountersData {
|
|
pub(crate) node_flow_data: NodeFlowData<BasicCoverageBlock>,
|
|
pub(crate) priority_list: Vec<BasicCoverageBlock>,
|
|
}
|
|
|
|
/// Analyzes the coverage graph to create intermediate data structures that
|
|
/// will later be used (during codegen) to create physical counters or counter
|
|
/// expressions for each BCB node that needs one.
|
|
pub(crate) fn prepare_bcb_counters_data(graph: &CoverageGraph) -> BcbCountersData {
|
|
// Create the derived graphs that are necessary for subsequent steps.
|
|
let balanced_graph = BalancedFlowGraph::for_graph(graph, |n| !graph[n].is_out_summable);
|
|
let node_flow_data = node_flow_data_for_balanced_graph(&balanced_graph);
|
|
|
|
// Also create a "priority list" of coverage graph nodes, to help determine
|
|
// which ones get physical counters or counter expressions. This needs to
|
|
// be done now, because the later parts of the counter-creation process
|
|
// won't have access to the original coverage graph.
|
|
let priority_list = make_node_flow_priority_list(graph, balanced_graph);
|
|
|
|
BcbCountersData { node_flow_data, priority_list }
|
|
}
|
|
|
|
/// 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.
|
|
fn make_node_flow_priority_list(
|
|
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::<BasicCoverageBlock, _>::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
|
|
}
|
|
|
|
// Converts node counters into a form suitable for embedding into MIR.
|
|
pub(crate) fn transcribe_counters(
|
|
old: &NodeCounters<BasicCoverageBlock>,
|
|
bcb_needs_counter: &DenseBitSet<BasicCoverageBlock>,
|
|
bcbs_seen: &DenseBitSet<BasicCoverageBlock>,
|
|
) -> CoverageCounters {
|
|
let mut new = CoverageCounters::with_num_bcbs(bcb_needs_counter.domain_size());
|
|
|
|
for bcb in bcb_needs_counter.iter() {
|
|
if !bcbs_seen.contains(bcb) {
|
|
// This BCB's code was removed by MIR opts, so its counter is always zero.
|
|
new.set_node_counter(bcb, CovTerm::Zero);
|
|
continue;
|
|
}
|
|
|
|
// Our counter-creation algorithm doesn't guarantee that a node's list
|
|
// of terms starts or ends with a positive term, so partition the
|
|
// counters into "positive" and "negative" lists for easier handling.
|
|
let (mut pos, mut neg): (Vec<_>, Vec<_>) = old.counter_terms[bcb]
|
|
.iter()
|
|
// Filter out any BCBs that were removed by MIR opts;
|
|
// this treats them as having an execution count of 0.
|
|
.filter(|term| bcbs_seen.contains(term.node))
|
|
.partition_map(|&CounterTerm { node, op }| match op {
|
|
Op::Add => Either::Left(node),
|
|
Op::Subtract => Either::Right(node),
|
|
});
|
|
|
|
// 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.
|
|
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 pos = new_counters_for_sites(pos);
|
|
let neg = new_counters_for_sites(neg);
|
|
|
|
let pos_counter = new.make_sum(&pos).unwrap_or(CovTerm::Zero);
|
|
let new_counter = new.make_subtracted_sum(pos_counter, &neg);
|
|
new.set_node_counter(bcb, new_counter);
|
|
}
|
|
|
|
new
|
|
}
|
|
|
|
/// Generates and stores coverage counter and coverage expression information
|
|
/// associated with nodes in the coverage graph.
|
|
pub(super) struct CoverageCounters {
|
|
/// List of places where a counter-increment statement should be injected
|
|
/// into MIR, each with its corresponding counter ID.
|
|
pub(crate) phys_counter_for_node: FxIndexMap<BasicCoverageBlock, CounterId>,
|
|
pub(crate) next_counter_id: CounterId,
|
|
|
|
/// Coverage counters/expressions that are associated with individual BCBs.
|
|
pub(crate) node_counters: IndexVec<BasicCoverageBlock, Option<CovTerm>>,
|
|
|
|
/// Table of expression data, associating each expression ID with its
|
|
/// corresponding operator (+ or -) and its LHS/RHS operands.
|
|
pub(crate) expressions: IndexVec<ExpressionId, Expression>,
|
|
/// Remember expressions that have already been created (or simplified),
|
|
/// so that we don't create unnecessary duplicates.
|
|
expressions_memo: FxHashMap<Expression, CovTerm>,
|
|
}
|
|
|
|
impl CoverageCounters {
|
|
fn with_num_bcbs(num_bcbs: usize) -> Self {
|
|
Self {
|
|
phys_counter_for_node: FxIndexMap::default(),
|
|
next_counter_id: CounterId::ZERO,
|
|
node_counters: IndexVec::from_elem_n(None, num_bcbs),
|
|
expressions: IndexVec::new(),
|
|
expressions_memo: FxHashMap::default(),
|
|
}
|
|
}
|
|
|
|
/// Returns the physical counter for the given node, creating it if necessary.
|
|
fn ensure_phys_counter(&mut self, bcb: BasicCoverageBlock) -> CovTerm {
|
|
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
|
|
});
|
|
CovTerm::Counter(id)
|
|
}
|
|
|
|
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(|| {
|
|
let id = self.expressions.push(new_expr);
|
|
CovTerm::Expression(id)
|
|
})
|
|
}
|
|
|
|
/// Creates a counter that is the sum of the given counters.
|
|
///
|
|
/// Returns `None` if the given list of counters was empty.
|
|
fn make_sum(&mut self, counters: &[CovTerm]) -> Option<CovTerm> {
|
|
counters
|
|
.iter()
|
|
.copied()
|
|
.reduce(|accum, counter| self.make_expression(accum, Op::Add, counter))
|
|
}
|
|
|
|
/// Creates a counter whose value is `lhs - SUM(rhs)`.
|
|
fn make_subtracted_sum(&mut self, lhs: CovTerm, rhs: &[CovTerm]) -> CovTerm {
|
|
let Some(rhs_sum) = self.make_sum(rhs) else { return lhs };
|
|
self.make_expression(lhs, Op::Subtract, rhs_sum)
|
|
}
|
|
|
|
fn set_node_counter(&mut self, bcb: BasicCoverageBlock, counter: CovTerm) -> CovTerm {
|
|
let existing = self.node_counters[bcb].replace(counter);
|
|
assert!(
|
|
existing.is_none(),
|
|
"node {bcb:?} already has a counter: {existing:?} => {counter:?}"
|
|
);
|
|
counter
|
|
}
|
|
}
|