Skip to content

Commit 527c629

Browse files
committed
coverage: Explicitly simplify coverage expressions in codegen
After coverage instrumentation and MIR transformations, we can sometimes end up with coverage expressions that always have a value of zero. Any expression operand that refers to an always-zero expression can be replaced with a literal `Operand::Zero`, making the emitted coverage mapping data smaller and simpler. This simplification step is mostly redundant with the simplifications performed inline in `expressions_with_regions`, except that it does a slightly more thorough job in some cases (because it checks for always-zero expressions *after* other simplifications). However, adding this simplification step will then let us greatly simplify that code, without affecting the quality of the emitted coverage maps.
1 parent 659575a commit 527c629

File tree

3 files changed

+185
-137
lines changed

3 files changed

+185
-137
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

+53
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
22

3+
use rustc_data_structures::fx::FxIndexSet;
34
use rustc_index::{IndexSlice, IndexVec};
45
use rustc_middle::bug;
56
use rustc_middle::mir::coverage::{
@@ -128,6 +129,58 @@ impl<'tcx> FunctionCoverage<'tcx> {
128129
self.unreachable_regions.push(region)
129130
}
130131

132+
/// Perform some simplifications to make the final coverage mappings
133+
/// slightly smaller.
134+
///
135+
/// This method mainly exists to preserve the simplifications that were
136+
/// already being performed by the Rust-side expression renumbering, so that
137+
/// the resulting coverage mappings don't get worse.
138+
pub(crate) fn simplify_expressions(&mut self) {
139+
// The set of expressions that either were optimized out entirely, or
140+
// have zero as both of their operands, and will therefore always have
141+
// a value of zero. Other expressions that refer to these as operands
142+
// can have those operands replaced with `Operand::Zero`.
143+
let mut zero_expressions = FxIndexSet::default();
144+
145+
// For each expression, perform simplifications based on lower-numbered
146+
// expressions, and then update the set of always-zero expressions if
147+
// necessary.
148+
// (By construction, expressions can only refer to other expressions
149+
// that have lower IDs, so one simplification pass is sufficient.)
150+
for (id, maybe_expression) in self.expressions.iter_enumerated_mut() {
151+
let Some(expression) = maybe_expression else {
152+
// If an expression is missing, it must have been optimized away,
153+
// so any operand that refers to it can be replaced with zero.
154+
zero_expressions.insert(id);
155+
continue;
156+
};
157+
158+
// If an operand refers to an expression that is always zero, then
159+
// that operand can be replaced with `Operand::Zero`.
160+
let maybe_set_operand_to_zero = |operand: &mut Operand| match &*operand {
161+
Operand::Expression(id) if zero_expressions.contains(id) => {
162+
*operand = Operand::Zero;
163+
}
164+
_ => (),
165+
};
166+
maybe_set_operand_to_zero(&mut expression.lhs);
167+
maybe_set_operand_to_zero(&mut expression.rhs);
168+
169+
// Coverage counter values cannot be negative, so if an expression
170+
// involves subtraction from zero, assume that its RHS must also be zero.
171+
// (Do this after simplifications that could set the LHS to zero.)
172+
if let Expression { lhs: Operand::Zero, op: Op::Subtract, .. } = expression {
173+
expression.rhs = Operand::Zero;
174+
}
175+
176+
// After the above simplifications, if both operands are zero, then
177+
// we know that this expression is always zero too.
178+
if let Expression { lhs: Operand::Zero, rhs: Operand::Zero, .. } = expression {
179+
zero_expressions.insert(id);
180+
}
181+
}
182+
}
183+
131184
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
132185
/// or not the source code structure changed between different compilations.
133186
pub fn source_hash(&self) -> u64 {

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
6060

6161
// Encode coverage mappings and generate function records
6262
let mut function_data = Vec::new();
63-
for (instance, function_coverage) in function_coverage_map {
63+
for (instance, mut function_coverage) in function_coverage_map {
6464
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
65+
function_coverage.simplify_expressions();
66+
let function_coverage = function_coverage;
67+
6568
let mangled_function_name = tcx.symbol_name(instance).name;
6669
let source_hash = function_coverage.source_hash();
6770
let is_used = function_coverage.is_used();

0 commit comments

Comments
 (0)