|
| 1 | +mod mcdc; |
1 | 2 | use std::assert_matches::assert_matches; |
2 | 3 | use std::collections::hash_map::Entry; |
3 | | -use std::collections::VecDeque; |
4 | 4 |
|
5 | 5 | use rustc_data_structures::fx::FxHashMap; |
6 | | -use rustc_middle::mir::coverage::{ |
7 | | - BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan, |
8 | | - MCDCDecisionSpan, |
9 | | -}; |
| 6 | +use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind}; |
10 | 7 | use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp}; |
11 | | -use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir}; |
| 8 | +use rustc_middle::thir::{ExprId, ExprKind, Thir}; |
12 | 9 | use rustc_middle::ty::TyCtxt; |
13 | 10 | use rustc_span::def_id::LocalDefId; |
14 | | -use rustc_span::Span; |
15 | 11 |
|
| 12 | +use crate::build::coverageinfo::mcdc::MCDCInfoBuilder; |
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. |
@@ -159,241 +155,6 @@ impl BranchInfoBuilder { |
159 | 155 | } |
160 | 156 | } |
161 | 157 |
|
162 | | -/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen, |
163 | | -/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge. |
164 | | -/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged. |
165 | | -const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6; |
166 | | - |
167 | | -#[derive(Default)] |
168 | | -struct MCDCDecisionCtx { |
169 | | - /// To construct condition evaluation tree. |
170 | | - decision_stack: VecDeque<ConditionInfo>, |
171 | | - processing_decision: Option<MCDCDecisionSpan>, |
172 | | -} |
173 | | - |
174 | | -struct MCDCState { |
175 | | - decision_ctx_stack: Vec<MCDCDecisionCtx>, |
176 | | -} |
177 | | - |
178 | | -impl MCDCState { |
179 | | - fn new() -> Self { |
180 | | - Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] } |
181 | | - } |
182 | | - |
183 | | - /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`, |
184 | | - /// as it is very unlikely that the depth ever reaches 2^16. |
185 | | - #[inline] |
186 | | - fn decision_depth(&self) -> u16 { |
187 | | - match u16::try_from(self.decision_ctx_stack.len()) |
188 | | - .expect( |
189 | | - "decision depth did not fit in u16, this is likely to be an instrumentation error", |
190 | | - ) |
191 | | - .checked_sub(1) |
192 | | - { |
193 | | - Some(d) => d, |
194 | | - None => bug!("Unexpected empty decision stack"), |
195 | | - } |
196 | | - } |
197 | | - |
198 | | - // At first we assign ConditionIds for each sub expression. |
199 | | - // If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS. |
200 | | - // |
201 | | - // Example: "x = (A && B) || (C && D) || (D && F)" |
202 | | - // |
203 | | - // Visit Depth1: |
204 | | - // (A && B) || (C && D) || (D && F) |
205 | | - // ^-------LHS--------^ ^-RHS--^ |
206 | | - // ID=1 ID=2 |
207 | | - // |
208 | | - // Visit LHS-Depth2: |
209 | | - // (A && B) || (C && D) |
210 | | - // ^-LHS--^ ^-RHS--^ |
211 | | - // ID=1 ID=3 |
212 | | - // |
213 | | - // Visit LHS-Depth3: |
214 | | - // (A && B) |
215 | | - // LHS RHS |
216 | | - // ID=1 ID=4 |
217 | | - // |
218 | | - // Visit RHS-Depth3: |
219 | | - // (C && D) |
220 | | - // LHS RHS |
221 | | - // ID=3 ID=5 |
222 | | - // |
223 | | - // Visit RHS-Depth2: (D && F) |
224 | | - // LHS RHS |
225 | | - // ID=2 ID=6 |
226 | | - // |
227 | | - // Visit Depth1: |
228 | | - // (A && B) || (C && D) || (D && F) |
229 | | - // ID=1 ID=4 ID=3 ID=5 ID=2 ID=6 |
230 | | - // |
231 | | - // A node ID of '0' always means MC/DC isn't being tracked. |
232 | | - // |
233 | | - // If a "next" node ID is '0', it means it's the end of the test vector. |
234 | | - // |
235 | | - // 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. |
236 | | - // - 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". |
237 | | - // - 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". |
238 | | - fn record_conditions(&mut self, op: LogicalOp, span: Span) { |
239 | | - let decision_depth = self.decision_depth(); |
240 | | - let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { |
241 | | - bug!("Unexpected empty decision_ctx_stack") |
242 | | - }; |
243 | | - let decision = match decision_ctx.processing_decision.as_mut() { |
244 | | - Some(decision) => { |
245 | | - decision.span = decision.span.to(span); |
246 | | - decision |
247 | | - } |
248 | | - None => decision_ctx.processing_decision.insert(MCDCDecisionSpan { |
249 | | - span, |
250 | | - conditions_num: 0, |
251 | | - end_markers: vec![], |
252 | | - decision_depth, |
253 | | - }), |
254 | | - }; |
255 | | - |
256 | | - let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default(); |
257 | | - let lhs_id = if parent_condition.condition_id == ConditionId::NONE { |
258 | | - decision.conditions_num += 1; |
259 | | - ConditionId::from(decision.conditions_num) |
260 | | - } else { |
261 | | - parent_condition.condition_id |
262 | | - }; |
263 | | - |
264 | | - decision.conditions_num += 1; |
265 | | - let rhs_condition_id = ConditionId::from(decision.conditions_num); |
266 | | - |
267 | | - let (lhs, rhs) = match op { |
268 | | - LogicalOp::And => { |
269 | | - let lhs = ConditionInfo { |
270 | | - condition_id: lhs_id, |
271 | | - true_next_id: rhs_condition_id, |
272 | | - false_next_id: parent_condition.false_next_id, |
273 | | - }; |
274 | | - let rhs = ConditionInfo { |
275 | | - condition_id: rhs_condition_id, |
276 | | - true_next_id: parent_condition.true_next_id, |
277 | | - false_next_id: parent_condition.false_next_id, |
278 | | - }; |
279 | | - (lhs, rhs) |
280 | | - } |
281 | | - LogicalOp::Or => { |
282 | | - let lhs = ConditionInfo { |
283 | | - condition_id: lhs_id, |
284 | | - true_next_id: parent_condition.true_next_id, |
285 | | - false_next_id: rhs_condition_id, |
286 | | - }; |
287 | | - let rhs = ConditionInfo { |
288 | | - condition_id: rhs_condition_id, |
289 | | - true_next_id: parent_condition.true_next_id, |
290 | | - false_next_id: parent_condition.false_next_id, |
291 | | - }; |
292 | | - (lhs, rhs) |
293 | | - } |
294 | | - }; |
295 | | - // We visit expressions tree in pre-order, so place the left-hand side on the top. |
296 | | - decision_ctx.decision_stack.push_back(rhs); |
297 | | - decision_ctx.decision_stack.push_back(lhs); |
298 | | - } |
299 | | - |
300 | | - fn take_condition( |
301 | | - &mut self, |
302 | | - true_marker: BlockMarkerId, |
303 | | - false_marker: BlockMarkerId, |
304 | | - ) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) { |
305 | | - let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { |
306 | | - bug!("Unexpected empty decision_ctx_stack") |
307 | | - }; |
308 | | - let Some(condition_info) = decision_ctx.decision_stack.pop_back() else { |
309 | | - return (None, None); |
310 | | - }; |
311 | | - let Some(decision) = decision_ctx.processing_decision.as_mut() else { |
312 | | - bug!("Processing decision should have been created before any conditions are taken"); |
313 | | - }; |
314 | | - if condition_info.true_next_id == ConditionId::NONE { |
315 | | - decision.end_markers.push(true_marker); |
316 | | - } |
317 | | - if condition_info.false_next_id == ConditionId::NONE { |
318 | | - decision.end_markers.push(false_marker); |
319 | | - } |
320 | | - |
321 | | - if decision_ctx.decision_stack.is_empty() { |
322 | | - (Some(condition_info), decision_ctx.processing_decision.take()) |
323 | | - } else { |
324 | | - (Some(condition_info), None) |
325 | | - } |
326 | | - } |
327 | | -} |
328 | | - |
329 | | -struct MCDCInfoBuilder { |
330 | | - branch_spans: Vec<MCDCBranchSpan>, |
331 | | - decision_spans: Vec<MCDCDecisionSpan>, |
332 | | - state: MCDCState, |
333 | | -} |
334 | | - |
335 | | -impl MCDCInfoBuilder { |
336 | | - fn new() -> Self { |
337 | | - Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::new() } |
338 | | - } |
339 | | - |
340 | | - fn visit_evaluated_condition( |
341 | | - &mut self, |
342 | | - tcx: TyCtxt<'_>, |
343 | | - source_info: SourceInfo, |
344 | | - true_block: BasicBlock, |
345 | | - false_block: BasicBlock, |
346 | | - mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId, |
347 | | - ) { |
348 | | - let true_marker = inject_block_marker(source_info, true_block); |
349 | | - let false_marker = inject_block_marker(source_info, false_block); |
350 | | - |
351 | | - let decision_depth = self.state.decision_depth(); |
352 | | - let (mut condition_info, decision_result) = |
353 | | - self.state.take_condition(true_marker, false_marker); |
354 | | - // take_condition() returns Some for decision_result when the decision stack |
355 | | - // is empty, i.e. when all the conditions of the decision were instrumented, |
356 | | - // and the decision is "complete". |
357 | | - if let Some(decision) = decision_result { |
358 | | - match decision.conditions_num { |
359 | | - 0 => { |
360 | | - unreachable!("Decision with no condition is not expected"); |
361 | | - } |
362 | | - 1..=MAX_CONDITIONS_NUM_IN_DECISION => { |
363 | | - self.decision_spans.push(decision); |
364 | | - } |
365 | | - _ => { |
366 | | - // Do not generate mcdc mappings and statements for decisions with too many conditions. |
367 | | - let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1; |
368 | | - for branch in &mut self.branch_spans[rebase_idx..] { |
369 | | - branch.condition_info = None; |
370 | | - } |
371 | | - |
372 | | - // ConditionInfo of this branch shall also be reset. |
373 | | - condition_info = None; |
374 | | - |
375 | | - tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit { |
376 | | - span: decision.span, |
377 | | - conditions_num: decision.conditions_num, |
378 | | - max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION, |
379 | | - }); |
380 | | - } |
381 | | - } |
382 | | - } |
383 | | - self.branch_spans.push(MCDCBranchSpan { |
384 | | - span: source_info.span, |
385 | | - condition_info, |
386 | | - true_marker, |
387 | | - false_marker, |
388 | | - decision_depth, |
389 | | - }); |
390 | | - } |
391 | | - |
392 | | - fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) { |
393 | | - (self.decision_spans, self.branch_spans) |
394 | | - } |
395 | | -} |
396 | | - |
397 | 158 | impl Builder<'_, '_> { |
398 | 159 | /// If branch coverage is enabled, inject marker statements into `then_block` |
399 | 160 | /// and `else_block`, and record their IDs in the table of branch spans. |
@@ -434,30 +195,4 @@ impl Builder<'_, '_> { |
434 | 195 |
|
435 | 196 | branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block); |
436 | 197 | } |
437 | | - |
438 | | - pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) { |
439 | | - if let Some(branch_info) = self.coverage_branch_info.as_mut() |
440 | | - && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() |
441 | | - { |
442 | | - mcdc_info.state.record_conditions(logical_op, span); |
443 | | - } |
444 | | - } |
445 | | - |
446 | | - pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) { |
447 | | - if let Some(branch_info) = self.coverage_branch_info.as_mut() |
448 | | - && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() |
449 | | - { |
450 | | - mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default()); |
451 | | - }; |
452 | | - } |
453 | | - |
454 | | - pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) { |
455 | | - if let Some(branch_info) = self.coverage_branch_info.as_mut() |
456 | | - && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() |
457 | | - { |
458 | | - if mcdc_info.state.decision_ctx_stack.pop().is_none() { |
459 | | - bug!("Unexpected empty decision stack"); |
460 | | - } |
461 | | - }; |
462 | | - } |
463 | 198 | } |
0 commit comments