Skip to content

Commit 9684257

Browse files
committed
coverage: Collect a function's coverage mappings into a single list
1 parent bc5a5c2 commit 9684257

File tree

3 files changed

+72
-99
lines changed

3 files changed

+72
-99
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

Lines changed: 42 additions & 88 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,21 @@ 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+
// Sort all of the collected mappings into a predictable order.
140+
// (Mappings have a total order, so an unstable sort should be fine.)
141+
self.mappings.sort_unstable();
142142
}
143143

144144
/// Perform some simplifications to make the final coverage mappings
@@ -147,7 +147,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
147147
/// This method mainly exists to preserve the simplifications that were
148148
/// already being performed by the Rust-side expression renumbering, so that
149149
/// the resulting coverage mappings don't get worse.
150-
pub(crate) fn simplify_expressions(&mut self) {
150+
fn simplify_expressions(&mut self) {
151151
// The set of expressions that either were optimized out entirely, or
152152
// have zero as both of their operands, and will therefore always have
153153
// a value of zero. Other expressions that refer to these as operands
@@ -199,43 +199,9 @@ impl<'tcx> FunctionCoverage<'tcx> {
199199
if self.is_used { self.function_coverage_info.function_source_hash } else { 0 }
200200
}
201201

202-
/// Generate an array of CounterExpressions, and an iterator over all `Counter`s and their
203-
/// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create
204-
/// `CounterMappingRegion`s.
205-
pub fn get_expressions_and_counter_regions(
206-
&self,
207-
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
208-
let counter_expressions = self.counter_expressions();
209-
// Expression IDs are indices into `self.expressions`, and on the LLVM
210-
// side they will be treated as indices into `counter_expressions`, so
211-
// the two vectors should correspond 1:1.
212-
assert_eq!(self.expressions.len(), counter_expressions.len());
213-
214-
let counter_regions = self.counter_regions();
215-
let expression_regions = self.expression_regions();
216-
let unreachable_regions = self.unreachable_regions();
217-
218-
let counter_regions =
219-
counter_regions.chain(expression_regions.into_iter().chain(unreachable_regions));
220-
(counter_expressions, counter_regions)
221-
}
222-
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-
236202
/// Convert this function's coverage expression data into a form that can be
237203
/// passed through FFI to LLVM.
238-
fn counter_expressions(&self) -> Vec<CounterExpression> {
204+
pub(crate) fn expressions_for_ffi(&self) -> Vec<CounterExpression> {
239205
// We know that LLVM will optimize out any unused expressions before
240206
// producing the final coverage map, so there's no need to do the same
241207
// thing on the Rust side unless we're confident we can do much better.
@@ -266,24 +232,12 @@ impl<'tcx> FunctionCoverage<'tcx> {
266232
.collect::<Vec<_>>()
267233
}
268234

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))
235+
/// Converts this function's coverage mappings into an intermediate form
236+
/// that will be used by `mapgen` when preparing for FFI.
237+
pub(crate) fn mappings_for_ffi(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
238+
self.mappings.iter().map(|&Mapping { term, ref code_region }| {
239+
let counter = Counter::from_term(term);
240+
(counter, code_region)
241+
})
288242
}
289243
}

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ 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();
65+
let function_coverage = function_coverage;
6566

6667
let mangled_function_name = tcx.symbol_name(instance).name;
6768
let source_hash = function_coverage.source_hash();
@@ -158,20 +159,21 @@ fn encode_mappings_for_function(
158159
global_file_table: &mut GlobalFileTable,
159160
function_coverage: &FunctionCoverage<'_>,
160161
) -> Vec<u8> {
161-
let (expressions, counter_regions) = function_coverage.get_expressions_and_counter_regions();
162+
let expressions = function_coverage.expressions_for_ffi();
163+
let mut counter_regions = function_coverage.mappings_for_ffi().collect::<Vec<_>>();
162164

163-
let mut counter_regions = counter_regions.collect::<Vec<_>>();
164165
if counter_regions.is_empty() {
165166
return Vec::new();
166167
}
167168

168169
let mut virtual_file_mapping = IndexVec::<u32, u32>::new();
169170
let mut mapping_regions = Vec::with_capacity(counter_regions.len());
170171

171-
// Sort the list of (counter, region) mapping pairs by region, so that they
172-
// can be grouped by filename. Prepare file IDs for each filename, and
173-
// prepare the mapping data so that we can pass it through FFI to LLVM.
174-
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);
175177
for counter_regions_for_file in
176178
counter_regions.group_by(|(_, a), (_, b)| a.file_name == b.file_name)
177179
{

compiler/rustc_middle/src/mir/coverage.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,19 @@ impl ExpressionId {
3535
pub const START: Self = Self::from_u32(0);
3636
}
3737

38-
/// Enum that can hold a constant zero value, the ID of an physical coverage
39-
/// counter, or the ID of a coverage-counter expression.
38+
/// Enum that can hold the ID of a physical coverage counter, the ID of a
39+
/// coverage-counter expression, or a constant zero value.
4040
///
4141
/// This was originally only used for expression operands (and named `Operand`),
4242
/// but the zero/counter/expression distinction is also useful for representing
4343
/// the value of code/gap mappings, and the true/false arms of branch mappings.
44-
#[derive(Copy, Clone, PartialEq, Eq)]
44+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
4545
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
4646
pub enum CovTerm {
47-
Zero,
47+
// Coverage codegen relies on this variant order when it sorts a function's mappings.
4848
Counter(CounterId),
4949
Expression(ExpressionId),
50+
Zero,
5051
}
5152

5253
impl Debug for CovTerm {
@@ -101,6 +102,8 @@ impl Debug for CoverageKind {
101102
#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, Eq, PartialOrd, Ord)]
102103
#[derive(TypeFoldable, TypeVisitable)]
103104
pub struct CodeRegion {
105+
// Coverage codegen relies on filenames being sorted first, because it
106+
// needs to group mappings by file.
104107
pub file_name: Symbol,
105108
pub start_line: u32,
106109
pub start_col: u32,
@@ -135,6 +138,20 @@ impl Op {
135138
}
136139
}
137140

141+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
142+
pub struct Mapping {
143+
// Coverage codegen relies on this field order when it sorts a function's mappings.
144+
pub code_region: CodeRegion,
145+
146+
/// Indicates whether this mapping uses a counter value, expression value,
147+
/// or zero value.
148+
///
149+
/// FIXME: When we add support for mapping kinds other than `Code`
150+
/// (e.g. branch regions, expansion regions), replace this with a dedicated
151+
/// mapping-kind enum.
152+
pub term: CovTerm,
153+
}
154+
138155
/// Stores per-function coverage information attached to a `mir::Body`,
139156
/// to be used in conjunction with the individual coverage statements injected
140157
/// into the function's basic blocks.

0 commit comments

Comments
 (0)