1+ mod mcdc;
2+ use mcdc:: MCDCInfoBuilder ;
13use std:: assert_matches:: assert_matches;
24use std:: collections:: hash_map:: Entry ;
3- use std:: collections:: VecDeque ;
45
56use 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 } ;
108use rustc_middle:: mir:: { self , BasicBlock , SourceInfo , UnOp } ;
11- use rustc_middle:: thir:: { ExprId , ExprKind , LogicalOp , Thir } ;
9+ use rustc_middle:: thir:: { ExprId , ExprKind , Thir } ;
1210use rustc_middle:: ty:: TyCtxt ;
1311use rustc_span:: def_id:: LocalDefId ;
14- use rustc_span:: Span ;
1512
1613use crate :: build:: { Builder , CFG } ;
17- use crate :: errors:: MCDCExceedsConditionNumLimit ;
1814
1915pub ( crate ) struct BranchInfoBuilder {
2016 /// Maps condition expressions to their enclosing `!`, for better instrumentation.
2117 nots : FxHashMap < ExprId , NotInfo > ,
22-
23- num_block_markers : usize ,
18+ block_marker_generator : BlockMarkerGenerator ,
2419 branch_spans : Vec < BranchSpan > ,
2520
26- mcdc_branch_spans : Vec < MCDCBranchSpan > ,
27- mcdc_decision_spans : Vec < MCDCDecisionSpan > ,
28- mcdc_state : Option < MCDCState > ,
21+ mcdc_builder : Option < MCDCInfoBuilder > ,
2922}
3023
3124#[ derive( Clone , Copy ) ]
@@ -38,18 +31,46 @@ struct NotInfo {
3831 is_flipped : bool ,
3932}
4033
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+
4164impl BranchInfoBuilder {
4265 /// Creates a new branch info builder, but only if branch coverage instrumentation
4366 /// is enabled and `def_id` represents a function that is eligible for coverage.
4467 pub ( crate ) fn new_if_enabled ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> Option < Self > {
4568 if tcx. sess . instrument_coverage_branch ( ) && tcx. is_eligible_for_coverage ( def_id) {
4669 Some ( Self {
4770 nots : FxHashMap :: default ( ) ,
48- num_block_markers : 0 ,
71+ block_marker_generator : BlockMarkerGenerator :: default ( ) ,
4972 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) ,
5374 } )
5475 } else {
5576 None
@@ -96,245 +117,46 @@ impl BranchInfoBuilder {
96117 }
97118 }
98119
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-
137120 fn add_two_way_branch < ' tcx > (
138121 & mut self ,
139122 cfg : & mut CFG < ' tcx > ,
140123 source_info : SourceInfo ,
141124 true_block : BasicBlock ,
142125 false_block : BasicBlock ,
143126 ) {
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) ;
146131
147132 self . branch_spans . push ( BranchSpan { span : source_info. span , true_marker, false_marker } ) ;
148133 }
149134
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-
173135 pub ( crate ) fn into_done ( self ) -> Option < Box < mir:: coverage:: BranchInfo > > {
174136 let Self {
175137 nots : _,
176- num_block_markers,
138+ block_marker_generator : BlockMarkerGenerator { num_block_markers } ,
177139 branch_spans,
178- mcdc_branch_spans,
179- mcdc_decision_spans,
180- mcdc_state : _,
140+ mcdc_builder,
181141 } = self ;
182142
183143 if num_block_markers == 0 {
184144 assert ! ( branch_spans. is_empty( ) ) ;
185145 return None ;
186146 }
187147
148+ let ( mcdc_decision_spans, mcdc_branch_spans) =
149+ mcdc_builder. map ( MCDCInfoBuilder :: into_done) . unwrap_or_default ( ) ;
150+
188151 Some ( Box :: new ( mir:: coverage:: BranchInfo {
189152 num_block_markers,
190153 branch_spans,
191- mcdc_branch_spans,
192154 mcdc_decision_spans,
155+ mcdc_branch_spans,
193156 } ) )
194157 }
195158}
196159
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-
338160impl Builder < ' _ , ' _ > {
339161 /// If branch coverage is enabled, inject marker statements into `then_block`
340162 /// and `else_block`, and record their IDs in the table of branch spans.
@@ -359,30 +181,18 @@ impl Builder<'_, '_> {
359181 let source_info = SourceInfo { span : self . thir [ expr_id] . span , scope : self . source_scope } ;
360182
361183 // 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+ ) ;
375193 return ;
376194 }
377195
378196 branch_info. add_two_way_branch ( & mut self . cfg , source_info, then_block, else_block) ;
379197 }
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- }
388198}
0 commit comments