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