1
+ mod mcdc;
2
+ use mcdc:: MCDCInfoBuilder ;
1
3
use std:: assert_matches:: assert_matches;
2
4
use std:: collections:: hash_map:: Entry ;
3
- use std:: collections:: VecDeque ;
4
5
5
6
use rustc_data_structures:: fx:: FxHashMap ;
6
- use rustc_middle:: mir:: coverage:: {
7
- BlockMarkerId , BranchSpan , ConditionId , ConditionInfo , CoverageKind , MCDCBranchSpan ,
8
- MCDCDecisionSpan ,
9
- } ;
7
+ use rustc_middle:: mir:: coverage:: { BlockMarkerId , BranchSpan , CoverageKind } ;
10
8
use rustc_middle:: mir:: { self , BasicBlock , SourceInfo , UnOp } ;
11
- use rustc_middle:: thir:: { ExprId , ExprKind , LogicalOp , Thir } ;
9
+ use rustc_middle:: thir:: { ExprId , ExprKind , Thir } ;
12
10
use rustc_middle:: ty:: TyCtxt ;
13
11
use rustc_span:: def_id:: LocalDefId ;
14
- use rustc_span:: Span ;
15
12
16
13
use crate :: build:: { Builder , CFG } ;
17
- use crate :: errors:: MCDCExceedsConditionNumLimit ;
18
14
19
15
pub ( crate ) struct BranchInfoBuilder {
20
16
/// Maps condition expressions to their enclosing `!`, for better instrumentation.
21
17
nots : FxHashMap < ExprId , NotInfo > ,
22
-
23
- num_block_markers : usize ,
18
+ block_marker_generator : BlockMarkerGenerator ,
24
19
branch_spans : Vec < BranchSpan > ,
25
20
26
- mcdc_branch_spans : Vec < MCDCBranchSpan > ,
27
- mcdc_decision_spans : Vec < MCDCDecisionSpan > ,
28
- mcdc_state : Option < MCDCState > ,
21
+ mcdc_builder : Option < MCDCInfoBuilder > ,
29
22
}
30
23
31
24
#[ derive( Clone , Copy ) ]
@@ -38,18 +31,46 @@ struct NotInfo {
38
31
is_flipped : bool ,
39
32
}
40
33
34
+ #[ derive( Default ) ]
35
+ struct BlockMarkerGenerator {
36
+ num_block_markers : usize ,
37
+ }
38
+
39
+ impl BlockMarkerGenerator {
40
+ fn next_block_marker_id ( & mut self ) -> BlockMarkerId {
41
+ let id = BlockMarkerId :: from_usize ( self . num_block_markers ) ;
42
+ self . num_block_markers += 1 ;
43
+ id
44
+ }
45
+
46
+ fn inject_block_marker (
47
+ & mut self ,
48
+ cfg : & mut CFG < ' _ > ,
49
+ source_info : SourceInfo ,
50
+ block : BasicBlock ,
51
+ ) -> BlockMarkerId {
52
+ let id = self . next_block_marker_id ( ) ;
53
+
54
+ let marker_statement = mir:: Statement {
55
+ source_info,
56
+ kind : mir:: StatementKind :: Coverage ( CoverageKind :: BlockMarker { id } ) ,
57
+ } ;
58
+ cfg. push ( block, marker_statement) ;
59
+
60
+ id
61
+ }
62
+ }
63
+
41
64
impl BranchInfoBuilder {
42
65
/// Creates a new branch info builder, but only if branch coverage instrumentation
43
66
/// is enabled and `def_id` represents a function that is eligible for coverage.
44
67
pub ( crate ) fn new_if_enabled ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> Option < Self > {
45
68
if tcx. sess . instrument_coverage_branch ( ) && tcx. is_eligible_for_coverage ( def_id) {
46
69
Some ( Self {
47
70
nots : FxHashMap :: default ( ) ,
48
- num_block_markers : 0 ,
71
+ block_marker_generator : BlockMarkerGenerator :: default ( ) ,
49
72
branch_spans : vec ! [ ] ,
50
- mcdc_branch_spans : vec ! [ ] ,
51
- mcdc_decision_spans : vec ! [ ] ,
52
- mcdc_state : MCDCState :: new_if_enabled ( tcx) ,
73
+ mcdc_builder : tcx. sess . instrument_coverage_mcdc ( ) . then ( MCDCInfoBuilder :: new) ,
53
74
} )
54
75
} else {
55
76
None
@@ -96,245 +117,46 @@ impl BranchInfoBuilder {
96
117
}
97
118
}
98
119
99
- fn fetch_mcdc_condition_info (
100
- & mut self ,
101
- tcx : TyCtxt < ' _ > ,
102
- true_marker : BlockMarkerId ,
103
- false_marker : BlockMarkerId ,
104
- ) -> Option < ConditionInfo > {
105
- let mcdc_state = self . mcdc_state . as_mut ( ) ?;
106
- let ( mut condition_info, decision_result) =
107
- mcdc_state. take_condition ( true_marker, false_marker) ;
108
- if let Some ( decision) = decision_result {
109
- match decision. conditions_num {
110
- 0 => {
111
- unreachable ! ( "Decision with no condition is not expected" ) ;
112
- }
113
- 1 ..=MAX_CONDITIONS_NUM_IN_DECISION => {
114
- self . mcdc_decision_spans . push ( decision) ;
115
- }
116
- _ => {
117
- // Do not generate mcdc mappings and statements for decisions with too many conditions.
118
- let rebase_idx = self . mcdc_branch_spans . len ( ) - decision. conditions_num + 1 ;
119
- for branch in & mut self . mcdc_branch_spans [ rebase_idx..] {
120
- branch. condition_info = None ;
121
- }
122
-
123
- // ConditionInfo of this branch shall also be reset.
124
- condition_info = None ;
125
-
126
- tcx. dcx ( ) . emit_warn ( MCDCExceedsConditionNumLimit {
127
- span : decision. span ,
128
- conditions_num : decision. conditions_num ,
129
- max_conditions_num : MAX_CONDITIONS_NUM_IN_DECISION ,
130
- } ) ;
131
- }
132
- }
133
- }
134
- condition_info
135
- }
136
-
137
120
fn add_two_way_branch < ' tcx > (
138
121
& mut self ,
139
122
cfg : & mut CFG < ' tcx > ,
140
123
source_info : SourceInfo ,
141
124
true_block : BasicBlock ,
142
125
false_block : BasicBlock ,
143
126
) {
144
- let true_marker = self . inject_block_marker ( cfg, source_info, true_block) ;
145
- let false_marker = self . inject_block_marker ( cfg, source_info, false_block) ;
127
+ let true_marker =
128
+ self . block_marker_generator . inject_block_marker ( cfg, source_info, true_block) ;
129
+ let false_marker =
130
+ self . block_marker_generator . inject_block_marker ( cfg, source_info, false_block) ;
146
131
147
132
self . branch_spans . push ( BranchSpan { span : source_info. span , true_marker, false_marker } ) ;
148
133
}
149
134
150
- fn next_block_marker_id ( & mut self ) -> BlockMarkerId {
151
- let id = BlockMarkerId :: from_usize ( self . num_block_markers ) ;
152
- self . num_block_markers += 1 ;
153
- id
154
- }
155
-
156
- fn inject_block_marker (
157
- & mut self ,
158
- cfg : & mut CFG < ' _ > ,
159
- source_info : SourceInfo ,
160
- block : BasicBlock ,
161
- ) -> BlockMarkerId {
162
- let id = self . next_block_marker_id ( ) ;
163
-
164
- let marker_statement = mir:: Statement {
165
- source_info,
166
- kind : mir:: StatementKind :: Coverage ( CoverageKind :: BlockMarker { id } ) ,
167
- } ;
168
- cfg. push ( block, marker_statement) ;
169
-
170
- id
171
- }
172
-
173
135
pub ( crate ) fn into_done ( self ) -> Option < Box < mir:: coverage:: BranchInfo > > {
174
136
let Self {
175
137
nots : _,
176
- num_block_markers,
138
+ block_marker_generator : BlockMarkerGenerator { num_block_markers } ,
177
139
branch_spans,
178
- mcdc_branch_spans,
179
- mcdc_decision_spans,
180
- mcdc_state : _,
140
+ mcdc_builder,
181
141
} = self ;
182
142
183
143
if num_block_markers == 0 {
184
144
assert ! ( branch_spans. is_empty( ) ) ;
185
145
return None ;
186
146
}
187
147
148
+ let ( mcdc_decision_spans, mcdc_branch_spans) =
149
+ mcdc_builder. map ( MCDCInfoBuilder :: into_done) . unwrap_or_default ( ) ;
150
+
188
151
Some ( Box :: new ( mir:: coverage:: BranchInfo {
189
152
num_block_markers,
190
153
branch_spans,
191
- mcdc_branch_spans,
192
154
mcdc_decision_spans,
155
+ mcdc_branch_spans,
193
156
} ) )
194
157
}
195
158
}
196
159
197
- /// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,
198
- /// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.
199
- /// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
200
- const MAX_CONDITIONS_NUM_IN_DECISION : usize = 6 ;
201
-
202
- struct MCDCState {
203
- /// To construct condition evaluation tree.
204
- decision_stack : VecDeque < ConditionInfo > ,
205
- processing_decision : Option < MCDCDecisionSpan > ,
206
- }
207
-
208
- impl MCDCState {
209
- fn new_if_enabled ( tcx : TyCtxt < ' _ > ) -> Option < Self > {
210
- tcx. sess
211
- . instrument_coverage_mcdc ( )
212
- . then ( || Self { decision_stack : VecDeque :: new ( ) , processing_decision : None } )
213
- }
214
-
215
- // At first we assign ConditionIds for each sub expression.
216
- // If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS.
217
- //
218
- // Example: "x = (A && B) || (C && D) || (D && F)"
219
- //
220
- // Visit Depth1:
221
- // (A && B) || (C && D) || (D && F)
222
- // ^-------LHS--------^ ^-RHS--^
223
- // ID=1 ID=2
224
- //
225
- // Visit LHS-Depth2:
226
- // (A && B) || (C && D)
227
- // ^-LHS--^ ^-RHS--^
228
- // ID=1 ID=3
229
- //
230
- // Visit LHS-Depth3:
231
- // (A && B)
232
- // LHS RHS
233
- // ID=1 ID=4
234
- //
235
- // Visit RHS-Depth3:
236
- // (C && D)
237
- // LHS RHS
238
- // ID=3 ID=5
239
- //
240
- // Visit RHS-Depth2: (D && F)
241
- // LHS RHS
242
- // ID=2 ID=6
243
- //
244
- // Visit Depth1:
245
- // (A && B) || (C && D) || (D && F)
246
- // ID=1 ID=4 ID=3 ID=5 ID=2 ID=6
247
- //
248
- // A node ID of '0' always means MC/DC isn't being tracked.
249
- //
250
- // If a "next" node ID is '0', it means it's the end of the test vector.
251
- //
252
- // As the compiler tracks expression in pre-order, we can ensure that condition info of parents are always properly assigned when their children are visited.
253
- // - If the op is AND, the "false_next" of LHS and RHS should be the parent's "false_next". While "true_next" of the LHS is the RHS, the "true next" of RHS is the parent's "true_next".
254
- // - If the op is OR, the "true_next" of LHS and RHS should be the parent's "true_next". While "false_next" of the LHS is the RHS, the "false next" of RHS is the parent's "false_next".
255
- fn record_conditions ( & mut self , op : LogicalOp , span : Span ) {
256
- let decision = match self . processing_decision . as_mut ( ) {
257
- Some ( decision) => {
258
- decision. span = decision. span . to ( span) ;
259
- decision
260
- }
261
- None => self . processing_decision . insert ( MCDCDecisionSpan {
262
- span,
263
- conditions_num : 0 ,
264
- end_markers : vec ! [ ] ,
265
- } ) ,
266
- } ;
267
-
268
- let parent_condition = self . decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
269
- let lhs_id = if parent_condition. condition_id == ConditionId :: NONE {
270
- decision. conditions_num += 1 ;
271
- ConditionId :: from ( decision. conditions_num )
272
- } else {
273
- parent_condition. condition_id
274
- } ;
275
-
276
- decision. conditions_num += 1 ;
277
- let rhs_condition_id = ConditionId :: from ( decision. conditions_num ) ;
278
-
279
- let ( lhs, rhs) = match op {
280
- LogicalOp :: And => {
281
- let lhs = ConditionInfo {
282
- condition_id : lhs_id,
283
- true_next_id : rhs_condition_id,
284
- false_next_id : parent_condition. false_next_id ,
285
- } ;
286
- let rhs = ConditionInfo {
287
- condition_id : rhs_condition_id,
288
- true_next_id : parent_condition. true_next_id ,
289
- false_next_id : parent_condition. false_next_id ,
290
- } ;
291
- ( lhs, rhs)
292
- }
293
- LogicalOp :: Or => {
294
- let lhs = ConditionInfo {
295
- condition_id : lhs_id,
296
- true_next_id : parent_condition. true_next_id ,
297
- false_next_id : rhs_condition_id,
298
- } ;
299
- let rhs = ConditionInfo {
300
- condition_id : rhs_condition_id,
301
- true_next_id : parent_condition. true_next_id ,
302
- false_next_id : parent_condition. false_next_id ,
303
- } ;
304
- ( lhs, rhs)
305
- }
306
- } ;
307
- // We visit expressions tree in pre-order, so place the left-hand side on the top.
308
- self . decision_stack . push_back ( rhs) ;
309
- self . decision_stack . push_back ( lhs) ;
310
- }
311
-
312
- fn take_condition (
313
- & mut self ,
314
- true_marker : BlockMarkerId ,
315
- false_marker : BlockMarkerId ,
316
- ) -> ( Option < ConditionInfo > , Option < MCDCDecisionSpan > ) {
317
- let Some ( condition_info) = self . decision_stack . pop_back ( ) else {
318
- return ( None , None ) ;
319
- } ;
320
- let Some ( decision) = self . processing_decision . as_mut ( ) else {
321
- bug ! ( "Processing decision should have been created before any conditions are taken" ) ;
322
- } ;
323
- if condition_info. true_next_id == ConditionId :: NONE {
324
- decision. end_markers . push ( true_marker) ;
325
- }
326
- if condition_info. false_next_id == ConditionId :: NONE {
327
- decision. end_markers . push ( false_marker) ;
328
- }
329
-
330
- if self . decision_stack . is_empty ( ) {
331
- ( Some ( condition_info) , self . processing_decision . take ( ) )
332
- } else {
333
- ( Some ( condition_info) , None )
334
- }
335
- }
336
- }
337
-
338
160
impl Builder < ' _ , ' _ > {
339
161
/// If branch coverage is enabled, inject marker statements into `then_block`
340
162
/// and `else_block`, and record their IDs in the table of branch spans.
@@ -359,30 +181,18 @@ impl Builder<'_, '_> {
359
181
let source_info = SourceInfo { span : self . thir [ expr_id] . span , scope : self . source_scope } ;
360
182
361
183
// Separate path for handling branches when MC/DC is enabled.
362
- if branch_info. mcdc_state . is_some ( ) {
363
- let mut inject_block_marker =
364
- |block| branch_info. inject_block_marker ( & mut self . cfg , source_info, block) ;
365
- let true_marker = inject_block_marker ( then_block) ;
366
- let false_marker = inject_block_marker ( else_block) ;
367
- let condition_info =
368
- branch_info. fetch_mcdc_condition_info ( self . tcx , true_marker, false_marker) ;
369
- branch_info. mcdc_branch_spans . push ( MCDCBranchSpan {
370
- span : source_info. span ,
371
- condition_info,
372
- true_marker,
373
- false_marker,
374
- } ) ;
184
+ if let Some ( mcdc_builder) = branch_info. mcdc_builder . as_mut ( ) {
185
+ mcdc_builder. visit_evaluated_condition (
186
+ self . tcx ,
187
+ & mut self . cfg ,
188
+ source_info,
189
+ then_block,
190
+ else_block,
191
+ & mut branch_info. block_marker_generator ,
192
+ ) ;
375
193
return ;
376
194
}
377
195
378
196
branch_info. add_two_way_branch ( & mut self . cfg , source_info, then_block, else_block) ;
379
197
}
380
-
381
- pub ( crate ) fn visit_coverage_branch_operation ( & mut self , logical_op : LogicalOp , span : Span ) {
382
- if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
383
- && let Some ( mcdc_state) = branch_info. mcdc_state . as_mut ( )
384
- {
385
- mcdc_state. record_conditions ( logical_op, span) ;
386
- }
387
- }
388
198
}
0 commit comments