@@ -134,8 +134,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
134
134
}
135
135
136
136
pub(crate) fn finalize(&mut self) {
137
- self.simplify_expressions();
138
-
139
137
// Reorder the collected mappings so that counter mappings are first and
140
138
// zero mappings are last, matching the historical order.
141
139
self.mappings.sort_by_key(|mapping| match mapping.term {
@@ -145,56 +143,76 @@ impl<'tcx> FunctionCoverage<'tcx> {
145
143
});
146
144
}
147
145
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 [`ZeroExpressions`]. Mappings that refer to a zero expression
148
+ /// can instead become mappings to a constant zero value.
150
149
///
151
150
/// This method mainly exists to preserve the simplifications that were
152
151
/// already being performed by the Rust-side expression renumbering, so that
153
152
/// the resulting coverage mappings don't get worse.
154
- fn simplify_expressions(&mut self) {
153
+ fn identify_zero_expressions(& self) -> ZeroExpressions {
155
154
// The set of expressions that either were optimized out entirely, or
156
155
// have zero as both of their operands, and will therefore always have
157
156
// a value of zero. Other expressions that refer to these as operands
158
157
// can have those operands replaced with `CovTerm::Zero`.
159
158
let mut zero_expressions = FxIndexSet::default();
160
159
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.
164
162
// (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 () {
167
165
let Some(expression) = maybe_expression else {
168
166
// If an expression is missing, it must have been optimized away,
169
167
// so any operand that refers to it can be replaced with zero.
170
168
zero_expressions.insert(id);
171
169
continue;
172
170
};
173
171
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 Expression { mut lhs, op, mut rhs } = *expression;
176
+
177
+ // If an expression has an operand that is also an expression, the
178
+ // operand's ID must be strictly lower. This is what lets us find
179
+ // all zero expressions in one pass.
180
+ let assert_operand_expression_is_lower = |operand_id: ExpressionId| {
181
+ assert!(
182
+ operand_id < id,
183
+ "Operand {operand_id:?} should be less than {id:?} in {expression:?}",
184
+ )
185
+ };
186
+
174
187
// If an operand refers to an expression that is always zero, then
175
188
// that operand can be replaced with `CovTerm::Zero`.
176
- let maybe_set_operand_to_zero = |operand: &mut CovTerm| match &*operand {
177
- CovTerm::Expression(id) if zero_expressions.contains(id) => {
178
- *operand = CovTerm::Zero;
189
+ let maybe_set_operand_to_zero = |operand: &mut CovTerm| match *operand {
190
+ CovTerm::Expression(id) => {
191
+ assert_operand_expression_is_lower(id);
192
+ if zero_expressions.contains(&id) {
193
+ *operand = CovTerm::Zero;
194
+ }
179
195
}
180
196
_ => (),
181
197
};
182
- maybe_set_operand_to_zero(&mut expression. lhs);
183
- maybe_set_operand_to_zero(&mut expression. rhs);
198
+ maybe_set_operand_to_zero(&mut lhs);
199
+ maybe_set_operand_to_zero(&mut rhs);
184
200
185
201
// Coverage counter values cannot be negative, so if an expression
186
202
// involves subtraction from zero, assume that its RHS must also be zero.
187
203
// (Do this after simplifications that could set the LHS to zero.)
188
- if let Expression { lhs: CovTerm::Zero, op: Op::Subtract, .. } = expression {
189
- expression. rhs = CovTerm::Zero;
204
+ if lhs == CovTerm::Zero && op == Op::Subtract {
205
+ rhs = CovTerm::Zero;
190
206
}
191
207
192
208
// After the above simplifications, if both operands are zero, then
193
209
// we know that this expression is always zero too.
194
- if let Expression { lhs: CovTerm::Zero, rhs: CovTerm::Zero, .. } = expression {
210
+ if lhs == CovTerm::Zero && rhs == CovTerm::Zero {
195
211
zero_expressions.insert(id);
196
212
}
197
213
}
214
+
215
+ ZeroExpressions(zero_expressions)
198
216
}
199
217
200
218
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
@@ -209,7 +227,9 @@ impl<'tcx> FunctionCoverage<'tcx> {
209
227
pub fn get_expressions_and_counter_regions(
210
228
&self,
211
229
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
212
- let counter_expressions = self.counter_expressions();
230
+ let zero_expressions = self.identify_zero_expressions();
231
+
232
+ let counter_expressions = self.counter_expressions(&zero_expressions);
213
233
// Expression IDs are indices into `self.expressions`, and on the LLVM
214
234
// side they will be treated as indices into `counter_expressions`, so
215
235
// the two vectors should correspond 1:1.
@@ -222,12 +242,17 @@ impl<'tcx> FunctionCoverage<'tcx> {
222
242
223
243
/// Convert this function's coverage expression data into a form that can be
224
244
/// passed through FFI to LLVM.
225
- fn counter_expressions(&self) -> Vec<CounterExpression> {
245
+ fn counter_expressions(&self, zero_expressions: &ZeroExpressions ) -> Vec<CounterExpression> {
226
246
// We know that LLVM will optimize out any unused expressions before
227
247
// producing the final coverage map, so there's no need to do the same
228
248
// thing on the Rust side unless we're confident we can do much better.
229
249
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
230
250
251
+ let counter_from_operand = |operand: CovTerm| match operand {
252
+ CovTerm::Expression(id) if zero_expressions.contains(id) => Counter::ZERO,
253
+ _ => Counter::from_term(operand),
254
+ };
255
+
231
256
self.expressions
232
257
.iter()
233
258
.map(|expression| match expression {
@@ -241,12 +266,12 @@ impl<'tcx> FunctionCoverage<'tcx> {
241
266
&Some(Expression { lhs, op, rhs, .. }) => {
242
267
// Convert the operands and operator as normal.
243
268
CounterExpression::new(
244
- Counter::from_term (lhs),
269
+ counter_from_operand (lhs),
245
270
match op {
246
271
Op::Add => ExprKind::Add,
247
272
Op::Subtract => ExprKind::Subtract,
248
273
},
249
- Counter::from_term (rhs),
274
+ counter_from_operand (rhs),
250
275
)
251
276
}
252
277
})
@@ -262,3 +287,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
262
287
})
263
288
}
264
289
}
290
+
291
+ /// Set of expression IDs that are known to always evaluate to zero.
292
+ /// Any mapping or expression operand that refers to these expressions can have
293
+ /// that reference replaced with a constant zero value.
294
+ struct ZeroExpressions(FxIndexSet<ExpressionId>);
295
+
296
+ impl ZeroExpressions {
297
+ fn contains(&self, id: ExpressionId) -> bool {
298
+ self.0.contains(&id)
299
+ }
300
+ }
0 commit comments