1
1
use crate :: coverageinfo:: ffi:: { Counter , CounterExpression , ExprKind } ;
2
2
3
- use rustc_index:: { IndexSlice , IndexVec } ;
4
- use rustc_middle:: bug;
5
- use rustc_middle:: mir:: coverage:: {
6
- CodeRegion , CounterId , ExpressionId , MappedExpressionIndex , Op , Operand ,
7
- } ;
3
+ use rustc_data_structures:: fx:: FxIndexSet ;
4
+ use rustc_index:: IndexVec ;
5
+ use rustc_middle:: mir:: coverage:: { CodeRegion , CounterId , ExpressionId , Op , Operand } ;
8
6
use rustc_middle:: ty:: Instance ;
9
7
use rustc_middle:: ty:: TyCtxt ;
10
8
@@ -128,6 +126,58 @@ impl<'tcx> FunctionCoverage<'tcx> {
128
126
self . unreachable_regions . push ( region)
129
127
}
130
128
129
+ /// Perform some simplifications to make the final coverage mappings
130
+ /// slightly smaller.
131
+ ///
132
+ /// This method mainly exists to preserve the simplifications that were
133
+ /// already being performed by the Rust-side expression renumbering, so that
134
+ /// the resulting coverage mappings don't get worse.
135
+ pub ( crate ) fn simplify_expressions ( & mut self ) {
136
+ // The set of expressions that either were optimized out entirely, or
137
+ // have zero as both of their operands, and will therefore always have
138
+ // a value of zero. Other expressions that refer to these as operands
139
+ // can have those operands replaced with `Operand::Zero`.
140
+ let mut zero_expressions = FxIndexSet :: default ( ) ;
141
+
142
+ // For each expression, perform simplifications based on lower-numbered
143
+ // expressions, and then update the set of always-zero expressions if
144
+ // necessary.
145
+ // (By construction, expressions can only refer to other expressions
146
+ // that have lower IDs, so one simplification pass is sufficient.)
147
+ for ( id, maybe_expression) in self . expressions . iter_enumerated_mut ( ) {
148
+ let Some ( expression) = maybe_expression else {
149
+ // If an expression is missing, it must have been optimized away,
150
+ // so any operand that refers to it can be replaced with zero.
151
+ zero_expressions. insert ( id) ;
152
+ continue ;
153
+ } ;
154
+
155
+ // If an operand refers to an expression that is always zero, then
156
+ // that operand can be replaced with `Operand::Zero`.
157
+ let maybe_set_operand_to_zero = |operand : & mut Operand | match & * operand {
158
+ Operand :: Expression ( id) if zero_expressions. contains ( id) => {
159
+ * operand = Operand :: Zero ;
160
+ }
161
+ _ => ( ) ,
162
+ } ;
163
+ maybe_set_operand_to_zero ( & mut expression. lhs ) ;
164
+ maybe_set_operand_to_zero ( & mut expression. rhs ) ;
165
+
166
+ // Coverage counter values cannot be negative, so if an expression
167
+ // involves subtraction from zero, assume that its RHS must also be zero.
168
+ // (Do this after simplifications that could set the LHS to zero.)
169
+ if let Expression { lhs : Operand :: Zero , op : Op :: Subtract , .. } = expression {
170
+ expression. rhs = Operand :: Zero ;
171
+ }
172
+
173
+ // After the above simplifications, if both operands are zero, then
174
+ // we know that this expression is always zero too.
175
+ if let Expression { lhs : Operand :: Zero , rhs : Operand :: Zero , .. } = expression {
176
+ zero_expressions. insert ( id) ;
177
+ }
178
+ }
179
+ }
180
+
131
181
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
132
182
/// or not the source code structure changed between different compilations.
133
183
pub fn source_hash ( & self ) -> u64 {
@@ -146,8 +196,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
146
196
self . instance
147
197
) ;
148
198
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
+
149
205
let counter_regions = self . counter_regions ( ) ;
150
- let ( counter_expressions , expression_regions) = self . expressions_with_regions ( ) ;
206
+ let expression_regions = self . expression_regions ( ) ;
151
207
let unreachable_regions = self . unreachable_regions ( ) ;
152
208
153
209
let counter_regions =
@@ -163,149 +219,53 @@ impl<'tcx> FunctionCoverage<'tcx> {
163
219
} )
164
220
}
165
221
166
- fn expressions_with_regions (
167
- & self ,
168
- ) -> ( Vec < CounterExpression > , impl Iterator < Item = ( Counter , & CodeRegion ) > ) {
169
- let mut counter_expressions = Vec :: with_capacity ( self . expressions . len ( ) ) ;
170
- let mut expression_regions = Vec :: with_capacity ( self . expressions . len ( ) ) ;
171
- let mut new_indexes = IndexVec :: from_elem_n ( None , self . expressions . len ( ) ) ;
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`.)
172
229
173
- // This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or
174
- // `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type
175
- // and value.
176
- //
177
- // Expressions will be returned from this function in a sequential vector (array) of
178
- // `CounterExpression`, so the expression IDs must be mapped from their original,
179
- // potentially sparse set of indexes.
180
- //
181
- // An `Expression` as an operand will have already been encountered as an `Expression` with
182
- // operands, so its new_index will already have been generated (as a 1-up index value).
183
- // (If an `Expression` as an operand does not have a corresponding new_index, it was
184
- // probably optimized out, after the expression was injected into the MIR, so it will
185
- // get a `CounterKind::Zero` instead.)
186
- //
187
- // In other words, an `Expression`s at any given index can include other expressions as
188
- // operands, but expression operands can only come from the subset of expressions having
189
- // `expression_index`s lower than the referencing `Expression`. Therefore, it is
190
- // reasonable to look up the new index of an expression operand while the `new_indexes`
191
- // vector is only complete up to the current `ExpressionIndex`.
192
- type NewIndexes = IndexSlice < ExpressionId , Option < MappedExpressionIndex > > ;
193
- let id_to_counter = |new_indexes : & NewIndexes , operand : Operand | match operand {
194
- Operand :: Zero => Some ( Counter :: zero ( ) ) ,
195
- Operand :: Counter ( id) => Some ( Counter :: counter_value_reference ( id) ) ,
196
- Operand :: Expression ( id) => {
197
- self . expressions
198
- . get ( id)
199
- . expect ( "expression id is out of range" )
200
- . as_ref ( )
201
- // If an expression was optimized out, assume it would have produced a count
202
- // of zero. This ensures that expressions dependent on optimized-out
203
- // expressions are still valid.
204
- . map_or ( Some ( Counter :: zero ( ) ) , |_| new_indexes[ id] . map ( Counter :: expression) )
205
- }
206
- } ;
207
-
208
- for ( original_index, expression) in
209
- self . expressions . iter_enumerated ( ) . filter_map ( |( original_index, entry) | {
210
- // Option::map() will return None to filter out missing expressions. This may happen
211
- // if, for example, a MIR-instrumented expression is removed during an optimization.
212
- entry. as_ref ( ) . map ( |expression| ( original_index, expression) )
213
- } )
214
- {
215
- let optional_region = & expression. region ;
216
- let Expression { lhs, op, rhs, .. } = * expression;
217
-
218
- if let Some ( Some ( ( lhs_counter, mut rhs_counter) ) ) = id_to_counter ( & new_indexes, lhs)
219
- . map ( |lhs_counter| {
220
- id_to_counter ( & new_indexes, rhs) . map ( |rhs_counter| ( lhs_counter, rhs_counter) )
221
- } )
222
- {
223
- if lhs_counter. is_zero ( ) && op. is_subtract ( ) {
224
- // The left side of a subtraction was probably optimized out. As an example,
225
- // a branch condition might be evaluated as a constant expression, and the
226
- // branch could be removed, dropping unused counters in the process.
227
- //
228
- // Since counters are unsigned, we must assume the result of the expression
229
- // can be no more and no less than zero. An expression known to evaluate to zero
230
- // does not need to be added to the coverage map.
231
- //
232
- // Coverage test `loops_branches.rs` includes multiple variations of branches
233
- // based on constant conditional (literal `true` or `false`), and demonstrates
234
- // that the expected counts are still correct.
235
- debug ! (
236
- "Expression subtracts from zero (assume unreachable): \
237
- original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
238
- original_index, lhs, op, rhs, optional_region,
239
- ) ;
240
- 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
241
239
}
242
- debug_assert ! (
243
- lhs_counter. is_zero( )
244
- // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16`
245
- || ( ( lhs_counter. zero_based_id( ) as usize )
246
- <= usize :: max( self . counters. len( ) , self . expressions. len( ) ) ) ,
247
- "lhs id={} > both counters.len()={} and expressions.len()={}
248
- ({:?} {:?} {:?})" ,
249
- lhs_counter. zero_based_id( ) ,
250
- self . counters. len( ) ,
251
- self . expressions. len( ) ,
252
- lhs_counter,
253
- op,
254
- rhs_counter,
255
- ) ;
256
-
257
- debug_assert ! (
258
- rhs_counter. is_zero( )
259
- // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16`
260
- || ( ( rhs_counter. zero_based_id( ) as usize )
261
- <= usize :: max( self . counters. len( ) , self . expressions. len( ) ) ) ,
262
- "rhs id={} > both counters.len()={} and expressions.len()={}
263
- ({:?} {:?} {:?})" ,
264
- rhs_counter. zero_based_id( ) ,
265
- self . counters. len( ) ,
266
- self . expressions. len( ) ,
267
- lhs_counter,
268
- op,
269
- rhs_counter,
270
- ) ;
271
-
272
- // Both operands exist. `Expression` operands exist in `self.expressions` and have
273
- // been assigned a `new_index`.
274
- let mapped_expression_index =
275
- MappedExpressionIndex :: from ( counter_expressions. len ( ) ) ;
276
- let expression = CounterExpression :: new (
277
- lhs_counter,
278
- match op {
279
- Op :: Add => ExprKind :: Add ,
280
- Op :: Subtract => ExprKind :: Subtract ,
281
- } ,
282
- rhs_counter,
283
- ) ;
284
- debug ! (
285
- "Adding expression {:?} = {:?}, region: {:?}" ,
286
- mapped_expression_index, expression, optional_region
287
- ) ;
288
- counter_expressions. push ( expression) ;
289
- new_indexes[ original_index] = Some ( mapped_expression_index) ;
290
- if let Some ( region) = optional_region {
291
- 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
+ )
292
250
}
293
- } else {
294
- bug ! (
295
- "expression has one or more missing operands \
296
- original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
297
- original_index,
298
- lhs,
299
- op,
300
- rhs,
301
- optional_region,
302
- ) ;
303
- }
304
- }
305
- ( 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 < _ > > ( )
306
266
}
307
267
308
268
fn unreachable_regions ( & self ) -> impl Iterator < Item = ( Counter , & CodeRegion ) > {
309
- self . unreachable_regions . iter ( ) . map ( |region| ( Counter :: zero ( ) , region) )
269
+ self . unreachable_regions . iter ( ) . map ( |region| ( Counter :: ZERO , region) )
310
270
}
311
271
}
0 commit comments