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