rustc_mir_transform/coverage/
counters.rsuse std::cmp::Ordering;
use either::Either;
use itertools::Itertools;
use rustc_data_structures::captures::Captures;
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::iter_nodes::IterNodes;
use crate::coverage::counters::node_flow::{CounterTerm, MergedNodeFlowGraph, NodeCounters};
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
mod balanced_flow;
mod iter_nodes;
mod node_flow;
mod union_find;
pub(super) fn make_bcb_counters(
graph: &CoverageGraph,
bcb_needs_counter: &DenseBitSet<BasicCoverageBlock>,
) -> CoverageCounters {
let balanced_graph = BalancedFlowGraph::for_graph(graph, |n| !graph[n].is_out_summable);
let merged_graph = MergedNodeFlowGraph::for_balanced_graph(&balanced_graph);
let nodes = make_node_counter_priority_list(graph, balanced_graph);
let node_counters = merged_graph.make_node_counters(&nodes);
transcribe_counters(&node_counters, bcb_needs_counter)
}
fn make_node_counter_priority_list(
graph: &CoverageGraph,
balanced_graph: BalancedFlowGraph<&CoverageGraph>,
) -> Vec<BasicCoverageBlock> {
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<_>>();
assert_eq!(nodes[0], balanced_graph.sink);
nodes[1..].sort_by(|&a, &b| {
Ordering::Equal
.then_with(|| Ord::cmp(&graph[a].is_out_summable, &graph[b].is_out_summable))
.then_with(|| Ord::cmp(&is_reloop_node[a], &is_reloop_node[b]).reverse())
.then_with(|| graph.cmp_in_dominator_order(a, b).reverse())
});
nodes
}
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() {
let (mut pos, mut neg): (Vec<_>, Vec<_>) =
old.counter_expr(bcb).iter().partition_map(|&CounterTerm { node, op }| match op {
Op::Add => Either::Left(node),
Op::Subtract => Either::Right(node),
});
if pos.is_empty() {
debug_assert!(false, "{bcb:?} has no positive counter terms");
pos = vec![bcb];
neg = vec![];
}
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);
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
}
pub(super) struct CoverageCounters {
phys_counter_for_node: FxIndexMap<BasicCoverageBlock, CounterId>,
next_counter_id: CounterId,
node_counters: IndexVec<BasicCoverageBlock, Option<CovTerm>>,
expressions: IndexVec<ExpressionId, Expression>,
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(),
}
}
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)
})
}
fn make_sum(&mut self, counters: &[CovTerm]) -> Option<CovTerm> {
counters
.iter()
.copied()
.reduce(|accum, counter| self.make_expression(accum, Op::Add, counter))
}
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)
}
pub(super) fn num_counters(&self) -> usize {
let num_counters = self.phys_counter_for_node.len();
assert_eq!(num_counters, self.next_counter_id.as_usize());
num_counters
}
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
}
pub(super) fn term_for_bcb(&self, bcb: BasicCoverageBlock) -> Option<CovTerm> {
self.node_counters[bcb]
}
pub(super) fn counter_increment_sites(
&self,
) -> impl Iterator<Item = (CounterId, BasicCoverageBlock)> + Captures<'_> {
self.phys_counter_for_node.iter().map(|(&site, &id)| (id, site))
}
pub(super) fn bcb_nodes_with_coverage_expressions(
&self,
) -> impl Iterator<Item = (BasicCoverageBlock, ExpressionId)> + Captures<'_> {
self.node_counters.iter_enumerated().filter_map(|(bcb, &counter)| match counter {
Some(CovTerm::Expression(id)) => Some((bcb, id)),
Some(CovTerm::Counter { .. } | CovTerm::Zero) | None => None,
})
}
pub(super) fn into_expressions(self) -> IndexVec<ExpressionId, Expression> {
self.expressions
}
}