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,82 @@ 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: 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 fragments_from_prev = std:: mem:: take ( & mut fragments) ;
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. into_iter ( ) . chain ( relevant_initial_spans) {
100
+ let ( before, after) = covspan. split_around_hole_span ( hole. span ) ;
101
+ bucket. extend ( before) ;
102
+ fragments. extend ( after) ;
103
+ }
104
+ }
105
+
106
+ // After finding the spans before each hole, any remaining fragments/spans
107
+ // form their own final bucket, after the final hole.
108
+ // (If there were no holes, this will just be all of the initial spans.)
109
+ fragments. extend ( initial_spans) ;
110
+ buckets. push ( fragments) ;
111
+
112
+ // Make sure each individual bucket is still internally sorted.
113
+ for bucket in & mut buckets {
114
+ bucket. sort_by ( compare_covspans) ;
115
+ }
116
+ buckets
117
+ }
118
+
119
+ fn compare_spans ( a : Span , b : Span ) -> std:: cmp:: Ordering {
120
+ // First sort by span start.
121
+ Ord :: cmp ( & a. lo ( ) , & b. lo ( ) )
122
+ // If span starts are the same, sort by span end in reverse order.
123
+ // This ensures that if spans A and B are adjacent in the list,
124
+ // and they overlap but are not equal, then either:
125
+ // - Span A extends further left, or
126
+ // - Both have the same start and span A extends further right
127
+ . then_with ( || Ord :: cmp ( & a. hi ( ) , & b. hi ( ) ) . reverse ( ) )
128
+ }
129
+
130
+ /// Similar to `.drain(..)`, but stops just before it would remove an item not
131
+ /// satisfying the predicate.
132
+ fn drain_front_while < ' a , T > (
133
+ queue : & ' a mut VecDeque < T > ,
134
+ mut pred_fn : impl FnMut ( & T ) -> bool ,
135
+ ) -> impl Iterator < Item = T > + Captures < ' a > {
136
+ std:: iter:: from_fn ( move || if pred_fn ( queue. front ( ) ?) { queue. pop_front ( ) } else { None } )
70
137
}
71
138
72
139
/// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate
@@ -79,8 +146,8 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
79
146
fn remove_unwanted_macro_spans ( initial_spans : & mut Vec < SpanFromMir > ) {
80
147
let mut seen_macro_spans = FxHashSet :: default ( ) ;
81
148
initial_spans. retain ( |covspan| {
82
- // Ignore (retain) hole spans and non-macro-expansion spans.
83
- if covspan. is_hole || covspan . visible_macro . is_none ( ) {
149
+ // Ignore (retain) non-macro-expansion spans.
150
+ if covspan. visible_macro . is_none ( ) {
84
151
return true ;
85
152
}
86
153
@@ -97,10 +164,6 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
97
164
let mut extra_spans = vec ! [ ] ;
98
165
99
166
initial_spans. retain ( |covspan| {
100
- if covspan. is_hole {
101
- return true ;
102
- }
103
-
104
167
let Some ( visible_macro) = covspan. visible_macro else { return true } ;
105
168
106
169
let split_len = visible_macro. as_str ( ) . len ( ) as u32 + 1 ;
@@ -113,9 +176,8 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
113
176
return true ;
114
177
}
115
178
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 ) ) ;
179
+ extra_spans. push ( SpanFromMir :: new ( before, covspan. visible_macro , covspan. bcb ) ) ;
180
+ extra_spans. push ( SpanFromMir :: new ( after, covspan. visible_macro , covspan. bcb ) ) ;
119
181
false // Discard the original covspan that we just split.
120
182
} ) ;
121
183
@@ -135,6 +197,7 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
135
197
bcb : BasicCoverageBlock ,
136
198
bcb_data : & ' a BasicCoverageBlockData ,
137
199
initial_covspans : & mut Vec < SpanFromMir > ,
200
+ holes : & mut Vec < Hole > ,
138
201
) {
139
202
for & bb in & bcb_data. basic_blocks {
140
203
let data = & mir_body[ bb] ;
@@ -146,26 +209,31 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
146
209
. filter ( |( span, _) | !span. source_equal ( body_span) )
147
210
} ;
148
211
212
+ let mut extract_statement_span = |statement| {
213
+ let expn_span = filtered_statement_span ( statement) ?;
214
+ let ( span, visible_macro) = unexpand ( expn_span) ?;
215
+
216
+ // A statement that looks like the assignment of a closure expression
217
+ // is treated as a "hole" span, to be carved out of other spans.
218
+ if is_closure_like ( statement) {
219
+ holes. push ( Hole { span } ) ;
220
+ } else {
221
+ initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb) ) ;
222
+ }
223
+ Some ( ( ) )
224
+ } ;
149
225
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
- } ;
226
+ extract_statement_span ( statement) ;
160
227
}
161
228
162
- let _: Option < ( ) > = try {
163
- let terminator = data. terminator ( ) ;
229
+ let mut extract_terminator_span = |terminator| {
164
230
let expn_span = filtered_terminator_span ( terminator) ?;
165
231
let ( span, visible_macro) = unexpand ( expn_span) ?;
166
232
167
- initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb, false ) ) ;
233
+ initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb) ) ;
234
+ Some ( ( ) )
168
235
} ;
236
+ extract_terminator_span ( data. terminator ( ) ) ;
169
237
}
170
238
}
171
239
@@ -333,6 +401,22 @@ fn unexpand_into_body_span_with_prev(
333
401
Some ( ( curr, prev) )
334
402
}
335
403
404
+ #[ derive( Debug ) ]
405
+ struct Hole {
406
+ span : Span ,
407
+ }
408
+
409
+ impl Hole {
410
+ fn merge_if_overlapping_or_adjacent ( & mut self , other : & mut Self ) -> bool {
411
+ if !self . span . overlaps_or_adjacent ( other. span ) {
412
+ return false ;
413
+ }
414
+
415
+ self . span = self . span . to ( other. span ) ;
416
+ true
417
+ }
418
+ }
419
+
336
420
#[ derive( Debug ) ]
337
421
pub ( super ) struct SpanFromMir {
338
422
/// A span that has been extracted from MIR and then "un-expanded" back to
@@ -345,23 +429,30 @@ pub(super) struct SpanFromMir {
345
429
pub ( super ) span : Span ,
346
430
visible_macro : Option < Symbol > ,
347
431
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
432
}
353
433
354
434
impl SpanFromMir {
355
435
fn for_fn_sig ( fn_sig_span : Span ) -> Self {
356
- Self :: new ( fn_sig_span, None , START_BCB , false )
436
+ Self :: new ( fn_sig_span, None , START_BCB )
437
+ }
438
+
439
+ fn new ( span : Span , visible_macro : Option < Symbol > , bcb : BasicCoverageBlock ) -> Self {
440
+ Self { span, visible_macro, bcb }
357
441
}
358
442
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 }
443
+ /// Splits this span into 0-2 parts:
444
+ /// - The part that is strictly before the hole span, if any.
445
+ /// - The part that is strictly after the hole span, if any.
446
+ fn split_around_hole_span ( & self , hole_span : Span ) -> ( Option < Self > , Option < Self > ) {
447
+ let before = try {
448
+ let span = self . span . trim_end ( hole_span) ?;
449
+ Self { span, ..* self }
450
+ } ;
451
+ let after = try {
452
+ let span = self . span . trim_start ( hole_span) ?;
453
+ Self { span, ..* self }
454
+ } ;
455
+
456
+ ( before, after)
366
457
}
367
458
}
0 commit comments