|
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,
|
@@ -41,6 +42,7 @@ impl BooleanDecisionCtx {
|
41 | 42 | span: Span::default(),
|
42 | 43 | end_markers: vec![],
|
43 | 44 | decision_depth: 0,
|
| 45 | + num_test_vectors: 0, |
44 | 46 | },
|
45 | 47 | decision_stack: VecDeque::new(),
|
46 | 48 | conditions: vec![],
|
@@ -158,11 +160,11 @@ impl BooleanDecisionCtx {
|
158 | 160 | self.decision_info.end_markers.push(false_marker);
|
159 | 161 | }
|
160 | 162 |
|
161 |
| - self.conditions.push(MCDCBranchSpan { |
| 163 | + self.conditions.push(MCDCBranchSpan::new( |
162 | 164 | span,
|
163 | 165 | condition_info,
|
164 |
| - markers: MCDCBranchMarkers::Boolean(true_marker, false_marker), |
165 |
| - }); |
| 166 | + MCDCBranchMarkers::Boolean(true_marker, false_marker), |
| 167 | + )); |
166 | 168 | }
|
167 | 169 |
|
168 | 170 | fn is_finished(&self) -> bool {
|
@@ -257,9 +259,86 @@ struct MCDCTargetInfo {
|
257 | 259 | }
|
258 | 260 |
|
259 | 261 | impl MCDCTargetInfo {
|
| 262 | + fn new(decision: MCDCDecisionSpan, conditions: Vec<MCDCBranchSpan>) -> Self { |
| 263 | + let mut this = Self { decision, conditions, nested_decisions_id: vec![] }; |
| 264 | + this.calc_test_vectors_index(); |
| 265 | + this |
| 266 | + } |
| 267 | + |
260 | 268 | fn set_depth(&mut self, depth: u16) {
|
261 | 269 | self.decision.decision_depth = depth;
|
262 | 270 | }
|
| 271 | + |
| 272 | + // LLVM checks the executed test vector by accumulate indices of tested branches. |
| 273 | + // We calculate number of all possible test vectors of the decision and assign indices |
| 274 | + // for each branch here. |
| 275 | + // See https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798/ for |
| 276 | + // more details of the algorithm. |
| 277 | + // The process of this function is mostly like `TVIdxBuilder` at |
| 278 | + // https://github.com/llvm/llvm-project/blob/d594d9f7f4dc6eb748b3261917db689fdc348b96/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L226 |
| 279 | + fn calc_test_vectors_index(&mut self) { |
| 280 | + let Self { decision, conditions, .. } = self; |
| 281 | + let mut indegree_stats = IndexVec::<ConditionId, usize>::from_elem_n(0, conditions.len()); |
| 282 | + // `num_paths` is `width` described at the llvm RFC, which indicates how many paths reaching the condition. |
| 283 | + let mut num_paths_stats = IndexVec::<ConditionId, usize>::from_elem_n(0, conditions.len()); |
| 284 | + let mut next_conditions = conditions |
| 285 | + .iter_mut() |
| 286 | + .map(|branch| { |
| 287 | + let ConditionInfo { condition_id, true_next_id, false_next_id } = |
| 288 | + branch.condition_info; |
| 289 | + [true_next_id, false_next_id] |
| 290 | + .into_iter() |
| 291 | + .filter_map(std::convert::identity) |
| 292 | + .for_each(|next_id| indegree_stats[next_id] += 1); |
| 293 | + (condition_id, branch) |
| 294 | + }) |
| 295 | + .collect::<FxIndexMap<_, _>>(); |
| 296 | + |
| 297 | + let mut queue = |
| 298 | + VecDeque::from_iter(next_conditions.swap_remove(&ConditionId::START).into_iter()); |
| 299 | + num_paths_stats[ConditionId::START] = 1; |
| 300 | + let mut decision_end_nodes = Vec::new(); |
| 301 | + while let Some(branch) = queue.pop_front() { |
| 302 | + let MCDCBranchSpan { |
| 303 | + span: _, |
| 304 | + condition_info: ConditionInfo { condition_id, true_next_id, false_next_id }, |
| 305 | + markers: _, |
| 306 | + false_index, |
| 307 | + true_index, |
| 308 | + } = branch; |
| 309 | + let this_paths_count = num_paths_stats[*condition_id]; |
| 310 | + for (next, index) in [(false_next_id, false_index), (true_next_id, true_index)] { |
| 311 | + if let Some(next_id) = next { |
| 312 | + let next_paths_count = &mut num_paths_stats[*next_id]; |
| 313 | + *index = *next_paths_count; |
| 314 | + *next_paths_count = next_paths_count.saturating_add(this_paths_count); |
| 315 | + let next_indegree = &mut indegree_stats[*next_id]; |
| 316 | + *next_indegree -= 1; |
| 317 | + if *next_indegree == 0 { |
| 318 | + queue.push_back(next_conditions.swap_remove(next_id).expect( |
| 319 | + "conditions with non-zero indegree before must be in next_conditions", |
| 320 | + )); |
| 321 | + } |
| 322 | + } else { |
| 323 | + decision_end_nodes.push((this_paths_count, *condition_id, index)); |
| 324 | + } |
| 325 | + } |
| 326 | + } |
| 327 | + assert!(next_conditions.is_empty(), "the decision tree has untouched nodes"); |
| 328 | + let mut cur_idx = 0; |
| 329 | + // LLVM hopes the end nodes is sorted in ascending order by `num_paths`. |
| 330 | + decision_end_nodes.sort_by_key(|(num_paths, _, _)| usize::MAX - *num_paths); |
| 331 | + for (num_paths, condition_id, index) in decision_end_nodes { |
| 332 | + assert_eq!( |
| 333 | + num_paths, num_paths_stats[condition_id], |
| 334 | + "end nodes should not be updated since they were visited" |
| 335 | + ); |
| 336 | + assert_eq!(*index, usize::MAX, "end nodes should not be assigned index before"); |
| 337 | + *index = cur_idx; |
| 338 | + cur_idx += num_paths; |
| 339 | + } |
| 340 | + decision.num_test_vectors = cur_idx; |
| 341 | + } |
263 | 342 | }
|
264 | 343 |
|
265 | 344 | #[derive(Default)]
|
@@ -323,7 +402,7 @@ impl MCDCInfoBuilder {
|
323 | 402 | }
|
324 | 403 | // Ignore decisions with only one condition given that mcdc for them is completely equivalent to branch coverage.
|
325 | 404 | 2..=MAX_CONDITIONS_IN_DECISION => {
|
326 |
| - let info = MCDCTargetInfo { decision, conditions, nested_decisions_id: vec![] }; |
| 405 | + let info = MCDCTargetInfo::new(decision, conditions); |
327 | 406 | Some(self.mcdc_targets.entry(id).or_insert(info))
|
328 | 407 | }
|
329 | 408 | _ => {
|
|
0 commit comments