Skip to content

Commit 36aab8d

Browse files
committed
Auto merge of #115301 - Zalathar:regions-vec, r=davidtwco
coverage: Allow each coverage statement to have multiple code regions The original implementation of coverage instrumentation was built around the assumption that a coverage counter/expression would be associated with *up to one* code region. When it was discovered that *multiple* regions would sometimes need to share a counter, a workaround was found: for the remaining regions, the instrumentor would create a fresh expression that adds zero to the existing counter/expression. That got the job done, but resulted in some awkward code, and produces unnecessarily complicated coverage maps in the final binary. --- This PR removes that tension by changing `StatementKind::Coverage`'s code region field from `Option<CodeRegion>` to `Vec<CodeRegion>`. The changes on the codegen side are fairly straightforward. As long as each `CoverageKind::Counter` only injects one `llvm.instrprof.increment`, the rest of coverage codegen is happy to handle multiple regions mapped to the same counter/expression, with only minor option-to-vec adjustments. On the instrumentor/mir-transform side, we can get rid of the code that creates extra (x + 0) expressions. Instead we gather all of the code regions associated with a single BCB, and inject them all into one coverage statement. --- There are several patches here but they can be divided in to three phases: - Preparatory work - Actually switching over to multiple regions per coverage statement - Cleaning up So viewing the patches individually may be easier.
2 parents 268d625 + 053c4f9 commit 36aab8d

37 files changed

+1070
-1214
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

+58-35
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct Expression {
1111
lhs: Operand,
1212
op: Op,
1313
rhs: Operand,
14-
region: Option<CodeRegion>,
14+
code_regions: Vec<CodeRegion>,
1515
}
1616

1717
/// Collects all of the coverage regions associated with (a) injected counters, (b) counter
@@ -30,7 +30,7 @@ pub struct FunctionCoverage<'tcx> {
3030
instance: Instance<'tcx>,
3131
source_hash: u64,
3232
is_used: bool,
33-
counters: IndexVec<CounterId, Option<CodeRegion>>,
33+
counters: IndexVec<CounterId, Option<Vec<CodeRegion>>>,
3434
expressions: IndexVec<ExpressionId, Option<Expression>>,
3535
unreachable_regions: Vec<CodeRegion>,
3636
}
@@ -77,28 +77,40 @@ impl<'tcx> FunctionCoverage<'tcx> {
7777
}
7878
}
7979

80-
/// Adds a code region to be counted by an injected counter intrinsic.
81-
pub fn add_counter(&mut self, id: CounterId, region: CodeRegion) {
82-
if let Some(previous_region) = self.counters[id].replace(region.clone()) {
83-
assert_eq!(previous_region, region, "add_counter: code region for id changed");
80+
/// Adds code regions to be counted by an injected counter intrinsic.
81+
#[instrument(level = "debug", skip(self))]
82+
pub(crate) fn add_counter(&mut self, id: CounterId, code_regions: &[CodeRegion]) {
83+
if code_regions.is_empty() {
84+
return;
85+
}
86+
87+
let slot = &mut self.counters[id];
88+
match slot {
89+
None => *slot = Some(code_regions.to_owned()),
90+
// If this counter ID slot has already been filled, it should
91+
// contain identical information.
92+
Some(ref previous_regions) => assert_eq!(
93+
previous_regions, code_regions,
94+
"add_counter: code regions for id changed"
95+
),
8496
}
8597
}
8698

99+
/// Adds information about a coverage expression, along with zero or more
100+
/// code regions mapped to that expression.
101+
///
87102
/// Both counters and "counter expressions" (or simply, "expressions") can be operands in other
88103
/// expressions. These are tracked as separate variants of `Operand`, so there is no ambiguity
89104
/// between operands that are counter IDs and operands that are expression IDs.
90-
pub fn add_counter_expression(
105+
#[instrument(level = "debug", skip(self))]
106+
pub(crate) fn add_counter_expression(
91107
&mut self,
92108
expression_id: ExpressionId,
93109
lhs: Operand,
94110
op: Op,
95111
rhs: Operand,
96-
region: Option<CodeRegion>,
112+
code_regions: &[CodeRegion],
97113
) {
98-
debug!(
99-
"add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}",
100-
expression_id, lhs, op, rhs, region
101-
);
102114
debug_assert!(
103115
expression_id.as_usize() < self.expressions.len(),
104116
"expression_id {} is out of range for expressions.len() = {}
@@ -107,23 +119,25 @@ impl<'tcx> FunctionCoverage<'tcx> {
107119
self.expressions.len(),
108120
self,
109121
);
110-
if let Some(previous_expression) = self.expressions[expression_id].replace(Expression {
111-
lhs,
112-
op,
113-
rhs,
114-
region: region.clone(),
115-
}) {
116-
assert_eq!(
117-
previous_expression,
118-
Expression { lhs, op, rhs, region },
122+
123+
let expression = Expression { lhs, op, rhs, code_regions: code_regions.to_owned() };
124+
let slot = &mut self.expressions[expression_id];
125+
match slot {
126+
None => *slot = Some(expression),
127+
// If this expression ID slot has already been filled, it should
128+
// contain identical information.
129+
Some(ref previous_expression) => assert_eq!(
130+
previous_expression, &expression,
119131
"add_counter_expression: expression for id changed"
120-
);
132+
),
121133
}
122134
}
123135

124-
/// Add a region that will be marked as "unreachable", with a constant "zero counter".
125-
pub fn add_unreachable_region(&mut self, region: CodeRegion) {
126-
self.unreachable_regions.push(region)
136+
/// Adds regions that will be marked as "unreachable", with a constant "zero counter".
137+
#[instrument(level = "debug", skip(self))]
138+
pub(crate) fn add_unreachable_regions(&mut self, code_regions: &[CodeRegion]) {
139+
assert!(!code_regions.is_empty(), "unreachable regions always have code regions");
140+
self.unreachable_regions.extend_from_slice(code_regions);
127141
}
128142

129143
/// Perform some simplifications to make the final coverage mappings
@@ -212,11 +226,16 @@ impl<'tcx> FunctionCoverage<'tcx> {
212226
}
213227

214228
fn counter_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {
215-
self.counters.iter_enumerated().filter_map(|(index, entry)| {
216-
// Option::map() will return None to filter out missing counters. This may happen
217-
// if, for example, a MIR-instrumented counter is removed during an optimization.
218-
entry.as_ref().map(|region| (Counter::counter_value_reference(index), region))
219-
})
229+
self.counters
230+
.iter_enumerated()
231+
// Filter out counter IDs that we never saw during MIR traversal.
232+
// This can happen if a counter was optimized out by MIR transforms
233+
// (and replaced with `CoverageKind::Unreachable` instead).
234+
.filter_map(|(id, maybe_code_regions)| Some((id, maybe_code_regions.as_ref()?)))
235+
.flat_map(|(id, code_regions)| {
236+
let counter = Counter::counter_value_reference(id);
237+
code_regions.iter().map(move |region| (counter, region))
238+
})
220239
}
221240

222241
/// Convert this function's coverage expression data into a form that can be
@@ -254,13 +273,17 @@ impl<'tcx> FunctionCoverage<'tcx> {
254273

255274
fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> {
256275
// Find all of the expression IDs that weren't optimized out AND have
257-
// an attached code region, and return the corresponding mapping as a
258-
// counter/region pair.
276+
// one or more attached code regions, and return the corresponding
277+
// mappings as counter/region pairs.
259278
self.expressions
260279
.iter_enumerated()
261-
.filter_map(|(id, expression)| {
262-
let code_region = expression.as_ref()?.region.as_ref()?;
263-
Some((Counter::expression(id), code_region))
280+
.filter_map(|(id, maybe_expression)| {
281+
let code_regions = &maybe_expression.as_ref()?.code_regions;
282+
Some((id, code_regions))
283+
})
284+
.flat_map(|(id, code_regions)| {
285+
let counter = Counter::expression(id);
286+
code_regions.iter().map(move |code_region| (counter, code_region))
264287
})
265288
.collect::<Vec<_>>()
266289
}

compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs

+9-36
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
8989
/// `function_coverage_map` (keyed by function `Instance`) during codegen.
9090
/// But in this case, since the unused function was _not_ previously
9191
/// codegenned, collect the coverage `CodeRegion`s from the MIR and add
92-
/// them. The first `CodeRegion` is used to add a single counter, with the
93-
/// same counter ID used in the injected `instrprof.increment` intrinsic
94-
/// call. Since the function is never called, all other `CodeRegion`s can be
92+
/// them. Since the function is never called, all of its `CodeRegion`s can be
9593
/// added as `unreachable_region`s.
9694
fn define_unused_fn(&self, def_id: DefId) {
9795
let instance = declare_unused_fn(self, def_id);
@@ -110,25 +108,15 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
110108
.entry(instance)
111109
.or_insert_with(|| FunctionCoverage::new(bx.tcx(), instance));
112110

113-
let Coverage { kind, code_region } = coverage.clone();
114-
match kind {
111+
let Coverage { kind, code_regions } = coverage;
112+
match *kind {
115113
CoverageKind::Counter { function_source_hash, id } => {
116114
debug!(
117115
"ensuring function source hash is set for instance={:?}; function_source_hash={}",
118116
instance, function_source_hash,
119117
);
120118
func_coverage.set_function_source_hash(function_source_hash);
121-
122-
if let Some(code_region) = code_region {
123-
// Note: Some counters do not have code regions, but may still be referenced
124-
// from expressions. In that case, don't add the counter to the coverage map,
125-
// but do inject the counter intrinsic.
126-
debug!(
127-
"adding counter to coverage_map: instance={:?}, id={:?}, region={:?}",
128-
instance, id, code_region,
129-
);
130-
func_coverage.add_counter(id, code_region);
131-
}
119+
func_coverage.add_counter(id, code_regions);
132120
// We need to explicitly drop the `RefMut` before calling into `instrprof_increment`,
133121
// as that needs an exclusive borrow.
134122
drop(coverage_map);
@@ -146,20 +134,10 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
146134
bx.instrprof_increment(fn_name, hash, num_counters, index);
147135
}
148136
CoverageKind::Expression { id, lhs, op, rhs } => {
149-
debug!(
150-
"adding counter expression to coverage_map: instance={:?}, id={:?}, {:?} {:?} {:?}; region: {:?}",
151-
instance, id, lhs, op, rhs, code_region,
152-
);
153-
func_coverage.add_counter_expression(id, lhs, op, rhs, code_region);
137+
func_coverage.add_counter_expression(id, lhs, op, rhs, code_regions);
154138
}
155139
CoverageKind::Unreachable => {
156-
let code_region =
157-
code_region.expect("unreachable regions always have code regions");
158-
debug!(
159-
"adding unreachable code to coverage_map: instance={:?}, at {:?}",
160-
instance, code_region,
161-
);
162-
func_coverage.add_unreachable_region(code_region);
140+
func_coverage.add_unreachable_regions(code_regions);
163141
}
164142
}
165143
}
@@ -227,14 +205,9 @@ fn add_unused_function_coverage<'tcx>(
227205
let tcx = cx.tcx;
228206

229207
let mut function_coverage = FunctionCoverage::unused(tcx, instance);
230-
for (index, &code_region) in tcx.covered_code_regions(def_id).iter().enumerate() {
231-
if index == 0 {
232-
// Insert at least one real counter so the LLVM CoverageMappingReader will find expected
233-
// definitions.
234-
function_coverage.add_counter(UNUSED_FUNCTION_COUNTER_ID, code_region.clone());
235-
} else {
236-
function_coverage.add_unreachable_region(code_region.clone());
237-
}
208+
for &code_region in tcx.covered_code_regions(def_id) {
209+
let code_region = std::slice::from_ref(code_region);
210+
function_coverage.add_unreachable_regions(code_region);
238211
}
239212

240213
if let Some(coverage_context) = cx.coverage_context() {

compiler/rustc_middle/src/mir/coverage.rs

-10
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ rustc_index::newtype_index! {
1818

1919
impl CounterId {
2020
pub const START: Self = Self::from_u32(0);
21-
22-
#[inline(always)]
23-
pub fn next_id(self) -> Self {
24-
Self::from_u32(self.as_u32() + 1)
25-
}
2621
}
2722

2823
rustc_index::newtype_index! {
@@ -38,11 +33,6 @@ rustc_index::newtype_index! {
3833

3934
impl ExpressionId {
4035
pub const START: Self = Self::from_u32(0);
41-
42-
#[inline(always)]
43-
pub fn next_id(self) -> Self {
44-
Self::from_u32(self.as_u32() + 1)
45-
}
4636
}
4737

4838
/// Operand of a coverage-counter expression.

compiler/rustc_middle/src/mir/pretty.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_middle::mir::interpret::{
1616
Pointer, Provenance,
1717
};
1818
use rustc_middle::mir::visit::Visitor;
19-
use rustc_middle::mir::*;
19+
use rustc_middle::mir::{self, *};
2020
use rustc_middle::ty::{self, TyCtxt};
2121
use rustc_target::abi::Size;
2222

@@ -685,10 +685,13 @@ impl Debug for Statement<'_> {
685685
AscribeUserType(box (ref place, ref c_ty), ref variance) => {
686686
write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})")
687687
}
688-
Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => {
689-
write!(fmt, "Coverage::{kind:?} for {rgn:?}")
688+
Coverage(box mir::Coverage { ref kind, ref code_regions }) => {
689+
if code_regions.is_empty() {
690+
write!(fmt, "Coverage::{kind:?}")
691+
} else {
692+
write!(fmt, "Coverage::{kind:?} for {code_regions:?}")
693+
}
690694
}
691-
Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind),
692695
Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"),
693696
ConstEvalCounter => write!(fmt, "ConstEvalCounter"),
694697
Nop => write!(fmt, "nop"),

compiler/rustc_middle/src/mir/syntax.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ pub enum FakeReadCause {
514514
#[derive(TypeFoldable, TypeVisitable)]
515515
pub struct Coverage {
516516
pub kind: CoverageKind,
517-
pub code_region: Option<CodeRegion>,
517+
pub code_regions: Vec<CodeRegion>,
518518
}
519519

520520
#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]

compiler/rustc_mir_transform/src/coverage/counters.rs

+14-23
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
use super::Error;
22

33
use super::graph;
4-
use super::spans;
54

65
use graph::{BasicCoverageBlock, BcbBranch, CoverageGraph, TraverseCoverageGraphWithLoops};
7-
use spans::CoverageSpan;
86

97
use rustc_data_structures::fx::FxHashMap;
108
use rustc_data_structures::graph::WithNumNodes;
@@ -93,14 +91,14 @@ impl CoverageCounters {
9391
}
9492

9593
/// Makes [`BcbCounter`] `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or
96-
/// indirectly associated with `CoverageSpans`, and accumulates additional `Expression`s
94+
/// indirectly associated with coverage spans, and accumulates additional `Expression`s
9795
/// representing intermediate values.
9896
pub fn make_bcb_counters(
9997
&mut self,
10098
basic_coverage_blocks: &CoverageGraph,
101-
coverage_spans: &[CoverageSpan],
99+
bcb_has_coverage_spans: impl Fn(BasicCoverageBlock) -> bool,
102100
) -> Result<(), Error> {
103-
MakeBcbCounters::new(self, basic_coverage_blocks).make_bcb_counters(coverage_spans)
101+
MakeBcbCounters::new(self, basic_coverage_blocks).make_bcb_counters(bcb_has_coverage_spans)
104102
}
105103

106104
fn make_counter(&mut self) -> BcbCounter {
@@ -113,22 +111,18 @@ impl CoverageCounters {
113111
BcbCounter::Expression { id, lhs, op, rhs }
114112
}
115113

116-
pub fn make_identity_counter(&mut self, counter_operand: Operand) -> BcbCounter {
117-
self.make_expression(counter_operand, Op::Add, Operand::Zero)
118-
}
119-
120114
/// Counter IDs start from one and go up.
121115
fn next_counter(&mut self) -> CounterId {
122116
let next = self.next_counter_id;
123-
self.next_counter_id = next.next_id();
117+
self.next_counter_id = self.next_counter_id + 1;
124118
next
125119
}
126120

127121
/// Expression IDs start from 0 and go up.
128122
/// (Counter IDs and Expression IDs are distinguished by the `Operand` enum.)
129123
fn next_expression(&mut self) -> ExpressionId {
130124
let next = self.next_expression_id;
131-
self.next_expression_id = next.next_id();
125+
self.next_expression_id = self.next_expression_id + 1;
132126
next
133127
}
134128

@@ -208,7 +202,7 @@ impl CoverageCounters {
208202
}
209203

210204
/// Traverse the `CoverageGraph` and add either a `Counter` or `Expression` to every BCB, to be
211-
/// injected with `CoverageSpan`s. `Expressions` have no runtime overhead, so if a viable expression
205+
/// injected with coverage spans. `Expressions` have no runtime overhead, so if a viable expression
212206
/// (adding or subtracting two other counters or expressions) can compute the same result as an
213207
/// embedded counter, an `Expression` should be used.
214208
struct MakeBcbCounters<'a> {
@@ -234,17 +228,14 @@ impl<'a> MakeBcbCounters<'a> {
234228
/// Returns any non-code-span expressions created to represent intermediate values (such as to
235229
/// add two counters so the result can be subtracted from another counter), or an Error with
236230
/// message for subsequent debugging.
237-
fn make_bcb_counters(&mut self, coverage_spans: &[CoverageSpan]) -> Result<(), Error> {
231+
fn make_bcb_counters(
232+
&mut self,
233+
bcb_has_coverage_spans: impl Fn(BasicCoverageBlock) -> bool,
234+
) -> Result<(), Error> {
238235
debug!("make_bcb_counters(): adding a counter or expression to each BasicCoverageBlock");
239-
let num_bcbs = self.basic_coverage_blocks.num_nodes();
240-
241-
let mut bcbs_with_coverage = BitSet::new_empty(num_bcbs);
242-
for covspan in coverage_spans {
243-
bcbs_with_coverage.insert(covspan.bcb);
244-
}
245236

246237
// Walk the `CoverageGraph`. For each `BasicCoverageBlock` node with an associated
247-
// `CoverageSpan`, add a counter. If the `BasicCoverageBlock` branches, add a counter or
238+
// coverage span, add a counter. If the `BasicCoverageBlock` branches, add a counter or
248239
// expression to each branch `BasicCoverageBlock` (if the branch BCB has only one incoming
249240
// edge) or edge from the branching BCB to the branch BCB (if the branch BCB has multiple
250241
// incoming edges).
@@ -255,16 +246,16 @@ impl<'a> MakeBcbCounters<'a> {
255246
// the current BCB is in one or more nested loops or not.
256247
let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks);
257248
while let Some(bcb) = traversal.next(self.basic_coverage_blocks) {
258-
if bcbs_with_coverage.contains(bcb) {
259-
debug!("{:?} has at least one `CoverageSpan`. Get or make its counter", bcb);
249+
if bcb_has_coverage_spans(bcb) {
250+
debug!("{:?} has at least one coverage span. Get or make its counter", bcb);
260251
let branching_counter_operand = self.get_or_make_counter_operand(bcb)?;
261252

262253
if self.bcb_needs_branch_counters(bcb) {
263254
self.make_branch_counters(&mut traversal, bcb, branching_counter_operand)?;
264255
}
265256
} else {
266257
debug!(
267-
"{:?} does not have any `CoverageSpan`s. A counter will only be added if \
258+
"{:?} does not have any coverage spans. A counter will only be added if \
268259
and when a covered BCB has an expression dependency.",
269260
bcb,
270261
);

0 commit comments

Comments
 (0)