Skip to content

Commit 7468eea

Browse files
authored
Rollup merge of rust-lang#139102 - Zalathar:no-split, r=oli-obk
coverage: Avoid splitting spans during span extraction/refinement This PR removes or simplifies some of the steps involved in extracting coverage-relevant spans from MIR, and preparing them for use in coverage instrumentation metadata. A common theme is that we now try harder to avoid modifying or combining spans in non-trivial ways, because those modifications present the most risk for weird behaviour or ICEs. The main changes are: - When extracting spans from MIR call terminators, try to restrict them to just the function name. - Instead of splitting spans around “holes”, just discard any span that overlaps with a hole. - Instead of splitting macro-invocation spans into two parts, truncate them to just the macro name and subsequent `!`. --- This results in a lot of tiny changes to the spans that end up in coverage metadata, and a few changes to coverage reports. Judging by test snapshots, these changes appear to be quite minor in practice.
2 parents 2b187ff + 26cea8a commit 7468eea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+304
-346
lines changed

compiler/rustc_mir_transform/src/coverage/mappings.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>(
9696
}
9797
} else {
9898
// Extract coverage spans from MIR statements/terminators as normal.
99-
extract_refined_covspans(mir_body, hir_info, graph, &mut code_mappings);
99+
extract_refined_covspans(tcx, mir_body, hir_info, graph, &mut code_mappings);
100100
}
101101

102102
branch_pairs.extend(extract_branch_pairs(mir_body, hir_info, graph));

compiler/rustc_mir_transform/src/coverage/spans.rs

+30-77
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::collections::VecDeque;
2+
use std::iter;
23

34
use rustc_data_structures::fx::FxHashSet;
45
use rustc_middle::mir;
6+
use rustc_middle::ty::TyCtxt;
57
use rustc_span::{DesugaringKind, ExpnKind, MacroKind, Span};
68
use tracing::{debug, debug_span, instrument};
79

@@ -11,8 +13,9 @@ use crate::coverage::{ExtractedHirInfo, mappings, unexpand};
1113

1214
mod from_mir;
1315

14-
pub(super) fn extract_refined_covspans(
15-
mir_body: &mir::Body<'_>,
16+
pub(super) fn extract_refined_covspans<'tcx>(
17+
tcx: TyCtxt<'tcx>,
18+
mir_body: &mir::Body<'tcx>,
1619
hir_info: &ExtractedHirInfo,
1720
graph: &CoverageGraph,
1821
code_mappings: &mut impl Extend<mappings::CodeMapping>,
@@ -50,7 +53,7 @@ pub(super) fn extract_refined_covspans(
5053
// First, perform the passes that need macro information.
5154
covspans.sort_by(|a, b| graph.cmp_in_dominator_order(a.bcb, b.bcb));
5255
remove_unwanted_expansion_spans(&mut covspans);
53-
split_visible_macro_spans(&mut covspans);
56+
shrink_visible_macro_spans(tcx, &mut covspans);
5457

5558
// We no longer need the extra information in `SpanFromMir`, so convert to `Covspan`.
5659
let mut covspans = covspans.into_iter().map(SpanFromMir::into_covspan).collect::<Vec<_>>();
@@ -83,9 +86,7 @@ pub(super) fn extract_refined_covspans(
8386
// Split the covspans into separate buckets that don't overlap any holes.
8487
let buckets = divide_spans_into_buckets(covspans, &holes);
8588

86-
for mut covspans in buckets {
87-
// Make sure each individual bucket is internally sorted.
88-
covspans.sort_by(compare_covspans);
89+
for covspans in buckets {
8990
let _span = debug_span!("processing bucket", ?covspans).entered();
9091

9192
let mut covspans = remove_unwanted_overlapping_spans(covspans);
@@ -129,82 +130,50 @@ fn remove_unwanted_expansion_spans(covspans: &mut Vec<SpanFromMir>) {
129130
}
130131

131132
/// When a span corresponds to a macro invocation that is visible from the
132-
/// function body, split it into two parts. The first part covers just the
133-
/// macro name plus `!`, and the second part covers the rest of the macro
134-
/// invocation. This seems to give better results for code that uses macros.
135-
fn split_visible_macro_spans(covspans: &mut Vec<SpanFromMir>) {
136-
let mut extra_spans = vec![];
137-
138-
covspans.retain(|covspan| {
139-
let Some(ExpnKind::Macro(MacroKind::Bang, visible_macro)) = covspan.expn_kind else {
140-
return true;
141-
};
142-
143-
let split_len = visible_macro.as_str().len() as u32 + 1;
144-
let (before, after) = covspan.span.split_at(split_len);
145-
if !covspan.span.contains(before) || !covspan.span.contains(after) {
146-
// Something is unexpectedly wrong with the split point.
147-
// The debug assertion in `split_at` will have already caught this,
148-
// but in release builds it's safer to do nothing and maybe get a
149-
// bug report for unexpected coverage, rather than risk an ICE.
150-
return true;
133+
/// function body, truncate it to just the macro name plus `!`.
134+
/// This seems to give better results for code that uses macros.
135+
fn shrink_visible_macro_spans(tcx: TyCtxt<'_>, covspans: &mut Vec<SpanFromMir>) {
136+
let source_map = tcx.sess.source_map();
137+
138+
for covspan in covspans {
139+
if matches!(covspan.expn_kind, Some(ExpnKind::Macro(MacroKind::Bang, _))) {
140+
covspan.span = source_map.span_through_char(covspan.span, '!');
151141
}
152-
153-
extra_spans.push(SpanFromMir::new(before, covspan.expn_kind.clone(), covspan.bcb));
154-
extra_spans.push(SpanFromMir::new(after, covspan.expn_kind.clone(), covspan.bcb));
155-
false // Discard the original covspan that we just split.
156-
});
157-
158-
// The newly-split spans are added at the end, so any previous sorting
159-
// is not preserved.
160-
covspans.extend(extra_spans);
142+
}
161143
}
162144

163145
/// Uses the holes to divide the given covspans into buckets, such that:
164-
/// - No span in any hole overlaps a bucket (truncating the spans if necessary).
146+
/// - No span in any hole overlaps a bucket (discarding spans if necessary).
165147
/// - The spans in each bucket are strictly after all spans in previous buckets,
166148
/// and strictly before all spans in subsequent buckets.
167149
///
168-
/// The resulting buckets are sorted relative to each other, but might not be
169-
/// internally sorted.
150+
/// The lists of covspans and holes must be sorted.
151+
/// The resulting buckets are sorted relative to each other, and each bucket's
152+
/// contents are sorted.
170153
#[instrument(level = "debug")]
171154
fn divide_spans_into_buckets(input_covspans: Vec<Covspan>, holes: &[Hole]) -> Vec<Vec<Covspan>> {
172155
debug_assert!(input_covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
173156
debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
174157

175-
// Now we're ready to start carving holes out of the initial coverage spans,
176-
// and grouping them in buckets separated by the holes.
158+
// Now we're ready to start grouping spans into buckets separated by holes.
177159

178160
let mut input_covspans = VecDeque::from(input_covspans);
179-
let mut fragments = vec![];
180161

181162
// For each hole:
182163
// - Identify the spans that are entirely or partly before the hole.
183-
// - Put those spans in a corresponding bucket, truncated to the start of the hole.
184-
// - If one of those spans also extends after the hole, put the rest of it
185-
// in a "fragments" vector that is processed by the next hole.
164+
// - Discard any that overlap with the hole.
165+
// - Add the remaining identified spans to the corresponding bucket.
186166
let mut buckets = (0..holes.len()).map(|_| vec![]).collect::<Vec<_>>();
187167
for (hole, bucket) in holes.iter().zip(&mut buckets) {
188-
let fragments_from_prev = std::mem::take(&mut fragments);
189-
190-
// Only inspect spans that precede or overlap this hole,
191-
// leaving the rest to be inspected by later holes.
192-
// (This relies on the spans and holes both being sorted.)
193-
let relevant_input_covspans =
194-
drain_front_while(&mut input_covspans, |c| c.span.lo() < hole.span.hi());
195-
196-
for covspan in fragments_from_prev.into_iter().chain(relevant_input_covspans) {
197-
let (before, after) = covspan.split_around_hole_span(hole.span);
198-
bucket.extend(before);
199-
fragments.extend(after);
200-
}
168+
bucket.extend(
169+
drain_front_while(&mut input_covspans, |c| c.span.lo() < hole.span.hi())
170+
.filter(|c| !c.span.overlaps(hole.span)),
171+
);
201172
}
202173

203-
// After finding the spans before each hole, any remaining fragments/spans
204-
// form their own final bucket, after the final hole.
174+
// Any remaining spans form their own final bucket, after the final hole.
205175
// (If there were no holes, this will just be all of the initial spans.)
206-
fragments.extend(input_covspans);
207-
buckets.push(fragments);
176+
buckets.push(Vec::from(input_covspans));
208177

209178
buckets
210179
}
@@ -215,7 +184,7 @@ fn drain_front_while<'a, T>(
215184
queue: &'a mut VecDeque<T>,
216185
mut pred_fn: impl FnMut(&T) -> bool,
217186
) -> impl Iterator<Item = T> {
218-
std::iter::from_fn(move || if pred_fn(queue.front()?) { queue.pop_front() } else { None })
187+
iter::from_fn(move || queue.pop_front_if(|x| pred_fn(x)))
219188
}
220189

221190
/// Takes one of the buckets of (sorted) spans extracted from MIR, and "refines"
@@ -258,22 +227,6 @@ struct Covspan {
258227
}
259228

260229
impl Covspan {
261-
/// Splits this covspan into 0-2 parts:
262-
/// - The part that is strictly before the hole span, if any.
263-
/// - The part that is strictly after the hole span, if any.
264-
fn split_around_hole_span(&self, hole_span: Span) -> (Option<Self>, Option<Self>) {
265-
let before = try {
266-
let span = self.span.trim_end(hole_span)?;
267-
Self { span, ..*self }
268-
};
269-
let after = try {
270-
let span = self.span.trim_start(hole_span)?;
271-
Self { span, ..*self }
272-
};
273-
274-
(before, after)
275-
}
276-
277230
/// If `self` and `other` can be merged (i.e. they have the same BCB),
278231
/// mutates `self.span` to also include `other.span` and returns true.
279232
///

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

+7-11
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,20 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Span> {
120120
// an `if condition { block }` has a span that includes the executed block, if true,
121121
// but for coverage, the code region executed, up to *and* through the SwitchInt,
122122
// actually stops before the if's block.)
123-
TerminatorKind::Unreachable // Unreachable blocks are not connected to the MIR CFG
123+
TerminatorKind::Unreachable
124124
| TerminatorKind::Assert { .. }
125125
| TerminatorKind::Drop { .. }
126126
| TerminatorKind::SwitchInt { .. }
127-
// For `FalseEdge`, only the `real` branch is taken, so it is similar to a `Goto`.
128127
| TerminatorKind::FalseEdge { .. }
129128
| TerminatorKind::Goto { .. } => None,
130129

131130
// Call `func` operand can have a more specific span when part of a chain of calls
132-
TerminatorKind::Call { ref func, .. }
133-
| TerminatorKind::TailCall { ref func, .. } => {
131+
TerminatorKind::Call { ref func, .. } | TerminatorKind::TailCall { ref func, .. } => {
134132
let mut span = terminator.source_info.span;
135-
if let mir::Operand::Constant(box constant) = func {
136-
if constant.span.lo() > span.lo() {
137-
span = span.with_lo(constant.span.lo());
138-
}
133+
if let mir::Operand::Constant(constant) = func
134+
&& span.contains(constant.span)
135+
{
136+
span = constant.span;
139137
}
140138
Some(span)
141139
}
@@ -147,9 +145,7 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Span> {
147145
| TerminatorKind::Yield { .. }
148146
| TerminatorKind::CoroutineDrop
149147
| TerminatorKind::FalseUnwind { .. }
150-
| TerminatorKind::InlineAsm { .. } => {
151-
Some(terminator.source_info.span)
152-
}
148+
| TerminatorKind::InlineAsm { .. } => Some(terminator.source_info.span),
153149
}
154150
}
155151

compiler/rustc_mir_transform/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#![feature(map_try_insert)]
1313
#![feature(never_type)]
1414
#![feature(try_blocks)]
15+
#![feature(vec_deque_pop_if)]
1516
#![feature(yeet_expr)]
1617
// tidy-alphabetical-end
1718

tests/coverage/abort.cov-map

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ Number of file 0 mappings: 13
3434
Highest counter ID seen: c4
3535

3636
Function name: abort::might_abort
37-
Raw bytes (21): 0x[01, 01, 01, 01, 05, 03, 01, 03, 01, 01, 14, 05, 02, 09, 01, 24, 02, 02, 0c, 03, 02]
37+
Raw bytes (21): 0x[01, 01, 01, 01, 05, 03, 01, 03, 01, 01, 14, 05, 02, 09, 01, 0f, 02, 02, 0c, 03, 02]
3838
Number of files: 1
3939
- file 0 => global file 1
4040
Number of expressions: 1
4141
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
4242
Number of file 0 mappings: 3
4343
- Code(Counter(0)) at (prev + 3, 1) to (start + 1, 20)
44-
- Code(Counter(1)) at (prev + 2, 9) to (start + 1, 36)
44+
- Code(Counter(1)) at (prev + 2, 9) to (start + 1, 15)
4545
- Code(Expression(0, Sub)) at (prev + 2, 12) to (start + 3, 2)
4646
= (c0 - c1)
4747
Highest counter ID seen: c1

tests/coverage/assert-ne.cov-map

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
Function name: assert_ne::main
2-
Raw bytes (28): 0x[01, 01, 02, 01, 05, 01, 09, 04, 01, 08, 01, 03, 1c, 05, 04, 0d, 00, 13, 02, 02, 0d, 00, 13, 06, 03, 05, 01, 02]
2+
Raw bytes (28): 0x[01, 01, 02, 01, 05, 01, 09, 04, 01, 08, 01, 03, 15, 05, 04, 0d, 00, 13, 02, 02, 0d, 00, 13, 06, 03, 05, 01, 02]
33
Number of files: 1
44
- file 0 => global file 1
55
Number of expressions: 2
66
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
77
- expression 1 operands: lhs = Counter(0), rhs = Counter(2)
88
Number of file 0 mappings: 4
9-
- Code(Counter(0)) at (prev + 8, 1) to (start + 3, 28)
9+
- Code(Counter(0)) at (prev + 8, 1) to (start + 3, 21)
1010
- Code(Counter(1)) at (prev + 4, 13) to (start + 0, 19)
1111
- Code(Expression(0, Sub)) at (prev + 2, 13) to (start + 0, 19)
1212
= (c0 - c1)

tests/coverage/assert-ne.coverage

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
LL| |
88
LL| 1|fn main() {
99
LL| 1| assert_ne!(
10-
LL| 1| Foo(5), // Make sure this expression's span isn't lost.
10+
LL| 1| black_box(Foo(5)), // Make sure this expression's span isn't lost.
1111
LL| 1| if black_box(false) {
1212
LL| 0| Foo(0) //
1313
LL| | } else {

tests/coverage/assert-ne.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ struct Foo(u32);
77

88
fn main() {
99
assert_ne!(
10-
Foo(5), // Make sure this expression's span isn't lost.
10+
black_box(Foo(5)), // Make sure this expression's span isn't lost.
1111
if black_box(false) {
1212
Foo(0) //
1313
} else {

tests/coverage/assert_not.cov-map

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
Function name: assert_not::main
2-
Raw bytes (29): 0x[01, 01, 00, 05, 01, 06, 01, 01, 12, 01, 02, 05, 00, 14, 01, 01, 05, 00, 14, 01, 01, 05, 00, 16, 01, 01, 01, 00, 02]
2+
Raw bytes (29): 0x[01, 01, 00, 05, 01, 06, 01, 01, 11, 01, 02, 05, 00, 13, 01, 01, 05, 00, 13, 01, 01, 05, 00, 15, 01, 01, 01, 00, 02]
33
Number of files: 1
44
- file 0 => global file 1
55
Number of expressions: 0
66
Number of file 0 mappings: 5
7-
- Code(Counter(0)) at (prev + 6, 1) to (start + 1, 18)
8-
- Code(Counter(0)) at (prev + 2, 5) to (start + 0, 20)
9-
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 20)
10-
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 22)
7+
- Code(Counter(0)) at (prev + 6, 1) to (start + 1, 17)
8+
- Code(Counter(0)) at (prev + 2, 5) to (start + 0, 19)
9+
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 19)
10+
- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 21)
1111
- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2)
1212
Highest counter ID seen: c0
1313

tests/coverage/async_block.cov-map

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Function name: async_block::main
2-
Raw bytes (36): 0x[01, 01, 01, 05, 01, 06, 01, 07, 01, 00, 0b, 02, 01, 09, 00, 0a, 05, 00, 0e, 00, 13, 02, 00, 14, 01, 16, 02, 07, 0a, 02, 06, 01, 03, 01, 00, 02]
2+
Raw bytes (36): 0x[01, 01, 01, 05, 01, 06, 01, 07, 01, 00, 0b, 02, 01, 09, 00, 0a, 05, 00, 0e, 00, 13, 02, 01, 0d, 00, 13, 02, 07, 09, 00, 22, 01, 02, 01, 00, 02]
33
Number of files: 1
44
- file 0 => global file 1
55
Number of expressions: 1
@@ -9,11 +9,11 @@ Number of file 0 mappings: 6
99
- Code(Expression(0, Sub)) at (prev + 1, 9) to (start + 0, 10)
1010
= (c1 - c0)
1111
- Code(Counter(1)) at (prev + 0, 14) to (start + 0, 19)
12-
- Code(Expression(0, Sub)) at (prev + 0, 20) to (start + 1, 22)
12+
- Code(Expression(0, Sub)) at (prev + 1, 13) to (start + 0, 19)
1313
= (c1 - c0)
14-
- Code(Expression(0, Sub)) at (prev + 7, 10) to (start + 2, 6)
14+
- Code(Expression(0, Sub)) at (prev + 7, 9) to (start + 0, 34)
1515
= (c1 - c0)
16-
- Code(Counter(0)) at (prev + 3, 1) to (start + 0, 2)
16+
- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2)
1717
Highest counter ID seen: c1
1818

1919
Function name: async_block::main::{closure#0}

tests/coverage/async_block.coverage

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
LL| 12| }
1616
LL| 16| };
1717
LL| 16| executor::block_on(future);
18-
LL| 16| }
18+
LL| | }
1919
LL| 1|}
2020

tests/coverage/async_closure.cov-map

+8-6
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,23 @@ Number of file 0 mappings: 2
3030
Highest counter ID seen: c0
3131

3232
Function name: async_closure::main::{closure#0}
33-
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 23, 00, 24]
33+
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
3434
Number of files: 1
3535
- file 0 => global file 1
3636
Number of expressions: 0
37-
Number of file 0 mappings: 1
38-
- Code(Counter(0)) at (prev + 11, 35) to (start + 0, 36)
37+
Number of file 0 mappings: 2
38+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
39+
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
3940
Highest counter ID seen: c0
4041

4142
Function name: async_closure::main::{closure#0}
42-
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 23, 00, 24]
43+
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
4344
Number of files: 1
4445
- file 0 => global file 1
4546
Number of expressions: 0
46-
Number of file 0 mappings: 1
47-
- Code(Counter(0)) at (prev + 11, 35) to (start + 0, 36)
47+
Number of file 0 mappings: 2
48+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
49+
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
4850
Highest counter ID seen: c0
4951

5052
Function name: async_closure::main::{closure#0}::{closure#0}::<i16>

tests/coverage/async_closure.coverage

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
LL| |
1010
LL| 1|pub fn main() {
1111
LL| 2| let async_closure = async || {};
12-
^1
1312
------------------
1413
| async_closure::main::{closure#0}:
1514
| LL| 1| let async_closure = async || {};
1615
------------------
1716
| async_closure::main::{closure#0}:
1817
| LL| 1| let async_closure = async || {};
18+
------------------
19+
| async_closure::main::{closure#0}::{closure#0}::<i16>:
20+
| LL| 1| let async_closure = async || {};
1921
------------------
2022
LL| 1| executor::block_on(async_closure());
2123
LL| 1| executor::block_on(call_once(async_closure));

0 commit comments

Comments
 (0)