Skip to content

Commit 91ff05c

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 d22bfb4 commit 91ff05c

File tree

4 files changed

+58
-164
lines changed

4 files changed

+58
-164
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs

+14-13
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

+44-140
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
22

33
use rustc_data_structures::fx::FxIndexSet;
4-
use rustc_index::{IndexSlice, IndexVec};
5-
use rustc_middle::bug;
6-
use rustc_middle::mir::coverage::{
7-
CodeRegion, CounterId, ExpressionId, MappedExpressionIndex, Op, Operand,
8-
};
4+
use rustc_index::IndexVec;
5+
use rustc_middle::mir::coverage::{CodeRegion, CounterId, ExpressionId, Op, Operand};
96
use rustc_middle::ty::Instance;
107
use rustc_middle::ty::TyCtxt;
118

@@ -195,8 +192,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
195192
self.instance
196193
);
197194

195+
let counter_expressions = self.counter_expressions();
196+
// Expression IDs are indices into `self.expressions`, and on the LLVM
197+
// side they will be treated as indices into `counter_expressions`, so
198+
// the two vectors should correspond 1:1.
199+
assert_eq!(self.expressions.len(), counter_expressions.len());
200+
198201
let counter_regions = self.counter_regions();
199-
let (counter_expressions, expression_regions) = self.expressions_with_regions();
202+
let expression_regions = self.expression_regions();
200203
let unreachable_regions = self.unreachable_regions();
201204

202205
let counter_regions =
@@ -212,146 +215,47 @@ impl<'tcx> FunctionCoverage<'tcx> {
212215
})
213216
}
214217

215-
fn expressions_with_regions(
216-
&self,
217-
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
218-
let mut counter_expressions = Vec::with_capacity(self.expressions.len());
219-
let mut expression_regions = Vec::with_capacity(self.expressions.len());
220-
let mut new_indexes = IndexVec::from_elem_n(None, self.expressions.len());
221-
222-
// This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or
223-
// `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type
224-
// and value.
225-
//
226-
// Expressions will be returned from this function in a sequential vector (array) of
227-
// `CounterExpression`, so the expression IDs must be mapped from their original,
228-
// potentially sparse set of indexes.
229-
//
230-
// An `Expression` as an operand will have already been encountered as an `Expression` with
231-
// operands, so its new_index will already have been generated (as a 1-up index value).
232-
// (If an `Expression` as an operand does not have a corresponding new_index, it was
233-
// probably optimized out, after the expression was injected into the MIR, so it will
234-
// get a `CounterKind::Zero` instead.)
235-
//
236-
// In other words, an `Expression`s at any given index can include other expressions as
237-
// operands, but expression operands can only come from the subset of expressions having
238-
// `expression_index`s lower than the referencing `Expression`. Therefore, it is
239-
// reasonable to look up the new index of an expression operand while the `new_indexes`
240-
// vector is only complete up to the current `ExpressionIndex`.
241-
type NewIndexes = IndexSlice<ExpressionId, Option<MappedExpressionIndex>>;
242-
let id_to_counter = |new_indexes: &NewIndexes, operand: Operand| match operand {
243-
Operand::Zero => Some(Counter::ZERO),
244-
Operand::Counter(id) => Some(Counter::counter_value_reference(id)),
245-
Operand::Expression(id) => {
246-
self.expressions
247-
.get(id)
248-
.expect("expression id is out of range")
249-
.as_ref()
250-
// If an expression was optimized out, assume it would have produced a count
251-
// of zero. This ensures that expressions dependent on optimized-out
252-
// expressions are still valid.
253-
.map_or(Some(Counter::ZERO), |_| new_indexes[id].map(Counter::expression))
254-
}
255-
};
256-
257-
for (original_index, expression) in
258-
self.expressions.iter_enumerated().filter_map(|(original_index, entry)| {
259-
// Option::map() will return None to filter out missing expressions. This may happen
260-
// if, for example, a MIR-instrumented expression is removed during an optimization.
261-
entry.as_ref().map(|expression| (original_index, expression))
262-
})
263-
{
264-
let optional_region = &expression.region;
265-
let Expression { lhs, op, rhs, .. } = *expression;
218+
/// Convert this function's coverage expression data into a form that can be
219+
/// passed through FFI to LLVM.
220+
fn counter_expressions(&self) -> Vec<CounterExpression> {
221+
// We know that LLVM will optimize out any unused expressions before
222+
// producing the final coverage map, so there's no need to do the same
223+
// thing on the Rust side unless we're confident we can do much better.
224+
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
266225

267-
if let Some(Some((lhs_counter, mut rhs_counter))) = id_to_counter(&new_indexes, lhs)
268-
.map(|lhs_counter| {
269-
id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter))
270-
})
271-
{
272-
if lhs_counter.is_zero() && op.is_subtract() {
273-
// The left side of a subtraction was probably optimized out. As an example,
274-
// a branch condition might be evaluated as a constant expression, and the
275-
// branch could be removed, dropping unused counters in the process.
276-
//
277-
// Since counters are unsigned, we must assume the result of the expression
278-
// can be no more and no less than zero. An expression known to evaluate to zero
279-
// does not need to be added to the coverage map.
280-
//
281-
// Coverage test `loops_branches.rs` includes multiple variations of branches
282-
// based on constant conditional (literal `true` or `false`), and demonstrates
283-
// that the expected counts are still correct.
284-
debug!(
285-
"Expression subtracts from zero (assume unreachable): \
286-
original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
287-
original_index, lhs, op, rhs, optional_region,
288-
);
289-
rhs_counter = Counter::ZERO;
226+
self.expressions
227+
.iter()
228+
.map(|expression| match expression {
229+
None => {
230+
// This expression ID was allocated, but we never saw the
231+
// actual expression, so it must have been optimized out.
232+
// Replace it with a dummy expression, and let LLVM take
233+
// care of omitting it from the expression list.
234+
CounterExpression::DUMMY
290235
}
291-
debug_assert!(
292-
lhs_counter.is_zero()
293-
// Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16`
294-
|| ((lhs_counter.zero_based_id() as usize)
295-
<= usize::max(self.counters.len(), self.expressions.len())),
296-
"lhs id={} > both counters.len()={} and expressions.len()={}
297-
({:?} {:?} {:?})",
298-
lhs_counter.zero_based_id(),
299-
self.counters.len(),
300-
self.expressions.len(),
301-
lhs_counter,
302-
op,
303-
rhs_counter,
304-
);
305-
306-
debug_assert!(
307-
rhs_counter.is_zero()
308-
// Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16`
309-
|| ((rhs_counter.zero_based_id() as usize)
310-
<= usize::max(self.counters.len(), self.expressions.len())),
311-
"rhs id={} > both counters.len()={} and expressions.len()={}
312-
({:?} {:?} {:?})",
313-
rhs_counter.zero_based_id(),
314-
self.counters.len(),
315-
self.expressions.len(),
316-
lhs_counter,
317-
op,
318-
rhs_counter,
319-
);
320-
321-
// Both operands exist. `Expression` operands exist in `self.expressions` and have
322-
// been assigned a `new_index`.
323-
let mapped_expression_index =
324-
MappedExpressionIndex::from(counter_expressions.len());
325-
let expression = CounterExpression::new(
326-
lhs_counter,
236+
&Some(Expression { lhs, op, rhs, .. }) => CounterExpression::new(
237+
Counter::from_operand(lhs),
327238
match op {
328239
Op::Add => ExprKind::Add,
329240
Op::Subtract => ExprKind::Subtract,
330241
},
331-
rhs_counter,
332-
);
333-
debug!(
334-
"Adding expression {:?} = {:?}, region: {:?}",
335-
mapped_expression_index, expression, optional_region
336-
);
337-
counter_expressions.push(expression);
338-
new_indexes[original_index] = Some(mapped_expression_index);
339-
if let Some(region) = optional_region {
340-
expression_regions.push((Counter::expression(mapped_expression_index), region));
341-
}
342-
} else {
343-
bug!(
344-
"expression has one or more missing operands \
345-
original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
346-
original_index,
347-
lhs,
348-
op,
349-
rhs,
350-
optional_region,
351-
);
352-
}
353-
}
354-
(counter_expressions, expression_regions.into_iter())
242+
Counter::from_operand(rhs),
243+
),
244+
})
245+
.collect::<Vec<_>>()
246+
}
247+
248+
fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> {
249+
// Find all of the expression IDs that weren't optimized out AND have
250+
// an attached code region, and return the corresponding mapping as a
251+
// counter/region pair.
252+
self.expressions
253+
.iter_enumerated()
254+
.filter_map(|(id, expression)| {
255+
let code_region = expression.as_ref()?.region.as_ref()?;
256+
Some((Counter::expression(id), code_region))
257+
})
258+
.collect::<Vec<_>>()
355259
}
356260

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

compiler/rustc_middle/src/mir/coverage.rs

-10
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

-1
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,6 @@ TrivialTypeTraversalAndLiftImpls! {
469469
::rustc_target::spec::abi::Abi,
470470
crate::mir::coverage::CounterId,
471471
crate::mir::coverage::ExpressionId,
472-
crate::mir::coverage::MappedExpressionIndex,
473472
crate::mir::Local,
474473
crate::mir::Promoted,
475474
crate::traits::Reveal,

0 commit comments

Comments
 (0)