1
+ use std:: collections:: VecDeque ;
2
+
3
+ use rustc_data_structures:: captures:: Captures ;
1
4
use rustc_data_structures:: fx:: FxHashSet ;
2
5
use rustc_middle:: bug;
3
6
use rustc_middle:: mir:: coverage:: CoverageKind ;
@@ -16,8 +19,11 @@ use crate::coverage::ExtractedHirInfo;
16
19
/// spans, each associated with a node in the coverage graph (BCB) and possibly
17
20
/// other metadata.
18
21
///
19
- /// The returned spans are sorted in a specific order that is expected by the
20
- /// subsequent span-refinement step.
22
+ /// The returned spans are divided into one or more buckets, such that:
23
+ /// - The spans in each bucket are strictly after all spans in previous buckets,
24
+ /// and strictly before all spans in subsequent buckets.
25
+ /// - The contents of each bucket are also sorted, in a specific order that is
26
+ /// expected by the subsequent span-refinement step.
21
27
pub ( super ) fn mir_to_initial_sorted_coverage_spans (
22
28
mir_body : & mir:: Body < ' _ > ,
23
29
hir_info : & ExtractedHirInfo ,
@@ -26,13 +32,21 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
26
32
let & ExtractedHirInfo { body_span, .. } = hir_info;
27
33
28
34
let mut initial_spans = vec ! [ ] ;
35
+ let mut holes = vec ! [ ] ;
29
36
30
37
for ( bcb, bcb_data) in basic_coverage_blocks. iter_enumerated ( ) {
31
- bcb_to_initial_coverage_spans ( mir_body, body_span, bcb, bcb_data, & mut initial_spans) ;
38
+ bcb_to_initial_coverage_spans (
39
+ mir_body,
40
+ body_span,
41
+ bcb,
42
+ bcb_data,
43
+ & mut initial_spans,
44
+ & mut holes,
45
+ ) ;
32
46
}
33
47
34
48
// Only add the signature span if we found at least one span in the body.
35
- if !initial_spans. is_empty ( ) {
49
+ if !initial_spans. is_empty ( ) || !holes . is_empty ( ) {
36
50
// If there is no usable signature span, add a fake one (before refinement)
37
51
// to avoid an ugly gap between the body start and the first real span.
38
52
// FIXME: Find a more principled way to solve this problem.
@@ -44,29 +58,85 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
44
58
remove_unwanted_macro_spans ( & mut initial_spans) ;
45
59
split_visible_macro_spans ( & mut initial_spans) ;
46
60
47
- initial_spans. sort_by ( |a, b| {
48
- // First sort by span start.
49
- Ord :: cmp ( & a. span . lo ( ) , & b. span . lo ( ) )
50
- // If span starts are the same, sort by span end in reverse order.
51
- // This ensures that if spans A and B are adjacent in the list,
52
- // and they overlap but are not equal, then either:
53
- // - Span A extends further left, or
54
- // - Both have the same start and span A extends further right
55
- . then_with ( || Ord :: cmp ( & a. span . hi ( ) , & b. span . hi ( ) ) . reverse ( ) )
56
- // If two spans have the same lo & hi, put hole spans first,
57
- // as they take precedence over non-hole spans.
58
- . then_with ( || Ord :: cmp ( & a. is_hole , & b. is_hole ) . reverse ( ) )
61
+ let compare_covspans = |a : & SpanFromMir , b : & SpanFromMir | {
62
+ compare_spans ( a. span , b. span )
59
63
// After deduplication, we want to keep only the most-dominated BCB.
60
64
. then_with ( || basic_coverage_blocks. cmp_in_dominator_order ( a. bcb , b. bcb ) . reverse ( ) )
61
- } ) ;
65
+ } ;
66
+ initial_spans. sort_by ( compare_covspans) ;
62
67
63
- // Among covspans with the same span, keep only one. Hole spans take
64
- // precedence, otherwise keep the one with the most-dominated BCB.
68
+ // Among covspans with the same span, keep only one,
69
+ // preferring the one with the most-dominated BCB.
65
70
// (Ideally we should try to preserve _all_ non-dominating BCBs, but that
66
71
// requires a lot more complexity in the span refiner, for little benefit.)
67
72
initial_spans. dedup_by ( |b, a| a. span . source_equal ( b. span ) ) ;
68
73
69
- vec ! [ initial_spans]
74
+ // Sort the holes, and merge overlapping/adjacent holes.
75
+ holes. sort_by ( |a, b| compare_spans ( a. span , b. span ) ) ;
76
+ holes. dedup_by ( |b, a| a. merge_if_overlapping_or_adjacent ( b) ) ;
77
+
78
+ // Now we're ready to start carving holes out of the initial coverage spans,
79
+ // and grouping them in buckets separated by the holes.
80
+
81
+ let mut initial_spans = VecDeque :: from ( initial_spans) ;
82
+ let mut fragments_from_prev: Vec < SpanFromMir > = vec ! [ ] ;
83
+
84
+ // For each hole:
85
+ // - Identify the spans that are entirely or partly before the hole.
86
+ // - Put those spans in a corresponding bucket, truncated to the start of the hole.
87
+ // - If one of those spans also extends after the hole, put the rest of it
88
+ // in a "fragments" vector that is processed by the next hole.
89
+ let mut buckets = ( 0 ..holes. len ( ) ) . map ( |_| vec ! [ ] ) . collect :: < Vec < _ > > ( ) ;
90
+ for ( hole, bucket) in holes. iter ( ) . zip ( & mut buckets) {
91
+ let mut fragments_for_next = vec ! [ ] ;
92
+
93
+ // Only inspect spans that precede or overlap this hole,
94
+ // leaving the rest to be inspected by later holes.
95
+ // (This relies on the spans and holes both being sorted.)
96
+ let relevant_initial_spans =
97
+ drain_front_while ( & mut initial_spans, |c| c. span . lo ( ) < hole. span . hi ( ) ) ;
98
+
99
+ for covspan in fragments_from_prev. drain ( ..) . chain ( relevant_initial_spans) {
100
+ let ( before, after) = covspan. split_around_hole_span ( hole. span ) ;
101
+ bucket. extend ( before) ;
102
+ fragments_for_next. extend ( after) ;
103
+ }
104
+
105
+ assert ! ( fragments_from_prev. is_empty( ) ) ;
106
+ fragments_from_prev = fragments_for_next;
107
+ }
108
+
109
+ // After finding the spans before each hole, any remaining fragments/spans
110
+ // form their own final bucket, after the final hole.
111
+ // (If there were no holes, this will just be all of the initial spans.)
112
+ fragments_from_prev. extend ( initial_spans) ;
113
+ buckets. push ( fragments_from_prev) ;
114
+
115
+ // Make sure each individual bucket is still internally sorted.
116
+ for bucket in & mut buckets {
117
+ bucket. sort_by ( compare_covspans) ;
118
+ }
119
+ buckets
120
+ }
121
+
122
+ fn compare_spans ( a : Span , b : Span ) -> std:: cmp:: Ordering {
123
+ // First sort by span start.
124
+ Ord :: cmp ( & a. lo ( ) , & b. lo ( ) )
125
+ // If span starts are the same, sort by span end in reverse order.
126
+ // This ensures that if spans A and B are adjacent in the list,
127
+ // and they overlap but are not equal, then either:
128
+ // - Span A extends further left, or
129
+ // - Both have the same start and span A extends further right
130
+ . then_with ( || Ord :: cmp ( & a. hi ( ) , & b. hi ( ) ) . reverse ( ) )
131
+ }
132
+
133
+ /// Similar to `.drain(..)`, but stops just before it would remove an item not
134
+ /// satisfying the predicate.
135
+ fn drain_front_while < ' a , T > (
136
+ queue : & ' a mut VecDeque < T > ,
137
+ mut pred_fn : impl FnMut ( & T ) -> bool ,
138
+ ) -> impl Iterator < Item = T > + Captures < ' a > {
139
+ std:: iter:: from_fn ( move || if pred_fn ( queue. front ( ) ?) { queue. pop_front ( ) } else { None } )
70
140
}
71
141
72
142
/// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate
@@ -79,8 +149,8 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
79
149
fn remove_unwanted_macro_spans ( initial_spans : & mut Vec < SpanFromMir > ) {
80
150
let mut seen_macro_spans = FxHashSet :: default ( ) ;
81
151
initial_spans. retain ( |covspan| {
82
- // Ignore (retain) hole spans and non-macro-expansion spans.
83
- if covspan. is_hole || covspan . visible_macro . is_none ( ) {
152
+ // Ignore (retain) non-macro-expansion spans.
153
+ if covspan. visible_macro . is_none ( ) {
84
154
return true ;
85
155
}
86
156
@@ -97,10 +167,6 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
97
167
let mut extra_spans = vec ! [ ] ;
98
168
99
169
initial_spans. retain ( |covspan| {
100
- if covspan. is_hole {
101
- return true ;
102
- }
103
-
104
170
let Some ( visible_macro) = covspan. visible_macro else { return true } ;
105
171
106
172
let split_len = visible_macro. as_str ( ) . len ( ) as u32 + 1 ;
@@ -113,9 +179,8 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
113
179
return true ;
114
180
}
115
181
116
- assert ! ( !covspan. is_hole) ;
117
- extra_spans. push ( SpanFromMir :: new ( before, covspan. visible_macro , covspan. bcb , false ) ) ;
118
- extra_spans. push ( SpanFromMir :: new ( after, covspan. visible_macro , covspan. bcb , false ) ) ;
182
+ extra_spans. push ( SpanFromMir :: new ( before, covspan. visible_macro , covspan. bcb ) ) ;
183
+ extra_spans. push ( SpanFromMir :: new ( after, covspan. visible_macro , covspan. bcb ) ) ;
119
184
false // Discard the original covspan that we just split.
120
185
} ) ;
121
186
@@ -135,6 +200,7 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
135
200
bcb : BasicCoverageBlock ,
136
201
bcb_data : & ' a BasicCoverageBlockData ,
137
202
initial_covspans : & mut Vec < SpanFromMir > ,
203
+ holes : & mut Vec < Hole > ,
138
204
) {
139
205
for & bb in & bcb_data. basic_blocks {
140
206
let data = & mir_body[ bb] ;
@@ -146,26 +212,31 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
146
212
. filter ( |( span, _) | !span. source_equal ( body_span) )
147
213
} ;
148
214
215
+ let mut extract_statement_span = |statement| {
216
+ let expn_span = filtered_statement_span ( statement) ?;
217
+ let ( span, visible_macro) = unexpand ( expn_span) ?;
218
+
219
+ // A statement that looks like the assignment of a closure expression
220
+ // is treated as a "hole" span, to be carved out of other spans.
221
+ if is_closure_like ( statement) {
222
+ holes. push ( Hole { span } ) ;
223
+ } else {
224
+ initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb) ) ;
225
+ }
226
+ Some ( ( ) )
227
+ } ;
149
228
for statement in data. statements . iter ( ) {
150
- let _: Option < ( ) > = try {
151
- let expn_span = filtered_statement_span ( statement) ?;
152
- let ( span, visible_macro) = unexpand ( expn_span) ?;
153
-
154
- // A statement that looks like the assignment of a closure expression
155
- // is treated as a "hole" span, to be carved out of other spans.
156
- let covspan =
157
- SpanFromMir :: new ( span, visible_macro, bcb, is_closure_like ( statement) ) ;
158
- initial_covspans. push ( covspan) ;
159
- } ;
229
+ extract_statement_span ( statement) ;
160
230
}
161
231
162
- let _: Option < ( ) > = try {
163
- let terminator = data. terminator ( ) ;
232
+ let mut extract_terminator_span = |terminator| {
164
233
let expn_span = filtered_terminator_span ( terminator) ?;
165
234
let ( span, visible_macro) = unexpand ( expn_span) ?;
166
235
167
- initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb, false ) ) ;
236
+ initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb) ) ;
237
+ Some ( ( ) )
168
238
} ;
239
+ extract_terminator_span ( data. terminator ( ) ) ;
169
240
}
170
241
}
171
242
@@ -333,6 +404,22 @@ fn unexpand_into_body_span_with_prev(
333
404
Some ( ( curr, prev) )
334
405
}
335
406
407
+ #[ derive( Debug ) ]
408
+ struct Hole {
409
+ span : Span ,
410
+ }
411
+
412
+ impl Hole {
413
+ fn merge_if_overlapping_or_adjacent ( & mut self , other : & mut Self ) -> bool {
414
+ if !self . span . overlaps_or_adjacent ( other. span ) {
415
+ return false ;
416
+ }
417
+
418
+ self . span = self . span . to ( other. span ) ;
419
+ true
420
+ }
421
+ }
422
+
336
423
#[ derive( Debug ) ]
337
424
pub ( super ) struct SpanFromMir {
338
425
/// A span that has been extracted from MIR and then "un-expanded" back to
@@ -345,23 +432,30 @@ pub(super) struct SpanFromMir {
345
432
pub ( super ) span : Span ,
346
433
visible_macro : Option < Symbol > ,
347
434
pub ( super ) bcb : BasicCoverageBlock ,
348
- /// If true, this covspan represents a "hole" that should be carved out
349
- /// from other spans, e.g. because it represents a closure expression that
350
- /// will be instrumented separately as its own function.
351
- pub ( super ) is_hole : bool ,
352
435
}
353
436
354
437
impl SpanFromMir {
355
438
fn for_fn_sig ( fn_sig_span : Span ) -> Self {
356
- Self :: new ( fn_sig_span, None , START_BCB , false )
439
+ Self :: new ( fn_sig_span, None , START_BCB )
440
+ }
441
+
442
+ fn new ( span : Span , visible_macro : Option < Symbol > , bcb : BasicCoverageBlock ) -> Self {
443
+ Self { span, visible_macro, bcb }
357
444
}
358
445
359
- fn new (
360
- span : Span ,
361
- visible_macro : Option < Symbol > ,
362
- bcb : BasicCoverageBlock ,
363
- is_hole : bool ,
364
- ) -> Self {
365
- Self { span, visible_macro, bcb, is_hole }
446
+ /// Splits this span into 0-2 parts:
447
+ /// - The part that is strictly before the hole span, if any.
448
+ /// - The part that is strictly after the hole span, if any.
449
+ fn split_around_hole_span ( & self , hole_span : Span ) -> ( Option < Self > , Option < Self > ) {
450
+ let before = try {
451
+ let span = self . span . trim_end ( hole_span) ?;
452
+ Self { span, ..* self }
453
+ } ;
454
+ let after = try {
455
+ let span = self . span . trim_start ( hole_span) ?;
456
+ Self { span, ..* self }
457
+ } ;
458
+
459
+ ( before, after)
366
460
}
367
461
}
0 commit comments