Skip to content

Commit 30e0910

Browse files
committed
coverage: Make expression simplification non-destructive
1 parent f2bed73 commit 30e0910

File tree

1 file changed

+37
-14
lines changed
  • compiler/rustc_codegen_llvm/src/coverageinfo

1 file changed

+37
-14
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

+37-14
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
134134
}
135135

136136
pub(crate) fn finalize(&mut self) {
137-
self.simplify_expressions();
138-
139137
// Reorder the collected mappings so that counter mappings are first and
140138
// zero mappings are last, matching the historical order.
141139
self.mappings.sort_by_key(|mapping| match mapping.term {
@@ -145,32 +143,37 @@ impl<'tcx> FunctionCoverage<'tcx> {
145143
});
146144
}
147145

148-
/// Perform some simplifications to make the final coverage mappings
149-
/// slightly smaller.
146+
/// Identify expressions that will always have a value of zero, and note
147+
/// their IDs in `zero_expressions`. Mappings that refer to a zero expression
148+
/// can instead become mappings to a constant zero value.
150149
///
151150
/// This method mainly exists to preserve the simplifications that were
152151
/// already being performed by the Rust-side expression renumbering, so that
153152
/// the resulting coverage mappings don't get worse.
154-
fn simplify_expressions(&mut self) {
153+
fn identify_zero_expressions(&self) -> ZeroExpressions {
155154
// The set of expressions that either were optimized out entirely, or
156155
// have zero as both of their operands, and will therefore always have
157156
// a value of zero. Other expressions that refer to these as operands
158157
// can have those operands replaced with `CovTerm::Zero`.
159158
let mut zero_expressions = FxIndexSet::default();
160159

161-
// For each expression, perform simplifications based on lower-numbered
162-
// expressions, and then update the set of always-zero expressions if
163-
// necessary.
160+
// Simplify a copy of each expression based on lower-numbered expressions,
161+
// and then update the set of always-zero expressions if necessary.
164162
// (By construction, expressions can only refer to other expressions
165-
// that have lower IDs, so one simplification pass is sufficient.)
166-
for (id, maybe_expression) in self.expressions.iter_enumerated_mut() {
163+
// that have lower IDs, so one pass is sufficient.)
164+
for (id, maybe_expression) in self.expressions.iter_enumerated() {
167165
let Some(expression) = maybe_expression else {
168166
// If an expression is missing, it must have been optimized away,
169167
// so any operand that refers to it can be replaced with zero.
170168
zero_expressions.insert(id);
171169
continue;
172170
};
173171

172+
// We don't need to simplify the actual expression data in the
173+
// expressions list; we can just simplify a temporary copy and then
174+
// use that to update the set of always-zero expressions.
175+
let mut expression = expression.clone();
176+
174177
// If an operand refers to an expression that is always zero, then
175178
// that operand can be replaced with `CovTerm::Zero`.
176179
let maybe_set_operand_to_zero = |operand: &mut CovTerm| match &*operand {
@@ -195,6 +198,8 @@ impl<'tcx> FunctionCoverage<'tcx> {
195198
zero_expressions.insert(id);
196199
}
197200
}
201+
202+
ZeroExpressions(zero_expressions)
198203
}
199204

200205
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
@@ -209,7 +214,9 @@ impl<'tcx> FunctionCoverage<'tcx> {
209214
pub fn get_expressions_and_counter_regions(
210215
&self,
211216
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
212-
let counter_expressions = self.counter_expressions();
217+
let zero_expressions = self.identify_zero_expressions();
218+
219+
let counter_expressions = self.counter_expressions(&zero_expressions);
213220
// Expression IDs are indices into `self.expressions`, and on the LLVM
214221
// side they will be treated as indices into `counter_expressions`, so
215222
// the two vectors should correspond 1:1.
@@ -222,12 +229,17 @@ impl<'tcx> FunctionCoverage<'tcx> {
222229

223230
/// Convert this function's coverage expression data into a form that can be
224231
/// passed through FFI to LLVM.
225-
fn counter_expressions(&self) -> Vec<CounterExpression> {
232+
fn counter_expressions(&self, zero_expressions: &ZeroExpressions) -> Vec<CounterExpression> {
226233
// We know that LLVM will optimize out any unused expressions before
227234
// producing the final coverage map, so there's no need to do the same
228235
// thing on the Rust side unless we're confident we can do much better.
229236
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
230237

238+
let counter_from_operand = |operand: CovTerm| match operand {
239+
CovTerm::Expression(id) if zero_expressions.contains(id) => Counter::ZERO,
240+
_ => Counter::from_term(operand),
241+
};
242+
231243
self.expressions
232244
.iter()
233245
.map(|expression| match expression {
@@ -241,12 +253,12 @@ impl<'tcx> FunctionCoverage<'tcx> {
241253
&Some(Expression { lhs, op, rhs, .. }) => {
242254
// Convert the operands and operator as normal.
243255
CounterExpression::new(
244-
Counter::from_term(lhs),
256+
counter_from_operand(lhs),
245257
match op {
246258
Op::Add => ExprKind::Add,
247259
Op::Subtract => ExprKind::Subtract,
248260
},
249-
Counter::from_term(rhs),
261+
counter_from_operand(rhs),
250262
)
251263
}
252264
})
@@ -262,3 +274,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
262274
})
263275
}
264276
}
277+
278+
/// Set of expression IDs that are known to always evaluate to zero.
279+
/// Any mapping or expression operand that refers to these expressions can have
280+
/// that reference replaced with a constant zero value.
281+
struct ZeroExpressions(FxIndexSet<ExpressionId>);
282+
283+
impl ZeroExpressions {
284+
fn contains(&self, id: ExpressionId) -> bool {
285+
self.0.contains(&id)
286+
}
287+
}

0 commit comments

Comments
 (0)