Skip to content

Commit 13b2d60

Browse files
committed
coverage: Store expression data in function coverage info
Even though expression details are now stored in the info structure, we still need to inject `ExpressionUsed` statements into MIR, because if one is missing during codegen then we know that it was optimized out and we can remap all of its associated code regions to zero.
1 parent 7d38f4a commit 13b2d60

File tree

9 files changed

+90
-173
lines changed

9 files changed

+90
-173
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs

-11
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,6 @@ pub struct CounterExpression {
7373
pub rhs: Counter,
7474
}
7575

76-
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-
82-
pub fn new(lhs: Counter, kind: ExprKind, rhs: Counter) -> Self {
83-
Self { kind, lhs, rhs }
84-
}
85-
}
86-
8776
/// Corresponds to enum `llvm::coverage::CounterMappingRegion::RegionKind`.
8877
///
8978
/// Must match the layout of `LLVMRustCounterMappingRegionKind`.

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

+40-65
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,11 @@ use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
22

33
use rustc_data_structures::fx::FxIndexSet;
44
use rustc_index::bit_set::BitSet;
5-
use rustc_index::IndexVec;
65
use rustc_middle::mir::coverage::{
7-
CodeRegion, CounterId, CovTerm, ExpressionId, FunctionCoverageInfo, Mapping, Op,
6+
CodeRegion, CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping, Op,
87
};
98
use rustc_middle::ty::Instance;
109

11-
#[derive(Clone, Debug, PartialEq)]
12-
pub struct Expression {
13-
lhs: CovTerm,
14-
op: Op,
15-
rhs: CovTerm,
16-
}
17-
1810
/// Holds all of the coverage mapping data associated with a function instance,
1911
/// collected during traversal of `Coverage` statements in the function's MIR.
2012
#[derive(Debug)]
@@ -26,7 +18,12 @@ pub struct FunctionCoverage<'tcx> {
2618
/// Tracks which counters have been seen, so that we can identify mappings
2719
/// to counters that were optimized out, and set them to zero.
2820
counters_seen: BitSet<CounterId>,
29-
expressions: IndexVec<ExpressionId, Option<Expression>>,
21+
/// Contains all expression IDs that have been seen in an `ExpressionUsed`
22+
/// coverage statement, plus all expression IDs that aren't directly used
23+
/// by any mappings (and therefore do not have expression-used statements).
24+
/// After MIR traversal is finished, we can conclude that any IDs missing
25+
/// from this set must have had their statements deleted by MIR opts.
26+
expressions_seen: BitSet<ExpressionId>,
3027
}
3128

3229
impl<'tcx> FunctionCoverage<'tcx> {
@@ -52,16 +49,30 @@ impl<'tcx> FunctionCoverage<'tcx> {
5249
is_used: bool,
5350
) -> Self {
5451
let num_counters = function_coverage_info.num_counters;
55-
let num_expressions = function_coverage_info.num_expressions;
52+
let num_expressions = function_coverage_info.expressions.len();
5653
debug!(
5754
"FunctionCoverage::create(instance={instance:?}) has \
5855
num_counters={num_counters}, num_expressions={num_expressions}, is_used={is_used}"
5956
);
57+
58+
// Create a filled set of expression IDs, so that expressions not
59+
// directly used by mappings will be treated as "seen".
60+
// (If they end up being unused, LLVM will delete them for us.)
61+
let mut expressions_seen = BitSet::new_filled(num_expressions);
62+
// For each expression ID that is directly used by one or more mappings,
63+
// mark it as not-yet-seen. This indicates that we expect to see a
64+
// corresponding `ExpressionUsed` statement during MIR traversal.
65+
for Mapping { term, .. } in &function_coverage_info.mappings {
66+
if let &CovTerm::Expression(id) = term {
67+
expressions_seen.remove(id);
68+
}
69+
}
70+
6071
Self {
6172
function_coverage_info,
6273
is_used,
6374
counters_seen: BitSet::new_empty(num_counters),
64-
expressions: IndexVec::from_elem_n(None, num_expressions),
75+
expressions_seen,
6576
}
6677
}
6778

@@ -76,35 +87,10 @@ impl<'tcx> FunctionCoverage<'tcx> {
7687
self.counters_seen.insert(id);
7788
}
7889

79-
/// Adds information about a coverage expression.
90+
/// Marks an expression ID as having been seen in an expression-used statement.
8091
#[instrument(level = "debug", skip(self))]
81-
pub(crate) fn add_counter_expression(
82-
&mut self,
83-
expression_id: ExpressionId,
84-
lhs: CovTerm,
85-
op: Op,
86-
rhs: CovTerm,
87-
) {
88-
debug_assert!(
89-
expression_id.as_usize() < self.expressions.len(),
90-
"expression_id {} is out of range for expressions.len() = {}
91-
for {:?}",
92-
expression_id.as_usize(),
93-
self.expressions.len(),
94-
self,
95-
);
96-
97-
let expression = Expression { lhs, op, rhs };
98-
let slot = &mut self.expressions[expression_id];
99-
match slot {
100-
None => *slot = Some(expression),
101-
// If this expression ID slot has already been filled, it should
102-
// contain identical information.
103-
Some(ref previous_expression) => assert_eq!(
104-
previous_expression, &expression,
105-
"add_counter_expression: expression for id changed"
106-
),
107-
}
92+
pub(crate) fn mark_expression_id_seen(&mut self, id: ExpressionId) {
93+
self.expressions_seen.insert(id);
10894
}
10995

11096
/// Identify expressions that will always have a value of zero, and note
@@ -125,13 +111,13 @@ impl<'tcx> FunctionCoverage<'tcx> {
125111
// and then update the set of always-zero expressions if necessary.
126112
// (By construction, expressions can only refer to other expressions
127113
// that have lower IDs, so one pass is sufficient.)
128-
for (id, maybe_expression) in self.expressions.iter_enumerated() {
129-
let Some(expression) = maybe_expression else {
130-
// If an expression is missing, it must have been optimized away,
114+
for (id, expression) in self.function_coverage_info.expressions.iter_enumerated() {
115+
if !self.expressions_seen.contains(id) {
116+
// If an expression was not seen, it must have been optimized away,
131117
// so any operand that refers to it can be replaced with zero.
132118
zero_expressions.insert(id);
133119
continue;
134-
};
120+
}
135121

136122
// We don't need to simplify the actual expression data in the
137123
// expressions list; we can just simplify a temporary copy and then
@@ -197,7 +183,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
197183
// Expression IDs are indices into `self.expressions`, and on the LLVM
198184
// side they will be treated as indices into `counter_expressions`, so
199185
// the two vectors should correspond 1:1.
200-
assert_eq!(self.expressions.len(), counter_expressions.len());
186+
assert_eq!(self.function_coverage_info.expressions.len(), counter_expressions.len());
201187

202188
let counter_regions = self.counter_regions(zero_expressions);
203189

@@ -217,27 +203,16 @@ impl<'tcx> FunctionCoverage<'tcx> {
217203
_ => Counter::from_term(operand),
218204
};
219205

220-
self.expressions
206+
self.function_coverage_info
207+
.expressions
221208
.iter()
222-
.map(|expression| match expression {
223-
None => {
224-
// This expression ID was allocated, but we never saw the
225-
// actual expression, so it must have been optimized out.
226-
// Replace it with a dummy expression, and let LLVM take
227-
// care of omitting it from the expression list.
228-
CounterExpression::DUMMY
229-
}
230-
&Some(Expression { lhs, op, rhs, .. }) => {
231-
// Convert the operands and operator as normal.
232-
CounterExpression::new(
233-
counter_from_operand(lhs),
234-
match op {
235-
Op::Add => ExprKind::Add,
236-
Op::Subtract => ExprKind::Subtract,
237-
},
238-
counter_from_operand(rhs),
239-
)
240-
}
209+
.map(|&Expression { lhs, op, rhs }| CounterExpression {
210+
lhs: counter_from_operand(lhs),
211+
kind: match op {
212+
Op::Add => ExprKind::Add,
213+
Op::Subtract => ExprKind::Subtract,
214+
},
215+
rhs: counter_from_operand(rhs),
241216
})
242217
.collect::<Vec<_>>()
243218
}

compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
145145
);
146146
bx.instrprof_increment(fn_name, hash, num_counters, index);
147147
}
148-
CoverageKind::Expression { id, lhs, op, rhs } => {
149-
func_coverage.add_counter_expression(id, lhs, op, rhs);
148+
CoverageKind::ExpressionUsed { id } => {
149+
func_coverage.mark_expression_id_seen(id);
150150
}
151151
}
152152
}

compiler/rustc_middle/src/mir/coverage.rs

+21-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Metadata from source code coverage analysis and instrumentation.
22
3+
use rustc_index::IndexVec;
34
use rustc_macros::HashStable;
45
use rustc_span::Symbol;
56

@@ -68,32 +69,24 @@ pub enum CoverageKind {
6869
/// If this statement does not survive MIR optimizations, any mappings that
6970
/// refer to this counter can have those references simplified to zero.
7071
CounterIncrement { id: CounterId },
71-
Expression {
72-
/// ID of this coverage-counter expression within its enclosing function.
73-
/// Other expressions in the same function can refer to it as an operand.
74-
id: ExpressionId,
75-
lhs: CovTerm,
76-
op: Op,
77-
rhs: CovTerm,
78-
},
72+
73+
/// Marks the point in MIR control-flow represented by a coverage expression.
74+
///
75+
/// If this statement does not survive MIR optimizations, any mappings that
76+
/// refer to this expression can have those references simplified to zero.
77+
///
78+
/// (This is only inserted for expression IDs that are directly used by
79+
/// mappings. Intermediate expressions with no direct mappings are
80+
/// retained/zeroed based on whether they are transitively used.)
81+
ExpressionUsed { id: ExpressionId },
7982
}
8083

8184
impl Debug for CoverageKind {
8285
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
8386
use CoverageKind::*;
8487
match self {
8588
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
86-
Expression { id, lhs, op, rhs } => write!(
87-
fmt,
88-
"Expression({:?}) = {:?} {} {:?}",
89-
id.index(),
90-
lhs,
91-
match op {
92-
Op::Add => "+",
93-
Op::Subtract => "-",
94-
},
95-
rhs,
96-
),
89+
ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
9790
}
9891
}
9992
}
@@ -135,6 +128,14 @@ impl Op {
135128
}
136129
}
137130

131+
#[derive(Clone, Debug)]
132+
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
133+
pub struct Expression {
134+
pub lhs: CovTerm,
135+
pub op: Op,
136+
pub rhs: CovTerm,
137+
}
138+
138139
#[derive(Clone, Debug)]
139140
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
140141
pub struct Mapping {
@@ -157,7 +158,7 @@ pub struct Mapping {
157158
pub struct FunctionCoverageInfo {
158159
pub function_source_hash: u64,
159160
pub num_counters: usize,
160-
pub num_expressions: usize,
161161

162+
pub expressions: IndexVec<ExpressionId, Expression>,
162163
pub mappings: Vec<Mapping>,
163164
}

compiler/rustc_middle/src/mir/pretty.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,11 @@ fn write_function_coverage_info(
504504
function_coverage_info: &coverage::FunctionCoverageInfo,
505505
w: &mut dyn io::Write,
506506
) -> io::Result<()> {
507-
let coverage::FunctionCoverageInfo { mappings, .. } = function_coverage_info;
507+
let coverage::FunctionCoverageInfo { expressions, mappings, .. } = function_coverage_info;
508508

509+
for (id, expression) in expressions.iter_enumerated() {
510+
writeln!(w, "{INDENT}coverage {id:?} => {expression:?};")?;
511+
}
509512
for coverage::Mapping { term, code_region } in mappings {
510513
writeln!(w, "{INDENT}coverage {term:?} => {code_region:?};")?;
511514
}

0 commit comments

Comments
 (0)