Skip to content

Commit d6463dc

Browse files
authored
Unrolled build for rust-lang#121261
Rollup merge of rust-lang#121261 - Zalathar:pending-dups, r=oli-obk coverage: Remove `pending_dups` from the span refiner When extracting coverage spans from a function's MIR, we need to decide how to handle spans that are associated with more than one node (BCB) in the coverage control flow graph. The existing code for managing those duplicate spans is very subtle and difficult to modify. But by eagerly deduplicating those extracted spans in a much simpler way, we can remove a massive chunk of complexity from the span refiner. There is a tradeoff here, in that we no longer try to retain *all* nondominating BCBs that have the same span, only the last one in the (semi-arbitrary) dominance ordering. But in practice, this produces very little difference in our coverage tests, and the simplification is so significant that I think it's worthwhile. ``@rustbot`` label +A-code-coverage
2 parents 3406ada + 3a83b27 commit d6463dc

File tree

4 files changed

+34
-179
lines changed

4 files changed

+34
-179
lines changed

compiler/rustc_mir_transform/src/coverage/spans.rs

+16-164
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub(super) fn generate_coverage_spans(
6161
hir_info,
6262
basic_coverage_blocks,
6363
);
64-
let coverage_spans = SpansRefiner::refine_sorted_spans(basic_coverage_blocks, sorted_spans);
64+
let coverage_spans = SpansRefiner::refine_sorted_spans(sorted_spans);
6565
mappings.extend(coverage_spans.into_iter().map(|RefinedCovspan { bcb, span, .. }| {
6666
// Each span produced by the generator represents an ordinary code region.
6767
BcbMapping { kind: BcbMappingKind::Code(bcb), span }
@@ -88,8 +88,6 @@ pub(super) fn generate_coverage_spans(
8888

8989
#[derive(Debug)]
9090
struct CurrCovspan {
91-
/// This is used as the basis for [`PrevCovspan::original_span`], so it must
92-
/// not be modified.
9391
span: Span,
9492
bcb: BasicCoverageBlock,
9593
is_closure: bool,
@@ -102,7 +100,7 @@ impl CurrCovspan {
102100

103101
fn into_prev(self) -> PrevCovspan {
104102
let Self { span, bcb, is_closure } = self;
105-
PrevCovspan { original_span: span, span, bcb, merged_spans: vec![span], is_closure }
103+
PrevCovspan { span, bcb, merged_spans: vec![span], is_closure }
106104
}
107105

108106
fn into_refined(self) -> RefinedCovspan {
@@ -115,7 +113,6 @@ impl CurrCovspan {
115113

116114
#[derive(Debug)]
117115
struct PrevCovspan {
118-
original_span: Span,
119116
span: Span,
120117
bcb: BasicCoverageBlock,
121118
/// List of all the original spans from MIR that have been merged into this
@@ -135,42 +132,17 @@ impl PrevCovspan {
135132
self.merged_spans.push(other.span);
136133
}
137134

138-
fn cutoff_statements_at(&mut self, cutoff_pos: BytePos) {
135+
fn cutoff_statements_at(mut self, cutoff_pos: BytePos) -> Option<RefinedCovspan> {
139136
self.merged_spans.retain(|span| span.hi() <= cutoff_pos);
140137
if let Some(max_hi) = self.merged_spans.iter().map(|span| span.hi()).max() {
141138
self.span = self.span.with_hi(max_hi);
142139
}
143-
}
144-
145-
fn into_dup(self) -> DuplicateCovspan {
146-
let Self { original_span, span, bcb, merged_spans: _, is_closure } = self;
147-
// Only unmodified spans end up in `pending_dups`.
148-
debug_assert_eq!(original_span, span);
149-
DuplicateCovspan { span, bcb, is_closure }
150-
}
151-
152-
fn refined_copy(&self) -> RefinedCovspan {
153-
let &Self { original_span: _, span, bcb, merged_spans: _, is_closure } = self;
154-
RefinedCovspan { span, bcb, is_closure }
155-
}
156140

157-
fn into_refined(self) -> RefinedCovspan {
158-
self.refined_copy()
141+
if self.merged_spans.is_empty() { None } else { Some(self.into_refined()) }
159142
}
160-
}
161-
162-
#[derive(Debug)]
163-
struct DuplicateCovspan {
164-
span: Span,
165-
bcb: BasicCoverageBlock,
166-
is_closure: bool,
167-
}
168143

169-
impl DuplicateCovspan {
170-
/// Returns a copy of this covspan, as a [`RefinedCovspan`].
171-
/// Should only be called in places that would otherwise clone this covspan.
172144
fn refined_copy(&self) -> RefinedCovspan {
173-
let &Self { span, bcb, is_closure } = self;
145+
let &Self { span, bcb, merged_spans: _, is_closure } = self;
174146
RefinedCovspan { span, bcb, is_closure }
175147
}
176148

@@ -205,10 +177,7 @@ impl RefinedCovspan {
205177
/// * Merge spans that represent continuous (both in source code and control flow), non-branching
206178
/// execution
207179
/// * Carve out (leave uncovered) any span that will be counted by another MIR (notably, closures)
208-
struct SpansRefiner<'a> {
209-
/// The BasicCoverageBlock Control Flow Graph (BCB CFG).
210-
basic_coverage_blocks: &'a CoverageGraph,
211-
180+
struct SpansRefiner {
212181
/// The initial set of coverage spans, sorted by `Span` (`lo` and `hi`) and by relative
213182
/// dominance between the `BasicCoverageBlock`s of equal `Span`s.
214183
sorted_spans_iter: std::vec::IntoIter<SpanFromMir>,
@@ -223,36 +192,22 @@ struct SpansRefiner<'a> {
223192
/// If that `curr` was discarded, `prev` retains its value from the previous iteration.
224193
some_prev: Option<PrevCovspan>,
225194

226-
/// One or more coverage spans with the same `Span` but different `BasicCoverageBlock`s, and
227-
/// no `BasicCoverageBlock` in this list dominates another `BasicCoverageBlock` in the list.
228-
/// If a new `curr` span also fits this criteria (compared to an existing list of
229-
/// `pending_dups`), that `curr` moves to `prev` before possibly being added to
230-
/// the `pending_dups` list, on the next iteration. As a result, if `prev` and `pending_dups`
231-
/// have the same `Span`, the criteria for `pending_dups` holds for `prev` as well: a `prev`
232-
/// with a matching `Span` does not dominate any `pending_dup` and no `pending_dup` dominates a
233-
/// `prev` with a matching `Span`)
234-
pending_dups: Vec<DuplicateCovspan>,
235-
236195
/// The final coverage spans to add to the coverage map. A `Counter` or `Expression`
237196
/// will also be injected into the MIR for each BCB that has associated spans.
238197
refined_spans: Vec<RefinedCovspan>,
239198
}
240199

241-
impl<'a> SpansRefiner<'a> {
200+
impl SpansRefiner {
242201
/// Takes the initial list of (sorted) spans extracted from MIR, and "refines"
243202
/// them by merging compatible adjacent spans, removing redundant spans,
244203
/// and carving holes in spans when they overlap in unwanted ways.
245-
fn refine_sorted_spans(
246-
basic_coverage_blocks: &'a CoverageGraph,
247-
sorted_spans: Vec<SpanFromMir>,
248-
) -> Vec<RefinedCovspan> {
204+
fn refine_sorted_spans(sorted_spans: Vec<SpanFromMir>) -> Vec<RefinedCovspan> {
205+
let sorted_spans_len = sorted_spans.len();
249206
let this = Self {
250-
basic_coverage_blocks,
251207
sorted_spans_iter: sorted_spans.into_iter(),
252208
some_curr: None,
253209
some_prev: None,
254-
pending_dups: Vec::new(),
255-
refined_spans: Vec::with_capacity(basic_coverage_blocks.num_nodes() * 2),
210+
refined_spans: Vec::with_capacity(sorted_spans_len),
256211
};
257212

258213
this.to_refined_spans()
@@ -292,21 +247,11 @@ impl<'a> SpansRefiner<'a> {
292247
self.take_curr(); // Discards curr.
293248
} else if curr.is_closure {
294249
self.carve_out_span_for_closure();
295-
} else if prev.original_span == prev.span && prev.span == curr.span {
296-
// Prev and curr have the same span, and prev's span hasn't
297-
// been modified by other spans.
298-
self.update_pending_dups();
299250
} else {
300251
self.cutoff_prev_at_overlapping_curr();
301252
}
302253
}
303254

304-
// Drain any remaining dups into the output.
305-
for dup in self.pending_dups.drain(..) {
306-
debug!(" ...adding at least one pending dup={:?}", dup);
307-
self.refined_spans.push(dup.into_refined());
308-
}
309-
310255
// There is usually a final span remaining in `prev` after the loop ends,
311256
// so add it to the output as well.
312257
if let Some(prev) = self.some_prev.take() {
@@ -359,36 +304,6 @@ impl<'a> SpansRefiner<'a> {
359304
self.some_prev.take().unwrap_or_else(|| bug!("some_prev is None (take_prev)"))
360305
}
361306

362-
/// If there are `pending_dups` but `prev` is not a matching dup (`prev.span` doesn't match the
363-
/// `pending_dups` spans), then one of the following two things happened during the previous
364-
/// iteration:
365-
/// * the previous `curr` span (which is now `prev`) was not a duplicate of the pending_dups
366-
/// (in which case there should be at least two spans in `pending_dups`); or
367-
/// * the `span` of `prev` was modified by `curr_mut().merge_from(prev)` (in which case
368-
/// `pending_dups` could have as few as one span)
369-
/// In either case, no more spans will match the span of `pending_dups`, so
370-
/// add the `pending_dups` if they don't overlap `curr`, and clear the list.
371-
fn maybe_flush_pending_dups(&mut self) {
372-
let Some(last_dup) = self.pending_dups.last() else { return };
373-
if last_dup.span == self.prev().span {
374-
return;
375-
}
376-
377-
debug!(
378-
" SAME spans, but pending_dups are NOT THE SAME, so BCBs matched on \
379-
previous iteration, or prev started a new disjoint span"
380-
);
381-
if last_dup.span.hi() <= self.curr().span.lo() {
382-
for dup in self.pending_dups.drain(..) {
383-
debug!(" ...adding at least one pending={:?}", dup);
384-
self.refined_spans.push(dup.into_refined());
385-
}
386-
} else {
387-
self.pending_dups.clear();
388-
}
389-
assert!(self.pending_dups.is_empty());
390-
}
391-
392307
/// Advance `prev` to `curr` (if any), and `curr` to the next coverage span in sorted order.
393308
fn next_coverage_span(&mut self) -> bool {
394309
if let Some(curr) = self.some_curr.take() {
@@ -408,7 +323,6 @@ impl<'a> SpansRefiner<'a> {
408323
);
409324
} else {
410325
self.some_curr = Some(CurrCovspan::new(curr.span, curr.bcb, curr.is_closure));
411-
self.maybe_flush_pending_dups();
412326
return true;
413327
}
414328
}
@@ -433,13 +347,6 @@ impl<'a> SpansRefiner<'a> {
433347
let mut pre_closure = self.prev().refined_copy();
434348
pre_closure.span = pre_closure.span.with_hi(left_cutoff);
435349
debug!(" prev overlaps a closure. Adding span for pre_closure={:?}", pre_closure);
436-
437-
for mut dup in self.pending_dups.iter().map(DuplicateCovspan::refined_copy) {
438-
dup.span = dup.span.with_hi(left_cutoff);
439-
debug!(" ...and at least one pre_closure dup={:?}", dup);
440-
self.refined_spans.push(dup);
441-
}
442-
443350
self.refined_spans.push(pre_closure);
444351
}
445352

@@ -448,58 +355,9 @@ impl<'a> SpansRefiner<'a> {
448355
self.prev_mut().span = self.prev().span.with_lo(right_cutoff);
449356
debug!(" Mutated prev.span to start after the closure. prev={:?}", self.prev());
450357

451-
for dup in &mut self.pending_dups {
452-
debug!(" ...and at least one overlapping dup={:?}", dup);
453-
dup.span = dup.span.with_lo(right_cutoff);
454-
}
455-
456358
// Prevent this curr from becoming prev.
457359
let closure_covspan = self.take_curr().into_refined();
458360
self.refined_spans.push(closure_covspan); // since self.prev() was already updated
459-
} else {
460-
self.pending_dups.clear();
461-
}
462-
}
463-
464-
/// Called if `curr.span` equals `prev.original_span` (and potentially equal to all
465-
/// `pending_dups` spans, if any). Keep in mind, `prev.span()` may have been changed.
466-
/// If prev.span() was merged into other spans (with matching BCB, for instance),
467-
/// `prev.span.hi()` will be greater than (further right of) `prev.original_span.hi()`.
468-
/// If prev.span() was split off to the right of a closure, prev.span().lo() will be
469-
/// greater than prev.original_span.lo(). The actual span of `prev.original_span` is
470-
/// not as important as knowing that `prev()` **used to have the same span** as `curr()`,
471-
/// which means their sort order is still meaningful for determining the dominator
472-
/// relationship.
473-
///
474-
/// When two coverage spans have the same `Span`, dominated spans can be discarded; but if
475-
/// neither coverage span dominates the other, both (or possibly more than two) are held,
476-
/// until their disposition is determined. In this latter case, the `prev` dup is moved into
477-
/// `pending_dups` so the new `curr` dup can be moved to `prev` for the next iteration.
478-
fn update_pending_dups(&mut self) {
479-
let prev_bcb = self.prev().bcb;
480-
let curr_bcb = self.curr().bcb;
481-
482-
// Equal coverage spans are ordered by dominators before dominated (if any), so it should be
483-
// impossible for `curr` to dominate any previous coverage span.
484-
debug_assert!(!self.basic_coverage_blocks.dominates(curr_bcb, prev_bcb));
485-
486-
// `prev` is a duplicate of `curr`, so add it to the list of pending dups.
487-
// If it dominates `curr`, it will be removed by the subsequent discard step.
488-
let prev = self.take_prev().into_dup();
489-
debug!(?prev, "adding prev to pending dups");
490-
self.pending_dups.push(prev);
491-
492-
let initial_pending_count = self.pending_dups.len();
493-
if initial_pending_count > 0 {
494-
self.pending_dups
495-
.retain(|dup| !self.basic_coverage_blocks.dominates(dup.bcb, curr_bcb));
496-
497-
let n_discarded = initial_pending_count - self.pending_dups.len();
498-
if n_discarded > 0 {
499-
debug!(
500-
" discarded {n_discarded} of {initial_pending_count} pending_dups that dominated curr",
501-
);
502-
}
503361
}
504362
}
505363

@@ -516,19 +374,13 @@ impl<'a> SpansRefiner<'a> {
516374
if it has statements that end before curr; prev={:?}",
517375
self.prev()
518376
);
519-
if self.pending_dups.is_empty() {
520-
let curr_span = self.curr().span;
521-
self.prev_mut().cutoff_statements_at(curr_span.lo());
522-
if self.prev().merged_spans.is_empty() {
523-
debug!(" ... no non-overlapping statements to add");
524-
} else {
525-
debug!(" ... adding modified prev={:?}", self.prev());
526-
let prev = self.take_prev().into_refined();
527-
self.refined_spans.push(prev);
528-
}
377+
378+
let curr_span = self.curr().span;
379+
if let Some(prev) = self.take_prev().cutoff_statements_at(curr_span.lo()) {
380+
debug!("after cutoff, adding {prev:?}");
381+
self.refined_spans.push(prev);
529382
} else {
530-
// with `pending_dups`, `prev` cannot have any statements that don't overlap
531-
self.pending_dups.clear();
383+
debug!("prev was eliminated by cutoff");
532384
}
533385
}
534386
}

compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,19 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
5252
// - Span A extends further left, or
5353
// - Both have the same start and span A extends further right
5454
.then_with(|| Ord::cmp(&a.span.hi(), &b.span.hi()).reverse())
55-
// If both spans are equal, sort the BCBs in dominator order,
56-
// so that dominating BCBs come before other BCBs they dominate.
57-
.then_with(|| basic_coverage_blocks.cmp_in_dominator_order(a.bcb, b.bcb))
58-
// If two spans are otherwise identical, put closure spans first,
59-
// as this seems to be what the refinement step expects.
55+
// If two spans have the same lo & hi, put closure spans first,
56+
// as they take precedence over non-closure spans.
6057
.then_with(|| Ord::cmp(&a.is_closure, &b.is_closure).reverse())
58+
// After deduplication, we want to keep only the most-dominated BCB.
59+
.then_with(|| basic_coverage_blocks.cmp_in_dominator_order(a.bcb, b.bcb).reverse())
6160
});
6261

62+
// Among covspans with the same span, keep only one. Closure spans take
63+
// precedence, otherwise keep the one with the most-dominated BCB.
64+
// (Ideally we should try to preserve _all_ non-dominating BCBs, but that
65+
// requires a lot more complexity in the span refiner, for little benefit.)
66+
initial_spans.dedup_by(|b, a| a.span.source_equal(b.span));
67+
6368
initial_spans
6469
}
6570

tests/coverage/closure_macro.cov-map

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ Number of file 0 mappings: 1
77
- Code(Counter(0)) at (prev + 29, 1) to (start + 2, 2)
88

99
Function name: closure_macro::main
10-
Raw bytes (43): 0x[01, 01, 02, 01, 05, 05, 02, 07, 01, 21, 01, 01, 21, 02, 02, 09, 00, 0f, 05, 00, 12, 00, 13, 02, 00, 12, 00, 13, 05, 00, 54, 00, 55, 02, 02, 09, 02, 0b, 07, 03, 01, 00, 02]
10+
Raw bytes (38): 0x[01, 01, 02, 01, 05, 05, 02, 06, 01, 21, 01, 01, 21, 02, 02, 09, 00, 12, 02, 00, 0f, 00, 54, 05, 00, 54, 00, 55, 02, 02, 09, 02, 0b, 07, 03, 01, 00, 02]
1111
Number of files: 1
1212
- file 0 => global file 1
1313
Number of expressions: 2
1414
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
1515
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
16-
Number of file 0 mappings: 7
16+
Number of file 0 mappings: 6
1717
- Code(Counter(0)) at (prev + 33, 1) to (start + 1, 33)
18-
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 0, 15)
18+
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 0, 18)
1919
= (c0 - c1)
20-
- Code(Counter(1)) at (prev + 0, 18) to (start + 0, 19)
21-
- Code(Expression(0, Sub)) at (prev + 0, 18) to (start + 0, 19)
20+
- Code(Expression(0, Sub)) at (prev + 0, 15) to (start + 0, 84)
2221
= (c0 - c1)
2322
- Code(Counter(1)) at (prev + 0, 84) to (start + 0, 85)
2423
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 2, 11)

tests/coverage/closure_macro_async.cov-map

+4-5
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,17 @@ Number of file 0 mappings: 1
1515
- Code(Counter(0)) at (prev + 35, 1) to (start + 0, 43)
1616

1717
Function name: closure_macro_async::test::{closure#0}
18-
Raw bytes (43): 0x[01, 01, 02, 01, 05, 05, 02, 07, 01, 23, 2b, 01, 21, 02, 02, 09, 00, 0f, 05, 00, 12, 00, 13, 02, 00, 12, 00, 13, 05, 00, 54, 00, 55, 02, 02, 09, 02, 0b, 07, 03, 01, 00, 02]
18+
Raw bytes (38): 0x[01, 01, 02, 01, 05, 05, 02, 06, 01, 23, 2b, 01, 21, 02, 02, 09, 00, 12, 02, 00, 0f, 00, 54, 05, 00, 54, 00, 55, 02, 02, 09, 02, 0b, 07, 03, 01, 00, 02]
1919
Number of files: 1
2020
- file 0 => global file 1
2121
Number of expressions: 2
2222
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
2323
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
24-
Number of file 0 mappings: 7
24+
Number of file 0 mappings: 6
2525
- Code(Counter(0)) at (prev + 35, 43) to (start + 1, 33)
26-
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 0, 15)
26+
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 0, 18)
2727
= (c0 - c1)
28-
- Code(Counter(1)) at (prev + 0, 18) to (start + 0, 19)
29-
- Code(Expression(0, Sub)) at (prev + 0, 18) to (start + 0, 19)
28+
- Code(Expression(0, Sub)) at (prev + 0, 15) to (start + 0, 84)
3029
= (c0 - c1)
3130
- Code(Counter(1)) at (prev + 0, 84) to (start + 0, 85)
3231
- Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 2, 11)

0 commit comments

Comments
 (0)