Skip to content

Commit 041a232

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 change our coverage code in other ways.
1 parent 527c629 commit 041a232

File tree

4 files changed

+64
-167
lines changed

4 files changed

+64
-167
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

+50-143
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

@@ -199,8 +196,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
199196
self.instance
200197
);
201198

199+
let counter_expressions = self.counter_expressions();
200+
// Expression IDs are indices into `self.expressions`, and on the LLVM
201+
// side they will be treated as indices into `counter_expressions`, so
202+
// the two vectors should correspond 1:1.
203+
assert_eq!(self.expressions.len(), counter_expressions.len());
204+
202205
let counter_regions = self.counter_regions();
203-
let (counter_expressions, expression_regions) = self.expressions_with_regions();
206+
let expression_regions = self.expression_regions();
204207
let unreachable_regions = self.unreachable_regions();
205208

206209
let counter_regions =
@@ -216,146 +219,50 @@ impl<'tcx> FunctionCoverage<'tcx> {
216219
})
217220
}
218221

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

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

361268
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
@@ -479,7 +479,6 @@ TrivialTypeTraversalImpls! {
479479
::rustc_target::asm::InlineAsmRegOrRegClass,
480480
crate::mir::coverage::CounterId,
481481
crate::mir::coverage::ExpressionId,
482-
crate::mir::coverage::MappedExpressionIndex,
483482
crate::mir::Local,
484483
crate::mir::Promoted,
485484
crate::traits::Reveal,

0 commit comments

Comments
 (0)