Skip to content

Commit dcb797a

Browse files
committed
coverage: Don't bother renumbering expressions on the Rust side
The LLVM API that we use to encode coverage mappings already has its own code for removing unused coverage expressions and renumbering the rest. This lets us get rid of our own complex renumbering code, making it easier to refactor our coverage code in other ways.
1 parent b45aadb commit dcb797a

File tree

4 files changed

+59
-165
lines changed

4 files changed

+59
-165
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_middle::mir::coverage::{CounterId, MappedExpressionIndex};
1+
use rustc_middle::mir::coverage::{CounterId, ExpressionId, Operand};
22

33
/// Must match the layout of `LLVMRustCounterKind`.
44
#[derive(Copy, Clone, Debug)]
@@ -39,20 +39,16 @@ impl Counter {
3939
}
4040

4141
/// Constructs a new `Counter` of kind `Expression`.
42-
pub fn expression(mapped_expression_index: MappedExpressionIndex) -> Self {
43-
Self { kind: CounterKind::Expression, id: mapped_expression_index.into() }
42+
pub(crate) fn expression(expression_id: ExpressionId) -> Self {
43+
Self { kind: CounterKind::Expression, id: expression_id.as_u32() }
4444
}
4545

46-
/// Returns true if the `Counter` kind is `Zero`.
47-
pub fn is_zero(&self) -> bool {
48-
matches!(self.kind, CounterKind::Zero)
49-
}
50-
51-
/// An explicitly-named function to get the ID value, making it more obvious
52-
/// that the stored value is now 0-based.
53-
pub fn zero_based_id(&self) -> u32 {
54-
debug_assert!(!self.is_zero(), "`id` is undefined for CounterKind::Zero");
55-
self.id
46+
pub(crate) fn from_operand(operand: Operand) -> Self {
47+
match operand {
48+
Operand::Zero => Self::ZERO,
49+
Operand::Counter(id) => Self::counter_value_reference(id),
50+
Operand::Expression(id) => Self::expression(id),
51+
}
5652
}
5753
}
5854

@@ -78,6 +74,11 @@ pub struct CounterExpression {
7874
}
7975

8076
impl CounterExpression {
77+
/// The dummy expression `(0 - 0)` has a representation of all zeroes,
78+
/// making it marginally more efficient to initialize than `(0 + 0)`.
79+
pub(crate) const DUMMY: Self =
80+
Self { lhs: Counter::ZERO, kind: ExprKind::Subtract, rhs: Counter::ZERO };
81+
8182
pub fn new(lhs: Counter, kind: ExprKind, rhs: Counter) -> Self {
8283
Self { kind, lhs, rhs }
8384
}

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

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

3-
use rustc_index::{IndexSlice, IndexVec};
4-
use rustc_middle::bug;
5-
use rustc_middle::mir::coverage::{
6-
CodeRegion, CounterId, ExpressionId, MappedExpressionIndex, Op, Operand,
7-
};
3+
use rustc_index::IndexVec;
4+
use rustc_middle::mir::coverage::{CodeRegion, CounterId, ExpressionId, Op, Operand};
85
use rustc_middle::ty::Instance;
96
use rustc_middle::ty::TyCtxt;
107

@@ -146,8 +143,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
146143
self.instance
147144
);
148145

146+
let counter_expressions = self.counter_expressions();
147+
// Expression IDs are indices into `self.expressions`, and on the LLVM
148+
// side they will be treated as indices into `counter_expressions`, so
149+
// the two vectors should correspond 1:1.
150+
assert_eq!(self.expressions.len(), counter_expressions.len());
151+
149152
let counter_regions = self.counter_regions();
150-
let (counter_expressions, expression_regions) = self.expressions_with_regions();
153+
let expression_regions = self.expression_regions();
151154
let unreachable_regions = self.unreachable_regions();
152155

153156
let counter_regions =
@@ -163,146 +166,47 @@ impl<'tcx> FunctionCoverage<'tcx> {
163166
})
164167
}
165168

166-
fn expressions_with_regions(
167-
&self,
168-
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
169-
let mut counter_expressions = Vec::with_capacity(self.expressions.len());
170-
let mut expression_regions = Vec::with_capacity(self.expressions.len());
171-
let mut new_indexes = IndexVec::from_elem_n(None, self.expressions.len());
172-
173-
// This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or
174-
// `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type
175-
// and value.
176-
//
177-
// Expressions will be returned from this function in a sequential vector (array) of
178-
// `CounterExpression`, so the expression IDs must be mapped from their original,
179-
// potentially sparse set of indexes.
180-
//
181-
// An `Expression` as an operand will have already been encountered as an `Expression` with
182-
// operands, so its new_index will already have been generated (as a 1-up index value).
183-
// (If an `Expression` as an operand does not have a corresponding new_index, it was
184-
// probably optimized out, after the expression was injected into the MIR, so it will
185-
// get a `CounterKind::Zero` instead.)
186-
//
187-
// In other words, an `Expression`s at any given index can include other expressions as
188-
// operands, but expression operands can only come from the subset of expressions having
189-
// `expression_index`s lower than the referencing `Expression`. Therefore, it is
190-
// reasonable to look up the new index of an expression operand while the `new_indexes`
191-
// vector is only complete up to the current `ExpressionIndex`.
192-
type NewIndexes = IndexSlice<ExpressionId, Option<MappedExpressionIndex>>;
193-
let id_to_counter = |new_indexes: &NewIndexes, operand: Operand| match operand {
194-
Operand::Zero => Some(Counter::ZERO),
195-
Operand::Counter(id) => Some(Counter::counter_value_reference(id)),
196-
Operand::Expression(id) => {
197-
self.expressions
198-
.get(id)
199-
.expect("expression id is out of range")
200-
.as_ref()
201-
// If an expression was optimized out, assume it would have produced a count
202-
// of zero. This ensures that expressions dependent on optimized-out
203-
// expressions are still valid.
204-
.map_or(Some(Counter::ZERO), |_| new_indexes[id].map(Counter::expression))
205-
}
206-
};
207-
208-
for (original_index, expression) in
209-
self.expressions.iter_enumerated().filter_map(|(original_index, entry)| {
210-
// Option::map() will return None to filter out missing expressions. This may happen
211-
// if, for example, a MIR-instrumented expression is removed during an optimization.
212-
entry.as_ref().map(|expression| (original_index, expression))
213-
})
214-
{
215-
let optional_region = &expression.region;
216-
let Expression { lhs, op, rhs, .. } = *expression;
217-
218-
if let Some(Some((lhs_counter, mut rhs_counter))) = id_to_counter(&new_indexes, lhs)
219-
.map(|lhs_counter| {
220-
id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter))
221-
})
222-
{
223-
if lhs_counter.is_zero() && op.is_subtract() {
224-
// The left side of a subtraction was probably optimized out. As an example,
225-
// a branch condition might be evaluated as a constant expression, and the
226-
// branch could be removed, dropping unused counters in the process.
227-
//
228-
// Since counters are unsigned, we must assume the result of the expression
229-
// can be no more and no less than zero. An expression known to evaluate to zero
230-
// does not need to be added to the coverage map.
231-
//
232-
// Coverage test `loops_branches.rs` includes multiple variations of branches
233-
// based on constant conditional (literal `true` or `false`), and demonstrates
234-
// that the expected counts are still correct.
235-
debug!(
236-
"Expression subtracts from zero (assume unreachable): \
237-
original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
238-
original_index, lhs, op, rhs, optional_region,
239-
);
240-
rhs_counter = Counter::ZERO;
169+
/// Convert this function's coverage expression data into a form that can be
170+
/// passed through FFI to LLVM.
171+
fn counter_expressions(&self) -> Vec<CounterExpression> {
172+
// We know that LLVM will optimize out any unused expressions before
173+
// producing the final coverage map, so there's no need to do the same
174+
// thing on the Rust side unless we're confident we can do much better.
175+
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
176+
177+
self.expressions
178+
.iter()
179+
.map(|expression| match expression {
180+
None => {
181+
// This expression ID was allocated, but we never saw the
182+
// actual expression, so it must have been optimized out.
183+
// Replace it with a dummy expression, and let LLVM take
184+
// care of omitting it from the expression list.
185+
CounterExpression::DUMMY
241186
}
242-
debug_assert!(
243-
lhs_counter.is_zero()
244-
// Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16`
245-
|| ((lhs_counter.zero_based_id() as usize)
246-
<= usize::max(self.counters.len(), self.expressions.len())),
247-
"lhs id={} > both counters.len()={} and expressions.len()={}
248-
({:?} {:?} {:?})",
249-
lhs_counter.zero_based_id(),
250-
self.counters.len(),
251-
self.expressions.len(),
252-
lhs_counter,
253-
op,
254-
rhs_counter,
255-
);
256-
257-
debug_assert!(
258-
rhs_counter.is_zero()
259-
// Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16`
260-
|| ((rhs_counter.zero_based_id() as usize)
261-
<= usize::max(self.counters.len(), self.expressions.len())),
262-
"rhs id={} > both counters.len()={} and expressions.len()={}
263-
({:?} {:?} {:?})",
264-
rhs_counter.zero_based_id(),
265-
self.counters.len(),
266-
self.expressions.len(),
267-
lhs_counter,
268-
op,
269-
rhs_counter,
270-
);
271-
272-
// Both operands exist. `Expression` operands exist in `self.expressions` and have
273-
// been assigned a `new_index`.
274-
let mapped_expression_index =
275-
MappedExpressionIndex::from(counter_expressions.len());
276-
let expression = CounterExpression::new(
277-
lhs_counter,
187+
&Some(Expression { lhs, op, rhs, .. }) => CounterExpression::new(
188+
Counter::from_operand(lhs),
278189
match op {
279190
Op::Add => ExprKind::Add,
280191
Op::Subtract => ExprKind::Subtract,
281192
},
282-
rhs_counter,
283-
);
284-
debug!(
285-
"Adding expression {:?} = {:?}, region: {:?}",
286-
mapped_expression_index, expression, optional_region
287-
);
288-
counter_expressions.push(expression);
289-
new_indexes[original_index] = Some(mapped_expression_index);
290-
if let Some(region) = optional_region {
291-
expression_regions.push((Counter::expression(mapped_expression_index), region));
292-
}
293-
} else {
294-
bug!(
295-
"expression has one or more missing operands \
296-
original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
297-
original_index,
298-
lhs,
299-
op,
300-
rhs,
301-
optional_region,
302-
);
303-
}
304-
}
305-
(counter_expressions, expression_regions.into_iter())
193+
Counter::from_operand(rhs),
194+
),
195+
})
196+
.collect::<Vec<_>>()
197+
}
198+
199+
fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> {
200+
// Find all of the expression IDs that weren't optimized out AND have
201+
// an attached code region, and return the corresponding mapping as a
202+
// counter/region pair.
203+
self.expressions
204+
.iter_enumerated()
205+
.filter_map(|(id, expression)| {
206+
let code_region = expression.as_ref()?.region.as_ref()?;
207+
Some((Counter::expression(id), code_region))
208+
})
209+
.collect::<Vec<_>>()
306210
}
307211

308212
fn unreachable_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> {

compiler/rustc_middle/src/mir/coverage.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,6 @@ impl ExpressionId {
4545
}
4646
}
4747

48-
rustc_index::newtype_index! {
49-
/// MappedExpressionIndex values ascend from zero, and are recalculated indexes based on their
50-
/// array position in the LLVM coverage map "Expressions" array, which is assembled during the
51-
/// "mapgen" process. They cannot be computed algorithmically, from the other `newtype_index`s.
52-
#[derive(HashStable)]
53-
#[max = 0xFFFF_FFFF]
54-
#[debug_format = "MappedExpressionIndex({})"]
55-
pub struct MappedExpressionIndex {}
56-
}
57-
5848
/// Operand of a coverage-counter expression.
5949
///
6050
/// Operands can be a constant zero value, an actual coverage counter, or another

compiler/rustc_middle/src/ty/structural_impls.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,6 @@ TrivialTypeTraversalAndLiftImpls! {
470470
::rustc_target::spec::abi::Abi,
471471
crate::mir::coverage::CounterId,
472472
crate::mir::coverage::ExpressionId,
473-
crate::mir::coverage::MappedExpressionIndex,
474473
crate::mir::Local,
475474
crate::mir::Promoted,
476475
crate::traits::Reveal,

0 commit comments

Comments
 (0)