1
+ use std:: collections:: VecDeque ;
2
+
1
3
use rustc_data_structures:: captures:: Captures ;
2
4
use rustc_data_structures:: fx:: FxHashSet ;
3
5
use rustc_middle:: bug;
@@ -17,23 +19,34 @@ use crate::coverage::ExtractedHirInfo;
17
19
/// spans, each associated with a node in the coverage graph (BCB) and possibly
18
20
/// other metadata.
19
21
///
20
- /// The returned spans are sorted in a specific order that is expected by the
21
- /// 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.
22
27
pub ( super ) fn mir_to_initial_sorted_coverage_spans (
23
28
mir_body : & mir:: Body < ' _ > ,
24
29
hir_info : & ExtractedHirInfo ,
25
30
basic_coverage_blocks : & CoverageGraph ,
26
- ) -> Vec < SpanFromMir > {
31
+ ) -> Vec < Vec < SpanFromMir > > {
27
32
let & ExtractedHirInfo { body_span, .. } = hir_info;
28
33
29
34
let mut initial_spans = vec ! [ ] ;
35
+ let mut holes = vec ! [ ] ;
30
36
31
37
for ( bcb, bcb_data) in basic_coverage_blocks. iter_enumerated ( ) {
32
- initial_spans. extend ( bcb_to_initial_coverage_spans ( mir_body, body_span, bcb, bcb_data) ) ;
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
+ ) ;
33
46
}
34
47
35
48
// Only add the signature span if we found at least one span in the body.
36
- if !initial_spans. is_empty ( ) {
49
+ if !initial_spans. is_empty ( ) || !holes . is_empty ( ) {
37
50
// If there is no usable signature span, add a fake one (before refinement)
38
51
// to avoid an ugly gap between the body start and the first real span.
39
52
// FIXME: Find a more principled way to solve this problem.
@@ -45,29 +58,82 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
45
58
remove_unwanted_macro_spans ( & mut initial_spans) ;
46
59
split_visible_macro_spans ( & mut initial_spans) ;
47
60
48
- initial_spans. sort_by ( |a, b| {
49
- // First sort by span start.
50
- Ord :: cmp ( & a. span . lo ( ) , & b. span . lo ( ) )
51
- // If span starts are the same, sort by span end in reverse order.
52
- // This ensures that if spans A and B are adjacent in the list,
53
- // and they overlap but are not equal, then either:
54
- // - Span A extends further left, or
55
- // - Both have the same start and span A extends further right
56
- . then_with ( || Ord :: cmp ( & a. span . hi ( ) , & b. span . hi ( ) ) . reverse ( ) )
57
- // If two spans have the same lo & hi, put hole spans first,
58
- // as they take precedence over non-hole spans.
59
- . 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 )
60
63
// After deduplication, we want to keep only the most-dominated BCB.
61
64
. then_with ( || basic_coverage_blocks. cmp_in_dominator_order ( a. bcb , b. bcb ) . reverse ( ) )
62
- } ) ;
65
+ } ;
66
+ initial_spans. sort_by ( compare_covspans) ;
63
67
64
- // Among covspans with the same span, keep only one. Hole spans take
65
- // 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.
66
70
// (Ideally we should try to preserve _all_ non-dominating BCBs, but that
67
71
// requires a lot more complexity in the span refiner, for little benefit.)
68
72
initial_spans. dedup_by ( |b, a| a. span . source_equal ( b. span ) ) ;
69
73
70
- 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 } )
71
137
}
72
138
73
139
/// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate
@@ -80,8 +146,8 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
80
146
fn remove_unwanted_macro_spans ( initial_spans : & mut Vec < SpanFromMir > ) {
81
147
let mut seen_macro_spans = FxHashSet :: default ( ) ;
82
148
initial_spans. retain ( |covspan| {
83
- // Ignore (retain) hole spans and non-macro-expansion spans.
84
- if covspan. is_hole || covspan . visible_macro . is_none ( ) {
149
+ // Ignore (retain) non-macro-expansion spans.
150
+ if covspan. visible_macro . is_none ( ) {
85
151
return true ;
86
152
}
87
153
@@ -98,10 +164,6 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
98
164
let mut extra_spans = vec ! [ ] ;
99
165
100
166
initial_spans. retain ( |covspan| {
101
- if covspan. is_hole {
102
- return true ;
103
- }
104
-
105
167
let Some ( visible_macro) = covspan. visible_macro else { return true } ;
106
168
107
169
let split_len = visible_macro. as_str ( ) . len ( ) as u32 + 1 ;
@@ -114,9 +176,8 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
114
176
return true ;
115
177
}
116
178
117
- assert ! ( !covspan. is_hole) ;
118
- extra_spans. push ( SpanFromMir :: new ( before, covspan. visible_macro , covspan. bcb , false ) ) ;
119
- 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 ) ) ;
120
181
false // Discard the original covspan that we just split.
121
182
} ) ;
122
183
@@ -135,8 +196,10 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
135
196
body_span : Span ,
136
197
bcb : BasicCoverageBlock ,
137
198
bcb_data : & ' a BasicCoverageBlockData ,
138
- ) -> impl Iterator < Item = SpanFromMir > + Captures < ' a > + Captures < ' tcx > {
139
- bcb_data. basic_blocks . iter ( ) . flat_map ( move |& bb| {
199
+ initial_covspans : & mut Vec < SpanFromMir > ,
200
+ holes : & mut Vec < Hole > ,
201
+ ) {
202
+ for & bb in & bcb_data. basic_blocks {
140
203
let data = & mir_body[ bb] ;
141
204
142
205
let unexpand = move |expn_span| {
@@ -146,24 +209,32 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
146
209
. filter ( |( span, _) | !span. source_equal ( body_span) )
147
210
} ;
148
211
149
- let statement_spans = data . statements . iter ( ) . filter_map ( move |statement| {
212
+ let mut extract_statement_span = |statement| {
150
213
let expn_span = filtered_statement_span ( statement) ?;
151
214
let ( span, visible_macro) = unexpand ( expn_span) ?;
152
215
153
216
// A statement that looks like the assignment of a closure expression
154
217
// is treated as a "hole" span, to be carved out of other spans.
155
- Some ( SpanFromMir :: new ( span, visible_macro, bcb, is_closure_like ( statement) ) )
156
- } ) ;
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
+ } ;
225
+ for statement in data. statements . iter ( ) {
226
+ extract_statement_span ( statement) ;
227
+ }
157
228
158
- let terminator_span = Some ( data . terminator ( ) ) . into_iter ( ) . filter_map ( move |terminator| {
229
+ let mut extract_terminator_span = |terminator| {
159
230
let expn_span = filtered_terminator_span ( terminator) ?;
160
231
let ( span, visible_macro) = unexpand ( expn_span) ?;
161
232
162
- Some ( SpanFromMir :: new ( span, visible_macro, bcb, false ) )
163
- } ) ;
164
-
165
- statement_spans . chain ( terminator_span )
166
- } )
233
+ initial_covspans . push ( SpanFromMir :: new ( span, visible_macro, bcb) ) ;
234
+ Some ( ( ) )
235
+ } ;
236
+ extract_terminator_span ( data . terminator ( ) ) ;
237
+ }
167
238
}
168
239
169
240
fn is_closure_like ( statement : & Statement < ' _ > ) -> bool {
@@ -330,6 +401,22 @@ fn unexpand_into_body_span_with_prev(
330
401
Some ( ( curr, prev) )
331
402
}
332
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
+
333
420
#[ derive( Debug ) ]
334
421
pub ( super ) struct SpanFromMir {
335
422
/// A span that has been extracted from MIR and then "un-expanded" back to
@@ -342,23 +429,30 @@ pub(super) struct SpanFromMir {
342
429
pub ( super ) span : Span ,
343
430
visible_macro : Option < Symbol > ,
344
431
pub ( super ) bcb : BasicCoverageBlock ,
345
- /// If true, this covspan represents a "hole" that should be carved out
346
- /// from other spans, e.g. because it represents a closure expression that
347
- /// will be instrumented separately as its own function.
348
- pub ( super ) is_hole : bool ,
349
432
}
350
433
351
434
impl SpanFromMir {
352
435
fn for_fn_sig ( fn_sig_span : Span ) -> Self {
353
- 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 }
354
441
}
355
442
356
- fn new (
357
- span : Span ,
358
- visible_macro : Option < Symbol > ,
359
- bcb : BasicCoverageBlock ,
360
- is_hole : bool ,
361
- ) -> Self {
362
- 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)
363
457
}
364
458
}
0 commit comments