1
1
use std:: collections:: VecDeque ;
2
2
3
3
use rustc_data_structures:: fx:: FxIndexMap ;
4
+ use rustc_index:: IndexVec ;
4
5
use rustc_middle:: bug;
5
6
use rustc_middle:: mir:: coverage:: {
6
7
BlockMarkerId , ConditionId , ConditionInfo , DecisionId , MCDCBranchMarkers , MCDCBranchSpan ,
@@ -14,10 +15,9 @@ use rustc_span::Span;
14
15
use crate :: build:: Builder ;
15
16
use crate :: errors:: { MCDCExceedsConditionLimit , MCDCExceedsDecisionDepth } ;
16
17
17
- /// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
18
- /// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
19
- /// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
20
- const MAX_CONDITIONS_IN_DECISION : usize = 6 ;
18
+ /// LLVM uses `i16` to represent condition id. Hence `i16::MAX` is the hard limit for number of
19
+ /// conditions in a decision.
20
+ const MAX_CONDITIONS_IN_DECISION : usize = i16:: MAX as usize ;
21
21
22
22
/// MCDC allocates an i32 variable on stack for each depth. Ignore decisions nested too much to prevent it
23
23
/// consuming excessive memory.
@@ -41,6 +41,7 @@ impl BooleanDecisionCtx {
41
41
span : Span :: default ( ) ,
42
42
end_markers : vec ! [ ] ,
43
43
decision_depth : 0 ,
44
+ num_test_vectors : 0 ,
44
45
} ,
45
46
decision_stack : VecDeque :: new ( ) ,
46
47
conditions : vec ! [ ] ,
@@ -158,11 +159,11 @@ impl BooleanDecisionCtx {
158
159
self . decision_info . end_markers . push ( false_marker) ;
159
160
}
160
161
161
- self . conditions . push ( MCDCBranchSpan {
162
+ self . conditions . push ( MCDCBranchSpan :: new (
162
163
span,
163
164
condition_info,
164
- markers : MCDCBranchMarkers :: Boolean ( true_marker, false_marker) ,
165
- } ) ;
165
+ MCDCBranchMarkers :: Boolean ( true_marker, false_marker) ,
166
+ ) ) ;
166
167
}
167
168
168
169
fn is_finished ( & self ) -> bool {
@@ -257,9 +258,86 @@ struct MCDCTargetInfo {
257
258
}
258
259
259
260
impl MCDCTargetInfo {
261
+ fn new ( decision : MCDCDecisionSpan , conditions : Vec < MCDCBranchSpan > ) -> Self {
262
+ let mut this = Self { decision, conditions, nested_decisions_id : vec ! [ ] } ;
263
+ this. calc_test_vectors_index ( ) ;
264
+ this
265
+ }
266
+
260
267
fn set_depth ( & mut self , depth : u16 ) {
261
268
self . decision . decision_depth = depth;
262
269
}
270
+
271
+ // LLVM checks the executed test vector by accumulate indices of tested branches.
272
+ // We calculate number of all possible test vectors of the decision and assign indices
273
+ // for each branch here.
274
+ // See https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798/ for
275
+ // more details of the algorithm.
276
+ // The process of this function is mostly like `TVIdxBuilder` at
277
+ // https://github.com/llvm/llvm-project/blob/d594d9f7f4dc6eb748b3261917db689fdc348b96/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L226
278
+ fn calc_test_vectors_index ( & mut self ) {
279
+ let Self { decision, conditions, .. } = self ;
280
+ let mut indegree_stats = IndexVec :: < ConditionId , usize > :: from_elem_n ( 0 , conditions. len ( ) ) ;
281
+ // `num_paths` is `width` described at the llvm RFC, which indicates how many paths reaching the condition.
282
+ let mut num_paths_stats = IndexVec :: < ConditionId , usize > :: from_elem_n ( 0 , conditions. len ( ) ) ;
283
+ let mut next_conditions = conditions
284
+ . iter_mut ( )
285
+ . map ( |branch| {
286
+ let ConditionInfo { condition_id, true_next_id, false_next_id } =
287
+ branch. condition_info ;
288
+ [ true_next_id, false_next_id]
289
+ . into_iter ( )
290
+ . filter_map ( std:: convert:: identity)
291
+ . for_each ( |next_id| indegree_stats[ next_id] += 1 ) ;
292
+ ( condition_id, branch)
293
+ } )
294
+ . collect :: < FxIndexMap < _ , _ > > ( ) ;
295
+
296
+ let mut queue =
297
+ VecDeque :: from_iter ( next_conditions. swap_remove ( & ConditionId :: START ) . into_iter ( ) ) ;
298
+ num_paths_stats[ ConditionId :: START ] = 1 ;
299
+ let mut decision_end_nodes = Vec :: new ( ) ;
300
+ while let Some ( branch) = queue. pop_front ( ) {
301
+ let MCDCBranchSpan {
302
+ span : _,
303
+ condition_info : ConditionInfo { condition_id, true_next_id, false_next_id } ,
304
+ markers : _,
305
+ false_index,
306
+ true_index,
307
+ } = branch;
308
+ let this_paths_count = num_paths_stats[ * condition_id] ;
309
+ for ( next, index) in [ ( false_next_id, false_index) , ( true_next_id, true_index) ] {
310
+ if let Some ( next_id) = next {
311
+ let next_paths_count = & mut num_paths_stats[ * next_id] ;
312
+ * index = * next_paths_count;
313
+ * next_paths_count = next_paths_count. saturating_add ( this_paths_count) ;
314
+ let next_indegree = & mut indegree_stats[ * next_id] ;
315
+ * next_indegree -= 1 ;
316
+ if * next_indegree == 0 {
317
+ queue. push_back ( next_conditions. swap_remove ( next_id) . expect (
318
+ "conditions with non-zero indegree before must be in next_conditions" ,
319
+ ) ) ;
320
+ }
321
+ } else {
322
+ decision_end_nodes. push ( ( this_paths_count, * condition_id, index) ) ;
323
+ }
324
+ }
325
+ }
326
+ assert ! ( next_conditions. is_empty( ) , "the decision tree has untouched nodes" ) ;
327
+ let mut cur_idx = 0 ;
328
+ // LLVM hopes the end nodes is sorted in ascending order by `num_paths`.
329
+ decision_end_nodes. sort_by_key ( |( num_paths, _, _) | usize:: MAX - * num_paths) ;
330
+ for ( num_paths, condition_id, index) in decision_end_nodes {
331
+ assert_eq ! (
332
+ num_paths, num_paths_stats[ condition_id] ,
333
+ "end nodes should not be updated since they were visited"
334
+ ) ;
335
+ assert_eq ! ( * index, usize :: MAX , "end nodes should not be assigned index before" ) ;
336
+ * index = cur_idx;
337
+ cur_idx += num_paths;
338
+ }
339
+ decision. num_test_vectors = cur_idx;
340
+ }
263
341
}
264
342
265
343
#[ derive( Default ) ]
@@ -323,7 +401,7 @@ impl MCDCInfoBuilder {
323
401
}
324
402
// Ignore decisions with only one condition given that mcdc for them is completely equivalent to branch coverage.
325
403
2 ..=MAX_CONDITIONS_IN_DECISION => {
326
- let info = MCDCTargetInfo { decision, conditions, nested_decisions_id : vec ! [ ] } ;
404
+ let info = MCDCTargetInfo :: new ( decision, conditions) ;
327
405
Some ( self . mcdc_targets . entry ( id) . or_insert ( info) )
328
406
}
329
407
_ => {
0 commit comments