coverage: Handle hole spans without dividing spans into buckets

Because we no longer merge non-adjacent spans, there is no need to use buckets
to prevent merging across hole spans.
This commit is contained in:
Zalathar
2025-04-17 17:03:08 +10:00
parent 4d5a1acebf
commit 77a7ae4e9f

View File

@@ -1,11 +1,8 @@
use std::collections::VecDeque;
use std::iter;
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::mir;
use rustc_middle::ty::TyCtxt;
use rustc_span::{DesugaringKind, ExpnKind, MacroKind, Span};
use tracing::{debug, debug_span, instrument};
use tracing::instrument;
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir, SpanFromMir};
@@ -83,24 +80,17 @@ pub(super) fn extract_refined_covspans<'tcx>(
holes.sort_by(|a, b| compare_spans(a.span, b.span));
holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b));
// Split the covspans into separate buckets that don't overlap any holes.
let buckets = divide_spans_into_buckets(covspans, &holes);
// Discard any span that overlaps with a hole.
discard_spans_overlapping_holes(&mut covspans, &holes);
for covspans in buckets {
let _span = debug_span!("processing bucket", ?covspans).entered();
// Perform more refinement steps after holes have been dealt with.
let mut covspans = remove_unwanted_overlapping_spans(covspans);
covspans.dedup_by(|b, a| a.merge_if_eligible(b));
let mut covspans = remove_unwanted_overlapping_spans(covspans);
debug!(?covspans, "after removing overlaps");
// Do one last merge pass, to simplify the output.
covspans.dedup_by(|b, a| a.merge_if_eligible(b));
debug!(?covspans, "after merge");
code_mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
// Each span produced by the refiner represents an ordinary code region.
mappings::CodeMapping { span, bcb }
}));
}
code_mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
// Each span produced by the refiner represents an ordinary code region.
mappings::CodeMapping { span, bcb }
}));
}
/// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate
@@ -142,52 +132,36 @@ fn shrink_visible_macro_spans(tcx: TyCtxt<'_>, covspans: &mut Vec<SpanFromMir>)
}
}
/// Uses the holes to divide the given covspans into buckets, such that:
/// - No span in any hole overlaps a bucket (discarding spans if necessary).
/// - The spans in each bucket are strictly after all spans in previous buckets,
/// and strictly before all spans in subsequent buckets.
/// Discard all covspans that overlap a hole.
///
/// The lists of covspans and holes must be sorted.
/// The resulting buckets are sorted relative to each other, and each bucket's
/// contents are sorted.
#[instrument(level = "debug")]
fn divide_spans_into_buckets(input_covspans: Vec<Covspan>, holes: &[Hole]) -> Vec<Vec<Covspan>> {
debug_assert!(input_covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
/// The lists of covspans and holes must be sorted, and any holes that overlap
/// with each other must have already been merged.
fn discard_spans_overlapping_holes(covspans: &mut Vec<Covspan>, holes: &[Hole]) {
debug_assert!(covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
debug_assert!(holes.array_windows().all(|[a, b]| !a.span.overlaps_or_adjacent(b.span)));
// Now we're ready to start grouping spans into buckets separated by holes.
let mut curr_hole = 0usize;
let mut overlaps_hole = |covspan: &Covspan| -> bool {
while let Some(hole) = holes.get(curr_hole) {
// Both lists are sorted, so we can permanently skip any holes that
// end before the start of the current span.
if hole.span.hi() <= covspan.span.lo() {
curr_hole += 1;
continue;
}
let mut input_covspans = VecDeque::from(input_covspans);
return hole.span.overlaps(covspan.span);
}
// For each hole:
// - Identify the spans that are entirely or partly before the hole.
// - Discard any that overlap with the hole.
// - Add the remaining identified spans to the corresponding bucket.
let mut buckets = (0..holes.len()).map(|_| vec![]).collect::<Vec<_>>();
for (hole, bucket) in holes.iter().zip(&mut buckets) {
bucket.extend(
drain_front_while(&mut input_covspans, |c| c.span.lo() < hole.span.hi())
.filter(|c| !c.span.overlaps(hole.span)),
);
}
// No holes left, so this covspan doesn't overlap with any holes.
false
};
// Any remaining spans form their own final bucket, after the final hole.
// (If there were no holes, this will just be all of the initial spans.)
buckets.push(Vec::from(input_covspans));
buckets
covspans.retain(|covspan| !overlaps_hole(covspan));
}
/// Similar to `.drain(..)`, but stops just before it would remove an item not
/// satisfying the predicate.
fn drain_front_while<'a, T>(
queue: &'a mut VecDeque<T>,
mut pred_fn: impl FnMut(&T) -> bool,
) -> impl Iterator<Item = T> {
iter::from_fn(move || queue.pop_front_if(|x| pred_fn(x)))
}
/// Takes one of the buckets of (sorted) spans extracted from MIR, and "refines"
/// Takes a list of sorted spans extracted from MIR, and "refines"
/// those spans by removing spans that overlap in unwanted ways.
#[instrument(level = "debug")]
fn remove_unwanted_overlapping_spans(sorted_spans: Vec<Covspan>) -> Vec<Covspan> {