@@ -3,7 +3,7 @@ use std::collections::VecDeque;
33use rustc_middle:: mir:: coverage:: {
44 BlockMarkerId , ConditionId , ConditionInfo , MCDCBranchSpan , MCDCDecisionSpan ,
55} ;
6- use rustc_middle:: mir:: { BasicBlock , SourceInfo } ;
6+ use rustc_middle:: mir:: BasicBlock ;
77use rustc_middle:: thir:: LogicalOp ;
88use rustc_middle:: ty:: TyCtxt ;
99use rustc_span:: Span ;
@@ -16,34 +16,24 @@ use crate::errors::MCDCExceedsConditionNumLimit;
1616/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
1717const MAX_CONDITIONS_NUM_IN_DECISION : usize = 6 ;
1818
19- #[ derive( Default ) ]
2019struct MCDCDecisionCtx {
2120 /// To construct condition evaluation tree.
21+ decision_info : MCDCDecisionSpan ,
2222 decision_stack : VecDeque < ConditionInfo > ,
23- processing_decision : Option < MCDCDecisionSpan > ,
23+ conditions : Vec < MCDCBranchSpan > ,
2424}
2525
26- struct MCDCState {
27- decision_ctx_stack : Vec < MCDCDecisionCtx > ,
28- }
29-
30- impl MCDCState {
31- fn new ( ) -> Self {
32- Self { decision_ctx_stack : vec ! [ MCDCDecisionCtx :: default ( ) ] }
33- }
34-
35- /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
36- /// as it is very unlikely that the depth ever reaches 2^16.
37- #[ inline]
38- fn decision_depth ( & self ) -> u16 {
39- match u16:: try_from ( self . decision_ctx_stack . len ( ) )
40- . expect (
41- "decision depth did not fit in u16, this is likely to be an instrumentation error" ,
42- )
43- . checked_sub ( 1 )
44- {
45- Some ( d) => d,
46- None => bug ! ( "Unexpected empty decision stack" ) ,
26+ impl MCDCDecisionCtx {
27+ fn new ( decision_depth : u16 ) -> Self {
28+ Self {
29+ decision_stack : VecDeque :: new ( ) ,
30+ decision_info : MCDCDecisionSpan {
31+ span : Span :: default ( ) ,
32+ conditions_num : 0 ,
33+ end_markers : vec ! [ ] ,
34+ decision_depth,
35+ } ,
36+ conditions : vec ! [ ] ,
4737 }
4838 }
4939
@@ -87,34 +77,17 @@ impl MCDCState {
8777 // 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.
8878 // - 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".
8979 // - 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".
90- fn record_conditions ( & mut self , op : LogicalOp , span : Span ) {
91- let decision_depth = self . decision_depth ( ) ;
92- let Some ( decision_ctx) = self . decision_ctx_stack . last_mut ( ) else {
93- bug ! ( "Unexpected empty decision_ctx_stack" )
94- } ;
95- let decision = match decision_ctx. processing_decision . as_mut ( ) {
96- Some ( decision) => {
97- decision. span = decision. span . to ( span) ;
98- decision
99- }
100- None => decision_ctx. processing_decision . insert ( MCDCDecisionSpan {
101- span,
102- conditions_num : 0 ,
103- end_markers : vec ! [ ] ,
104- decision_depth,
105- } ) ,
106- } ;
107-
108- let parent_condition = decision_ctx. decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
80+ fn record_conditions ( & mut self , op : LogicalOp ) {
81+ let parent_condition = self . decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
10982 let lhs_id = if parent_condition. condition_id == ConditionId :: NONE {
110- decision . conditions_num += 1 ;
111- ConditionId :: from ( decision . conditions_num )
83+ self . decision_info . conditions_num += 1 ;
84+ ConditionId :: from ( self . decision_info . conditions_num )
11285 } else {
11386 parent_condition. condition_id
11487 } ;
11588
116- decision . conditions_num += 1 ;
117- let rhs_condition_id = ConditionId :: from ( decision . conditions_num ) ;
89+ self . decision_info . conditions_num += 1 ;
90+ let rhs_condition_id = ConditionId :: from ( self . decision_info . conditions_num ) ;
11891
11992 let ( lhs, rhs) = match op {
12093 LogicalOp :: And => {
@@ -145,85 +118,118 @@ impl MCDCState {
145118 }
146119 } ;
147120 // We visit expressions tree in pre-order, so place the left-hand side on the top.
148- decision_ctx . decision_stack . push_back ( rhs) ;
149- decision_ctx . decision_stack . push_back ( lhs) ;
121+ self . decision_stack . push_back ( rhs) ;
122+ self . decision_stack . push_back ( lhs) ;
150123 }
151124
152- fn take_condition (
125+ fn finish_two_way_branch (
153126 & mut self ,
127+ span : Span ,
128+ test_marker : BlockMarkerId ,
154129 true_marker : BlockMarkerId ,
155130 false_marker : BlockMarkerId ,
156- ) -> ( Option < ConditionInfo > , Option < MCDCDecisionSpan > ) {
157- let Some ( decision_ctx) = self . decision_ctx_stack . last_mut ( ) else {
158- bug ! ( "Unexpected empty decision_ctx_stack" )
159- } ;
160- let Some ( condition_info) = decision_ctx. decision_stack . pop_back ( ) else {
161- return ( None , None ) ;
162- } ;
163- let Some ( decision) = decision_ctx. processing_decision . as_mut ( ) else {
164- bug ! ( "Processing decision should have been created before any conditions are taken" ) ;
165- } ;
131+ ) {
132+ let condition_info = self . decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
166133 if condition_info. true_next_id == ConditionId :: NONE {
167- decision . end_markers . push ( true_marker) ;
134+ self . decision_info . end_markers . push ( true_marker) ;
168135 }
169136 if condition_info. false_next_id == ConditionId :: NONE {
170- decision . end_markers . push ( false_marker) ;
137+ self . decision_info . end_markers . push ( false_marker) ;
171138 }
172139
173- if decision_ctx. decision_stack . is_empty ( ) {
174- ( Some ( condition_info) , decision_ctx. processing_decision . take ( ) )
175- } else {
176- ( Some ( condition_info) , None )
177- }
140+ self . conditions . push ( MCDCBranchSpan {
141+ span,
142+ condition_info : Some ( condition_info) ,
143+ test_markers : vec ! [ test_marker] ,
144+ true_markers : vec ! [ true_marker] ,
145+ decision_depth : self . decision_info . decision_depth ,
146+ } ) ;
147+ // In case this decision had only one condition
148+ self . decision_info . conditions_num = self . decision_info . conditions_num . max ( 1 ) ;
149+ }
150+
151+ fn finished ( & self ) -> bool {
152+ self . decision_stack . is_empty ( )
178153 }
179154}
180155
181156pub struct MCDCInfoBuilder {
182157 branch_spans : Vec < MCDCBranchSpan > ,
183158 decision_spans : Vec < MCDCDecisionSpan > ,
184- state : MCDCState ,
159+ decision_ctx_stack : Vec < MCDCDecisionCtx > ,
160+ base_depth : u16 ,
185161}
186162
187163impl MCDCInfoBuilder {
188164 pub fn new ( ) -> Self {
189- Self { branch_spans : vec ! [ ] , decision_spans : vec ! [ ] , state : MCDCState :: new ( ) }
165+ Self {
166+ branch_spans : vec ! [ ] ,
167+ decision_spans : vec ! [ ] ,
168+ decision_ctx_stack : vec ! [ ] ,
169+ base_depth : 0 ,
170+ }
190171 }
191172
192- pub fn visit_evaluated_condition (
173+ fn next_decision_depth ( & self ) -> u16 {
174+ u16:: try_from ( self . decision_ctx_stack . len ( ) ) . expect (
175+ "decision depth did not fit in u16, this is likely to be an instrumentation error" ,
176+ )
177+ }
178+
179+ fn ensure_decision ( & mut self , span : Span ) -> & mut MCDCDecisionCtx {
180+ if self . base_depth == self . next_decision_depth ( ) {
181+ let depth = self . next_decision_depth ( ) ;
182+ self . decision_ctx_stack . push ( MCDCDecisionCtx :: new ( depth) ) ;
183+ } else {
184+ assert ! (
185+ self . base_depth <= self . next_decision_depth( ) ,
186+ "expected depth shall not skip next decision depth"
187+ ) ;
188+ }
189+ let ctx = self . decision_ctx_stack . last_mut ( ) . expect ( "ensured above" ) ;
190+
191+ if ctx. decision_info . span == Span :: default ( ) {
192+ ctx. decision_info . span = span;
193+ } else {
194+ ctx. decision_info . span = ctx. decision_info . span . to ( span) ;
195+ }
196+ ctx
197+ }
198+
199+ fn try_finish_decision ( & mut self , tcx : TyCtxt < ' _ > ) {
200+ if !self . decision_ctx_stack . last ( ) . is_some_and ( |decision| decision. finished ( ) ) {
201+ return ;
202+ }
203+ let MCDCDecisionCtx { decision_info, decision_stack : _, conditions } =
204+ self . decision_ctx_stack . pop ( ) . expect ( "has checked above" ) ;
205+
206+ self . append_mcdc_info ( tcx, decision_info, conditions) ;
207+ }
208+
209+ fn append_mcdc_info (
193210 & mut self ,
194211 tcx : TyCtxt < ' _ > ,
195- source_info : SourceInfo ,
196- true_block : BasicBlock ,
197- false_block : BasicBlock ,
198- mut inject_block_marker : impl FnMut ( SourceInfo , BasicBlock ) -> BlockMarkerId ,
212+ decision : MCDCDecisionSpan ,
213+ mut conditions : Vec < MCDCBranchSpan > ,
199214 ) {
200- let true_marker = inject_block_marker ( source_info, true_block) ;
201- let false_marker = inject_block_marker ( source_info, false_block) ;
202-
203- let decision_depth = self . state . decision_depth ( ) ;
204- let ( mut condition_info, decision_result) =
205- self . state . take_condition ( true_marker, false_marker) ;
206215 // take_condition() returns Some for decision_result when the decision stack
207216 // is empty, i.e. when all the conditions of the decision were instrumented,
208217 // and the decision is "complete".
209- if let Some ( decision) = decision_result {
210- match decision. conditions_num {
211- 0 => {
212- unreachable ! ( "Decision with no condition is not expected" ) ;
213- }
214- 1 ..=MAX_CONDITIONS_NUM_IN_DECISION => {
215- self . decision_spans . push ( decision) ;
216- }
217- _ => {
218- // Do not generate mcdc mappings and statements for decisions with too many conditions.
219- let rebase_idx = self . branch_spans . len ( ) - decision. conditions_num + 1 ;
220- for branch in & mut self . branch_spans [ rebase_idx..] {
221- branch. condition_info = None ;
222- }
223-
224- // ConditionInfo of this branch shall also be reset.
225- condition_info = None ;
226-
218+ match decision. conditions_num {
219+ 0 => {
220+ unreachable ! ( "Decision with no condition is not expected" ) ;
221+ }
222+ 2 ..=MAX_CONDITIONS_NUM_IN_DECISION => {
223+ self . decision_spans . push ( decision) ;
224+ self . branch_spans . extend ( conditions) ;
225+ }
226+ // MCDC is equivalent to normal branch coverage if number of conditions is less than 1, so ignore these decisions.
227+ // See comment of `MAX_CONDITIONS_NUM_IN_DECISION` for why decisions with oversized conditions are ignored.
228+ _ => {
229+ // Generate normal branch coverage mappings in such cases.
230+ conditions. iter_mut ( ) . for_each ( |branch| branch. condition_info = None ) ;
231+ self . branch_spans . extend ( conditions) ;
232+ if decision. conditions_num > MAX_CONDITIONS_NUM_IN_DECISION {
227233 tcx. dcx ( ) . emit_warn ( MCDCExceedsConditionNumLimit {
228234 span : decision. span ,
229235 conditions_num : decision. conditions_num ,
@@ -232,13 +238,23 @@ impl MCDCInfoBuilder {
232238 }
233239 }
234240 }
235- self . branch_spans . push ( MCDCBranchSpan {
236- span : source_info. span ,
237- condition_info,
238- true_marker,
239- false_marker,
240- decision_depth,
241- } ) ;
241+ }
242+
243+ pub fn visit_evaluated_condition (
244+ & mut self ,
245+ tcx : TyCtxt < ' _ > ,
246+ span : Span ,
247+ test_block : BasicBlock ,
248+ true_block : BasicBlock ,
249+ false_block : BasicBlock ,
250+ mut inject_block_marker : impl FnMut ( BasicBlock ) -> BlockMarkerId ,
251+ ) {
252+ let test_marker = inject_block_marker ( test_block) ;
253+ let true_marker = inject_block_marker ( true_block) ;
254+ let false_marker = inject_block_marker ( false_block) ;
255+ let decision = self . ensure_decision ( span) ;
256+ decision. finish_two_way_branch ( span, test_marker, true_marker, false_marker) ;
257+ self . try_finish_decision ( tcx) ;
242258 }
243259
244260 pub fn into_done ( self ) -> ( Vec < MCDCDecisionSpan > , Vec < MCDCBranchSpan > ) {
@@ -251,25 +267,24 @@ impl Builder<'_, '_> {
251267 if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
252268 && let Some ( mcdc_info) = branch_info. mcdc_info . as_mut ( )
253269 {
254- mcdc_info. state . record_conditions ( logical_op, span) ;
270+ let decision = mcdc_info. ensure_decision ( span) ;
271+ decision. record_conditions ( logical_op) ;
255272 }
256273 }
257274
258275 pub ( crate ) fn mcdc_increment_depth_if_enabled ( & mut self ) {
259276 if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
260277 && let Some ( mcdc_info) = branch_info. mcdc_info . as_mut ( )
261278 {
262- mcdc_info. state . decision_ctx_stack . push ( MCDCDecisionCtx :: default ( ) ) ;
279+ mcdc_info. base_depth = mcdc_info . next_decision_depth ( ) . max ( mcdc_info . base_depth + 1 ) ;
263280 } ;
264281 }
265282
266283 pub ( crate ) fn mcdc_decrement_depth_if_enabled ( & mut self ) {
267284 if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
268285 && let Some ( mcdc_info) = branch_info. mcdc_info . as_mut ( )
269286 {
270- if mcdc_info. state . decision_ctx_stack . pop ( ) . is_none ( ) {
271- bug ! ( "Unexpected empty decision stack" ) ;
272- }
287+ mcdc_info. base_depth -= 1 ;
273288 } ;
274289 }
275290}
0 commit comments