Skip to content

Commit 23fe917

Browse files
committed
coverage: Collect a function's coverage mappings into a single list
1 parent 6bfeff3 commit 23fe917

File tree

3 files changed

+65
-75
lines changed

3 files changed

+65
-75
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

Lines changed: 45 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
22

33
use rustc_data_structures::fx::FxIndexSet;
4+
use rustc_index::bit_set::BitSet;
45
use rustc_index::IndexVec;
56
use rustc_middle::mir::coverage::{
6-
CodeRegion, CounterId, CovTerm, ExpressionId, FunctionCoverageInfo, Op,
7+
CodeRegion, CounterId, CovTerm, ExpressionId, FunctionCoverageInfo, Mapping, Op,
78
};
89
use rustc_middle::ty::Instance;
910

@@ -12,28 +13,21 @@ pub struct Expression {
1213
lhs: CovTerm,
1314
op: Op,
1415
rhs: CovTerm,
15-
code_regions: Vec<CodeRegion>,
1616
}
1717

18-
/// Collects all of the coverage regions associated with (a) injected counters, (b) counter
19-
/// expressions (additions or subtraction), and (c) unreachable regions (always counted as zero),
20-
/// for a given Function. This struct also stores the `function_source_hash`,
21-
/// computed during instrumentation, and forwarded with counters.
22-
///
23-
/// Note, it may be important to understand LLVM's definitions of `unreachable` regions versus "gap
24-
/// regions" (or "gap areas"). A gap region is a code region within a counted region (either counter
25-
/// or expression), but the line or lines in the gap region are not executable (such as lines with
26-
/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count
27-
/// for a gap area is only used as the line execution count if there are no other regions on a
28-
/// line."
18+
/// Holds all of the coverage mapping data associated with a function instance,
19+
/// collected during traversal of `Coverage` statements in the function's MIR.
2920
#[derive(Debug)]
3021
pub struct FunctionCoverage<'tcx> {
3122
/// Coverage info that was attached to this function by the instrumentor.
3223
function_coverage_info: &'tcx FunctionCoverageInfo,
3324
is_used: bool,
34-
counters: IndexVec<CounterId, Option<Vec<CodeRegion>>>,
25+
26+
/// Tracks which counters have been seen, to avoid duplicate mappings
27+
/// that might be introduced by MIR inlining.
28+
counters_seen: BitSet<CounterId>,
3529
expressions: IndexVec<ExpressionId, Option<Expression>>,
36-
unreachable_regions: Vec<CodeRegion>,
30+
mappings: Vec<Mapping>,
3731
}
3832

3933
impl<'tcx> FunctionCoverage<'tcx> {
@@ -67,9 +61,9 @@ impl<'tcx> FunctionCoverage<'tcx> {
6761
Self {
6862
function_coverage_info,
6963
is_used,
70-
counters: IndexVec::from_elem_n(None, num_counters),
64+
counters_seen: BitSet::new_empty(num_counters),
7165
expressions: IndexVec::from_elem_n(None, num_expressions),
72-
unreachable_regions: Vec::new(),
66+
mappings: Vec::new(),
7367
}
7468
}
7569

@@ -81,19 +75,8 @@ impl<'tcx> FunctionCoverage<'tcx> {
8175
/// Adds code regions to be counted by an injected counter intrinsic.
8276
#[instrument(level = "debug", skip(self))]
8377
pub(crate) fn add_counter(&mut self, id: CounterId, code_regions: &[CodeRegion]) {
84-
if code_regions.is_empty() {
85-
return;
86-
}
87-
88-
let slot = &mut self.counters[id];
89-
match slot {
90-
None => *slot = Some(code_regions.to_owned()),
91-
// If this counter ID slot has already been filled, it should
92-
// contain identical information.
93-
Some(ref previous_regions) => assert_eq!(
94-
previous_regions, code_regions,
95-
"add_counter: code regions for id changed"
96-
),
78+
if self.counters_seen.insert(id) {
79+
self.add_mappings(CovTerm::Counter(id), code_regions);
9780
}
9881
}
9982

@@ -121,10 +104,13 @@ impl<'tcx> FunctionCoverage<'tcx> {
121104
self,
122105
);
123106

124-
let expression = Expression { lhs, op, rhs, code_regions: code_regions.to_owned() };
107+
let expression = Expression { lhs, op, rhs };
125108
let slot = &mut self.expressions[expression_id];
126109
match slot {
127-
None => *slot = Some(expression),
110+
None => {
111+
*slot = Some(expression);
112+
self.add_mappings(CovTerm::Expression(expression_id), code_regions);
113+
}
128114
// If this expression ID slot has already been filled, it should
129115
// contain identical information.
130116
Some(ref previous_expression) => assert_eq!(
@@ -138,7 +124,25 @@ impl<'tcx> FunctionCoverage<'tcx> {
138124
#[instrument(level = "debug", skip(self))]
139125
pub(crate) fn add_unreachable_regions(&mut self, code_regions: &[CodeRegion]) {
140126
assert!(!code_regions.is_empty(), "unreachable regions always have code regions");
141-
self.unreachable_regions.extend_from_slice(code_regions);
127+
self.add_mappings(CovTerm::Zero, code_regions);
128+
}
129+
130+
#[instrument(level = "debug", skip(self))]
131+
fn add_mappings(&mut self, term: CovTerm, code_regions: &[CodeRegion]) {
132+
self.mappings
133+
.extend(code_regions.iter().cloned().map(|code_region| Mapping { term, code_region }));
134+
}
135+
136+
pub(crate) fn finalize(&mut self) {
137+
self.simplify_expressions();
138+
139+
// Reorder the collected mappings so that counter mappings are first and
140+
// zero mappings are last, matching the historical order.
141+
self.mappings.sort_by_key(|mapping| match mapping.term {
142+
CovTerm::Counter(_) => 0,
143+
CovTerm::Expression(_) => 1,
144+
CovTerm::Zero => u8::MAX,
145+
});
142146
}
143147

144148
/// Perform some simplifications to make the final coverage mappings
@@ -147,7 +151,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
147151
/// This method mainly exists to preserve the simplifications that were
148152
/// already being performed by the Rust-side expression renumbering, so that
149153
/// the resulting coverage mappings don't get worse.
150-
pub(crate) fn simplify_expressions(&mut self) {
154+
fn simplify_expressions(&mut self) {
151155
// The set of expressions that either were optimized out entirely, or
152156
// have zero as both of their operands, and will therefore always have
153157
// a value of zero. Other expressions that refer to these as operands
@@ -212,27 +216,10 @@ impl<'tcx> FunctionCoverage<'tcx> {
212216
assert_eq!(self.expressions.len(), counter_expressions.len());
213217

214218
let counter_regions = self.counter_regions();
215-
let expression_regions = self.expression_regions();
216-
let unreachable_regions = self.unreachable_regions();
217219

218-
let counter_regions =
219-
counter_regions.chain(expression_regions.into_iter().chain(unreachable_regions));
220220
(counter_expressions, counter_regions)
221221
}
222222

223-
fn counter_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
224-
self.counters
225-
.iter_enumerated()
226-
// Filter out counter IDs that we never saw during MIR traversal.
227-
// This can happen if a counter was optimized out by MIR transforms
228-
// (and replaced with `CoverageKind::Unreachable` instead).
229-
.filter_map(|(id, maybe_code_regions)| Some((id, maybe_code_regions.as_ref()?)))
230-
.flat_map(|(id, code_regions)| {
231-
let counter = Counter::counter_value_reference(id);
232-
code_regions.iter().map(move |region| (counter, region))
233-
})
234-
}
235-
236223
/// Convert this function's coverage expression data into a form that can be
237224
/// passed through FFI to LLVM.
238225
fn counter_expressions(&self) -> Vec<CounterExpression> {
@@ -266,24 +253,12 @@ impl<'tcx> FunctionCoverage<'tcx> {
266253
.collect::<Vec<_>>()
267254
}
268255

269-
fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> {
270-
// Find all of the expression IDs that weren't optimized out AND have
271-
// one or more attached code regions, and return the corresponding
272-
// mappings as counter/region pairs.
273-
self.expressions
274-
.iter_enumerated()
275-
.filter_map(|(id, maybe_expression)| {
276-
let code_regions = &maybe_expression.as_ref()?.code_regions;
277-
Some((id, code_regions))
278-
})
279-
.flat_map(|(id, code_regions)| {
280-
let counter = Counter::expression(id);
281-
code_regions.iter().map(move |code_region| (counter, code_region))
282-
})
283-
.collect::<Vec<_>>()
284-
}
285-
286-
fn unreachable_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
287-
self.unreachable_regions.iter().map(|region| (Counter::ZERO, region))
256+
/// Converts this function's coverage mappings into an intermediate form
257+
/// that will be used by `mapgen` when preparing for FFI.
258+
fn counter_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
259+
self.mappings.iter().map(|&Mapping { term, ref code_region }| {
260+
let counter = Counter::from_term(term);
261+
(counter, code_region)
262+
})
288263
}
289264
}

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
6161
let mut function_data = Vec::new();
6262
for (instance, mut function_coverage) in function_coverage_map {
6363
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
64-
function_coverage.simplify_expressions();
64+
function_coverage.finalize();
6565
let function_coverage = function_coverage;
6666

6767
let mangled_function_name = tcx.symbol_name(instance).name;
@@ -169,10 +169,11 @@ fn encode_mappings_for_function(
169169
let mut virtual_file_mapping = IndexVec::<u32, u32>::new();
170170
let mut mapping_regions = Vec::with_capacity(counter_regions.len());
171171

172-
// Sort the list of (counter, region) mapping pairs by region, so that they
173-
// can be grouped by filename. Prepare file IDs for each filename, and
174-
// prepare the mapping data so that we can pass it through FFI to LLVM.
175-
counter_regions.sort_by_key(|(_counter, region)| *region);
172+
// Sort and group the list of (counter, region) mapping pairs by filename.
173+
// (Preserve any further ordering imposed by `FunctionCoverage`.)
174+
// Prepare file IDs for each filename, and prepare the mapping data so that
175+
// we can pass it through FFI to LLVM.
176+
counter_regions.sort_by_key(|(_counter, region)| region.file_name);
176177
for counter_regions_for_file in
177178
counter_regions.group_by(|(_, a), (_, b)| a.file_name == b.file_name)
178179
{

compiler/rustc_middle/src/mir/coverage.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,20 @@ impl Op {
135135
}
136136
}
137137

138+
#[derive(Clone, Debug)]
139+
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
140+
pub struct Mapping {
141+
pub code_region: CodeRegion,
142+
143+
/// Indicates whether this mapping uses a counter value, expression value,
144+
/// or zero value.
145+
///
146+
/// FIXME: When we add support for mapping kinds other than `Code`
147+
/// (e.g. branch regions, expansion regions), replace this with a dedicated
148+
/// mapping-kind enum.
149+
pub term: CovTerm,
150+
}
151+
138152
/// Stores per-function coverage information attached to a `mir::Body`,
139153
/// to be used in conjunction with the individual coverage statements injected
140154
/// into the function's basic blocks.

0 commit comments

Comments
 (0)