1+ use std:: collections:: VecDeque ;
2+
3+ use rustc_data_structures:: captures:: Captures ;
14use rustc_data_structures:: fx:: FxHashSet ;
25use rustc_middle:: bug;
36use rustc_middle:: mir:: coverage:: CoverageKind ;
@@ -16,8 +19,11 @@ use crate::coverage::ExtractedHirInfo;
1619/// spans, each associated with a node in the coverage graph (BCB) and possibly
1720/// other metadata.
1821///
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.
2127pub ( super ) fn mir_to_initial_sorted_coverage_spans (
2228 mir_body : & mir:: Body < ' _ > ,
2329 hir_info : & ExtractedHirInfo ,
@@ -26,13 +32,21 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
2632 let & ExtractedHirInfo { body_span, .. } = hir_info;
2733
2834 let mut initial_spans = vec ! [ ] ;
35+ let mut holes = vec ! [ ] ;
2936
3037 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+ ) ;
3246 }
3347
3448 // 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 ( ) {
3650 // If there is no usable signature span, add a fake one (before refinement)
3751 // to avoid an ugly gap between the body start and the first real span.
3852 // FIXME: Find a more principled way to solve this problem.
@@ -44,29 +58,85 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
4458 remove_unwanted_macro_spans ( & mut initial_spans) ;
4559 split_visible_macro_spans ( & mut initial_spans) ;
4660
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 )
5963 // After deduplication, we want to keep only the most-dominated BCB.
6064 . then_with ( || basic_coverage_blocks. cmp_in_dominator_order ( a. bcb , b. bcb ) . reverse ( ) )
61- } ) ;
65+ } ;
66+ initial_spans. sort_by ( compare_covspans) ;
6267
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.
6570 // (Ideally we should try to preserve _all_ non-dominating BCBs, but that
6671 // requires a lot more complexity in the span refiner, for little benefit.)
6772 initial_spans. dedup_by ( |b, a| a. span . source_equal ( b. span ) ) ;
6873
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 } )
70140}
71141
72142/// 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(
79149fn remove_unwanted_macro_spans ( initial_spans : & mut Vec < SpanFromMir > ) {
80150 let mut seen_macro_spans = FxHashSet :: default ( ) ;
81151 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 ( ) {
84154 return true ;
85155 }
86156
@@ -97,10 +167,6 @@ fn split_visible_macro_spans(initial_spans: &mut Vec<SpanFromMir>) {
97167 let mut extra_spans = vec ! [ ] ;
98168
99169 initial_spans. retain ( |covspan| {
100- if covspan. is_hole {
101- return true ;
102- }
103-
104170 let Some ( visible_macro) = covspan. visible_macro else { return true } ;
105171
106172 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>) {
113179 return true ;
114180 }
115181
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 ) ) ;
119184 false // Discard the original covspan that we just split.
120185 } ) ;
121186
@@ -135,6 +200,7 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
135200 bcb : BasicCoverageBlock ,
136201 bcb_data : & ' a BasicCoverageBlockData ,
137202 initial_covspans : & mut Vec < SpanFromMir > ,
203+ holes : & mut Vec < Hole > ,
138204) {
139205 for & bb in & bcb_data. basic_blocks {
140206 let data = & mir_body[ bb] ;
@@ -146,26 +212,31 @@ fn bcb_to_initial_coverage_spans<'a, 'tcx>(
146212 . filter ( |( span, _) | !span. source_equal ( body_span) )
147213 } ;
148214
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+ } ;
149228 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) ;
160230 }
161231
162- let _: Option < ( ) > = try {
163- let terminator = data. terminator ( ) ;
232+ let mut extract_terminator_span = |terminator| {
164233 let expn_span = filtered_terminator_span ( terminator) ?;
165234 let ( span, visible_macro) = unexpand ( expn_span) ?;
166235
167- initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb, false ) ) ;
236+ initial_covspans. push ( SpanFromMir :: new ( span, visible_macro, bcb) ) ;
237+ Some ( ( ) )
168238 } ;
239+ extract_terminator_span ( data. terminator ( ) ) ;
169240 }
170241}
171242
@@ -333,6 +404,22 @@ fn unexpand_into_body_span_with_prev(
333404 Some ( ( curr, prev) )
334405}
335406
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+
336423#[ derive( Debug ) ]
337424pub ( super ) struct SpanFromMir {
338425 /// A span that has been extracted from MIR and then "un-expanded" back to
@@ -345,23 +432,30 @@ pub(super) struct SpanFromMir {
345432 pub ( super ) span : Span ,
346433 visible_macro : Option < Symbol > ,
347434 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 ,
352435}
353436
354437impl SpanFromMir {
355438 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 }
357444 }
358445
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)
366460 }
367461}
0 commit comments