Skip to content

Commit 37e62f0

Browse files
committed
coverage: Represent branches as a list of arms
Within the `InstrumentCoverage` pass, we now represent branches as a list of arms, instead of a true/false pair, until we prepare the final table of mappings to be attached to the MIR body. (We then flatten the list into two-way branches by treating each arm as a branch between its success block, and the total of all later arms.) Currently all of the branches produced by MIR building are still two-way, but this is a step towards allowing many-way branches.
1 parent 27cdd6e commit 37e62f0

File tree

4 files changed

+87
-42
lines changed

4 files changed

+87
-42
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
6666
// For each expression ID that is directly used by one or more mappings,
6767
// mark it as not-yet-seen. This indicates that we expect to see a
6868
// corresponding `ExpressionUsed` statement during MIR traversal.
69-
for term in function_coverage_info.mappings.iter().flat_map(|m| m.kind.terms()) {
69+
for term in function_coverage_info
70+
.mappings
71+
.iter()
72+
// For many-armed branches, some branch mappings will have expressions
73+
// that don't correspond to any node in the control-flow graph, so don't
74+
// expect to see `ExpressionUsed` statements for them.
75+
.filter(|m| !matches!(m.kind, MappingKind::Branch { .. }))
76+
.flat_map(|m| m.kind.terms())
77+
{
7078
if let CovTerm::Expression(id) = term {
7179
expressions_seen.remove(id);
7280
}

compiler/rustc_mir_transform/src/coverage/counters.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ impl CoverageCounters {
101101
BcbCounter::Counter { id }
102102
}
103103

104-
fn make_expression(&mut self, lhs: BcbCounter, op: Op, rhs: BcbCounter) -> BcbCounter {
104+
pub(super) fn make_expression(
105+
&mut self,
106+
lhs: BcbCounter,
107+
op: Op,
108+
rhs: BcbCounter,
109+
) -> BcbCounter {
105110
let new_expr = BcbExpression { lhs, op, rhs };
106111
*self
107112
.expressions_memo

compiler/rustc_mir_transform/src/coverage/mappings.rs

+18-18
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ pub(super) struct CodeMapping {
2121
pub(super) bcb: BasicCoverageBlock,
2222
}
2323

24-
/// This is separate from [`MCDCBranch`] to help prepare for larger changes
25-
/// that will be needed for improved branch coverage in the future.
26-
/// (See <https://github.com/rust-lang/rust/pull/124217>.)
2724
#[derive(Debug)]
28-
pub(super) struct BranchPair {
25+
pub(super) struct BranchArm {
2926
pub(super) span: Span,
30-
pub(super) true_bcb: BasicCoverageBlock,
31-
pub(super) false_bcb: BasicCoverageBlock,
27+
pub(super) pre_guard_bcb: BasicCoverageBlock,
28+
pub(super) arm_taken_bcb: BasicCoverageBlock,
3229
}
3330

3431
/// Associates an MC/DC branch span with condition info besides fields for normal branch.
@@ -56,7 +53,7 @@ pub(super) struct MCDCDecision {
5653
#[derive(Default)]
5754
pub(super) struct ExtractedMappings {
5855
pub(super) code_mappings: Vec<CodeMapping>,
59-
pub(super) branch_pairs: Vec<BranchPair>,
56+
pub(super) branch_arm_lists: Vec<Vec<BranchArm>>,
6057
pub(super) mcdc_bitmap_bytes: u32,
6158
pub(super) mcdc_branches: Vec<MCDCBranch>,
6259
pub(super) mcdc_decisions: Vec<MCDCDecision>,
@@ -71,7 +68,7 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>(
7168
basic_coverage_blocks: &CoverageGraph,
7269
) -> ExtractedMappings {
7370
let mut code_mappings = vec![];
74-
let mut branch_pairs = vec![];
71+
let mut branch_arm_lists = vec![];
7572
let mut mcdc_bitmap_bytes = 0;
7673
let mut mcdc_branches = vec![];
7774
let mut mcdc_decisions = vec![];
@@ -93,7 +90,7 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>(
9390
extract_refined_covspans(mir_body, hir_info, basic_coverage_blocks, &mut code_mappings);
9491
}
9592

96-
branch_pairs.extend(extract_branch_pairs(mir_body, hir_info, basic_coverage_blocks));
93+
branch_arm_lists.extend(extract_branch_arm_lists(mir_body, hir_info, basic_coverage_blocks));
9794

9895
extract_mcdc_mappings(
9996
mir_body,
@@ -106,7 +103,7 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>(
106103

107104
ExtractedMappings {
108105
code_mappings,
109-
branch_pairs,
106+
branch_arm_lists,
110107
mcdc_bitmap_bytes,
111108
mcdc_branches,
112109
mcdc_decisions,
@@ -121,7 +118,7 @@ impl ExtractedMappings {
121118
// Fully destructure self to make sure we don't miss any fields that have mappings.
122119
let Self {
123120
code_mappings,
124-
branch_pairs,
121+
branch_arm_lists,
125122
mcdc_bitmap_bytes: _,
126123
mcdc_branches,
127124
mcdc_decisions,
@@ -136,9 +133,11 @@ impl ExtractedMappings {
136133
for &CodeMapping { span: _, bcb } in code_mappings {
137134
insert(bcb);
138135
}
139-
for &BranchPair { true_bcb, false_bcb, .. } in branch_pairs {
140-
insert(true_bcb);
141-
insert(false_bcb);
136+
for &BranchArm { span: _, pre_guard_bcb, arm_taken_bcb } in
137+
branch_arm_lists.iter().flatten()
138+
{
139+
insert(pre_guard_bcb);
140+
insert(arm_taken_bcb);
142141
}
143142
for &MCDCBranch { true_bcb, false_bcb, .. } in mcdc_branches {
144143
insert(true_bcb);
@@ -179,16 +178,16 @@ fn resolve_block_markers(
179178
}
180179

181180
// FIXME: There is currently a lot of redundancy between
182-
// `extract_branch_pairs` and `extract_mcdc_mappings`. This is needed so
181+
// `extract_branch_arm_lists` and `extract_mcdc_mappings`. This is needed so
183182
// that they can each be modified without interfering with the other, but in
184183
// the long term we should try to bring them together again when branch coverage
185184
// and MC/DC coverage support are more mature.
186185

187-
pub(super) fn extract_branch_pairs(
186+
pub(super) fn extract_branch_arm_lists(
188187
mir_body: &mir::Body<'_>,
189188
hir_info: &ExtractedHirInfo,
190189
basic_coverage_blocks: &CoverageGraph,
191-
) -> Vec<BranchPair> {
190+
) -> Vec<Vec<BranchArm>> {
192191
let Some(branch_info) = mir_body.coverage_branch_info.as_deref() else { return vec![] };
193192

194193
let block_markers = resolve_block_markers(branch_info, mir_body);
@@ -211,7 +210,8 @@ pub(super) fn extract_branch_pairs(
211210
let true_bcb = bcb_from_marker(true_marker)?;
212211
let false_bcb = bcb_from_marker(false_marker)?;
213212

214-
Some(BranchPair { span, true_bcb, false_bcb })
213+
let arm = |bcb| BranchArm { span, pre_guard_bcb: bcb, arm_taken_bcb: bcb };
214+
Some(vec![arm(true_bcb), arm(false_bcb)])
215215
})
216216
.collect::<Vec<_>>()
217217
}

compiler/rustc_mir_transform/src/coverage/mod.rs

+54-22
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod spans;
88
mod tests;
99

1010
use rustc_middle::mir::coverage::{
11-
CodeRegion, CoverageKind, DecisionInfo, FunctionCoverageInfo, Mapping, MappingKind,
11+
CodeRegion, CoverageKind, DecisionInfo, FunctionCoverageInfo, Mapping, MappingKind, Op,
1212
};
1313
use rustc_middle::mir::{
1414
self, BasicBlock, BasicBlockData, SourceInfo, Statement, StatementKind, Terminator,
@@ -21,7 +21,7 @@ use rustc_span::{BytePos, Pos, RelativeBytePos, Span, Symbol};
2121

2222
use crate::coverage::counters::{CounterIncrementSite, CoverageCounters};
2323
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
24-
use crate::coverage::mappings::ExtractedMappings;
24+
use crate::coverage::mappings::{BranchArm, ExtractedMappings};
2525
use crate::MirPass;
2626

2727
/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
@@ -91,10 +91,10 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
9191
}
9292

9393
let bcb_has_counter_mappings = |bcb| bcbs_with_counter_mappings.contains(bcb);
94-
let coverage_counters =
94+
let mut coverage_counters =
9595
CoverageCounters::make_bcb_counters(&basic_coverage_blocks, bcb_has_counter_mappings);
9696

97-
let mappings = create_mappings(tcx, &hir_info, &extracted_mappings, &coverage_counters);
97+
let mappings = create_mappings(tcx, &hir_info, &extracted_mappings, &mut coverage_counters);
9898
if mappings.is_empty() {
9999
// No spans could be converted into valid mappings, so skip this function.
100100
debug!("no spans could be converted into valid mappings; skipping");
@@ -136,7 +136,7 @@ fn create_mappings<'tcx>(
136136
tcx: TyCtxt<'tcx>,
137137
hir_info: &ExtractedHirInfo,
138138
extracted_mappings: &ExtractedMappings,
139-
coverage_counters: &CoverageCounters,
139+
coverage_counters: &mut CoverageCounters,
140140
) -> Vec<Mapping> {
141141
let source_map = tcx.sess.source_map();
142142
let body_span = hir_info.body_span;
@@ -148,24 +148,66 @@ fn create_mappings<'tcx>(
148148
&source_file.name.for_scope(tcx.sess, RemapPathScopeComponents::MACRO).to_string_lossy(),
149149
);
150150

151-
let term_for_bcb = |bcb| {
152-
coverage_counters
153-
.bcb_counter(bcb)
154-
.expect("all BCBs with spans were given counters")
155-
.as_term()
156-
};
157151
let region_for_span = |span: Span| make_code_region(source_map, file_name, span, body_span);
158152

159153
// Fully destructure the mappings struct to make sure we don't miss any kinds.
160154
let ExtractedMappings {
161155
code_mappings,
162-
branch_pairs,
156+
branch_arm_lists,
163157
mcdc_bitmap_bytes: _,
164158
mcdc_branches,
165159
mcdc_decisions,
166160
} = extracted_mappings;
167161
let mut mappings = Vec::new();
168162

163+
// Process branch arms first, because they might need to mutate `coverage_counters`
164+
// to create new expressions.
165+
for arm_list in branch_arm_lists {
166+
let mut arms_rev = arm_list.iter().rev();
167+
168+
let mut rest_counter = {
169+
// The last arm's span is ignored, because its BCB is only used as the
170+
// false branch of the second-last arm; it's not a branch of its own.
171+
let Some(&BranchArm { span: _, pre_guard_bcb, arm_taken_bcb }) = arms_rev.next() else {
172+
continue;
173+
};
174+
debug_assert_eq!(pre_guard_bcb, arm_taken_bcb, "last arm should not have a guard");
175+
coverage_counters.bcb_counter(pre_guard_bcb).expect("all relevant BCBs have counters")
176+
};
177+
178+
// All relevant BCBs should have counters, so we can `.unwrap()` them.
179+
for &BranchArm { span, pre_guard_bcb, arm_taken_bcb } in arms_rev {
180+
// Number of times the pattern matched.
181+
let matched_counter = coverage_counters.bcb_counter(pre_guard_bcb).unwrap();
182+
// Number of times the pattern matched and the guard succeeded.
183+
let arm_taken_counter = coverage_counters.bcb_counter(arm_taken_bcb).unwrap();
184+
// Total number of times execution logically reached this pattern.
185+
let reached_counter =
186+
coverage_counters.make_expression(rest_counter, Op::Add, arm_taken_counter);
187+
// Number of times execution reached this pattern, but didn't match it.
188+
let unmatched_counter =
189+
coverage_counters.make_expression(reached_counter, Op::Subtract, matched_counter);
190+
191+
let kind = MappingKind::Branch {
192+
true_term: matched_counter.as_term(),
193+
false_term: unmatched_counter.as_term(),
194+
};
195+
196+
if let Some(code_region) = region_for_span(span) {
197+
mappings.push(Mapping { kind, code_region });
198+
}
199+
200+
rest_counter = reached_counter;
201+
}
202+
}
203+
204+
let term_for_bcb = |bcb| {
205+
coverage_counters
206+
.bcb_counter(bcb)
207+
.expect("all BCBs with spans were given counters")
208+
.as_term()
209+
};
210+
169211
mappings.extend(code_mappings.iter().filter_map(
170212
// Ordinary code mappings are the simplest kind.
171213
|&mappings::CodeMapping { span, bcb }| {
@@ -175,16 +217,6 @@ fn create_mappings<'tcx>(
175217
},
176218
));
177219

178-
mappings.extend(branch_pairs.iter().filter_map(
179-
|&mappings::BranchPair { span, true_bcb, false_bcb }| {
180-
let true_term = term_for_bcb(true_bcb);
181-
let false_term = term_for_bcb(false_bcb);
182-
let kind = MappingKind::Branch { true_term, false_term };
183-
let code_region = region_for_span(span)?;
184-
Some(Mapping { kind, code_region })
185-
},
186-
));
187-
188220
mappings.extend(mcdc_branches.iter().filter_map(
189221
|&mappings::MCDCBranch { span, true_bcb, false_bcb, condition_info, decision_depth: _ }| {
190222
let code_region = region_for_span(span)?;

0 commit comments

Comments
 (0)