Skip to content

Commit 771659d

Browse files
committed
coverage: Track whether a node's count is the sum of its out-edges
1 parent 6251d16 commit 771659d

File tree

2 files changed

+57
-13
lines changed

2 files changed

+57
-13
lines changed

compiler/rustc_mir_transform/src/coverage/counters.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ impl<'a> MakeBcbCounters<'a> {
316316

317317
let successors = self.basic_coverage_blocks.successors[from_bcb].as_slice();
318318

319+
// If this node's out-edges won't sum to the node's counter,
320+
// then there's no reason to create edge counters here.
321+
if !self.basic_coverage_blocks[from_bcb].is_out_summable {
322+
return;
323+
}
319324
// If this node doesn't have multiple out-edges, or all of its out-edges
320325
// already have counters, then we don't need to create edge counters.
321326
let needs_out_edge_counters = successors.len() > 1
@@ -416,9 +421,10 @@ impl<'a> MakeBcbCounters<'a> {
416421
return self.get_or_make_node_counter(to_bcb);
417422
}
418423

419-
// If the source BCB has only one successor (assumed to be the given target), an edge
420-
// counter is unnecessary. Just get or make a counter for the source BCB.
421-
if self.bcb_successors(from_bcb).len() == 1 {
424+
// If the source node has exactly one out-edge (i.e. this one) and would have
425+
// the same execution count as that edge, then just use the node's counter.
426+
if let Some(simple_succ) = self.basic_coverage_blocks.simple_successor(from_bcb) {
427+
assert_eq!(simple_succ, to_bcb);
422428
return self.get_or_make_node_counter(from_bcb);
423429
}
424430

compiler/rustc_mir_transform/src/coverage/graph.rs

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ impl CoverageGraph {
8787
for &bb in basic_blocks.iter() {
8888
bb_to_bcb[bb] = Some(bcb);
8989
}
90-
let bcb_data = BasicCoverageBlockData::from(basic_blocks);
90+
91+
let is_out_summable = basic_blocks.last().map_or(false, |&bb| {
92+
bcb_filtered_successors(mir_body[bb].terminator()).is_out_summable()
93+
});
94+
let bcb_data = BasicCoverageBlockData { basic_blocks, is_out_summable };
9195
debug!("adding bcb{}: {:?}", bcb.index(), bcb_data);
9296
bcbs.push(bcb_data);
9397
};
@@ -168,8 +172,6 @@ impl CoverageGraph {
168172
/// edges, because if a node _doesn't_ have multiple in-edges, then there's
169173
/// no benefit in having a separate counter for its in-edge, because it
170174
/// would have the same value as the node's own counter.
171-
///
172-
/// FIXME: That assumption might not be true for [`TerminatorKind::Yield`]?
173175
#[inline(always)]
174176
pub(crate) fn bcb_has_multiple_in_edges(&self, bcb: BasicCoverageBlock) -> bool {
175177
// Even though bcb0 conceptually has an extra virtual in-edge due to
@@ -179,6 +181,24 @@ impl CoverageGraph {
179181
// can't have a separate counter anyway.)
180182
self.predecessors[bcb].len() > 1
181183
}
184+
185+
/// Returns the target of this node's sole out-edge, if it has exactly
186+
/// one, but only if that edge can be assumed to have the same execution
187+
/// count as the node itself (in the absence of panics).
188+
pub(crate) fn simple_successor(
189+
&self,
190+
from_bcb: BasicCoverageBlock,
191+
) -> Option<BasicCoverageBlock> {
192+
// If a node's count is the sum of its out-edges, and it has exactly
193+
// one out-edge, then that edge has the same count as the node.
194+
if self.bcbs[from_bcb].is_out_summable
195+
&& let &[to_bcb] = self.successors[from_bcb].as_slice()
196+
{
197+
Some(to_bcb)
198+
} else {
199+
None
200+
}
201+
}
182202
}
183203

184204
impl Index<BasicCoverageBlock> for CoverageGraph {
@@ -266,14 +286,16 @@ rustc_index::newtype_index! {
266286
#[derive(Debug, Clone)]
267287
pub(crate) struct BasicCoverageBlockData {
268288
pub(crate) basic_blocks: Vec<BasicBlock>,
289+
290+
/// If true, this node's execution count can be assumed to be the sum of the
291+
/// execution counts of all of its **out-edges** (assuming no panics).
292+
///
293+
/// Notably, this is false for a node ending with [`TerminatorKind::Yield`],
294+
/// because the yielding coroutine might not be resumed.
295+
pub(crate) is_out_summable: bool,
269296
}
270297

271298
impl BasicCoverageBlockData {
272-
fn from(basic_blocks: Vec<BasicBlock>) -> Self {
273-
assert!(basic_blocks.len() > 0);
274-
Self { basic_blocks }
275-
}
276-
277299
#[inline(always)]
278300
pub(crate) fn leader_bb(&self) -> BasicBlock {
279301
self.basic_blocks[0]
@@ -295,13 +317,27 @@ enum CoverageSuccessors<'a> {
295317
Chainable(BasicBlock),
296318
/// The block cannot be combined into the same BCB as its successor(s).
297319
NotChainable(&'a [BasicBlock]),
320+
/// Yield terminators are not chainable, and their execution count can also
321+
/// differ from the execution count of their out-edge.
322+
Yield(BasicBlock),
298323
}
299324

300325
impl CoverageSuccessors<'_> {
301326
fn is_chainable(&self) -> bool {
302327
match self {
303328
Self::Chainable(_) => true,
304329
Self::NotChainable(_) => false,
330+
Self::Yield(_) => false,
331+
}
332+
}
333+
334+
/// Returns true if the terminator itself is assumed to have the same
335+
/// execution count as the sum of its out-edges (assuming no panics).
336+
fn is_out_summable(&self) -> bool {
337+
match self {
338+
Self::Chainable(_) => true,
339+
Self::NotChainable(_) => true,
340+
Self::Yield(_) => false,
305341
}
306342
}
307343
}
@@ -312,7 +348,9 @@ impl IntoIterator for CoverageSuccessors<'_> {
312348

313349
fn into_iter(self) -> Self::IntoIter {
314350
match self {
315-
Self::Chainable(bb) => Some(bb).into_iter().chain((&[]).iter().copied()),
351+
Self::Chainable(bb) | Self::Yield(bb) => {
352+
Some(bb).into_iter().chain((&[]).iter().copied())
353+
}
316354
Self::NotChainable(bbs) => None.into_iter().chain(bbs.iter().copied()),
317355
}
318356
}
@@ -331,7 +369,7 @@ fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> Covera
331369

332370
// A yield terminator has exactly 1 successor, but should not be chained,
333371
// because its resume edge has a different execution count.
334-
Yield { ref resume, .. } => CoverageSuccessors::NotChainable(std::slice::from_ref(resume)),
372+
Yield { resume, .. } => CoverageSuccessors::Yield(resume),
335373

336374
// These terminators have exactly one coverage-relevant successor,
337375
// and can be chained into it.

0 commit comments

Comments
 (0)