Skip to content

Commit d22bfb4

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 f4d73b5 commit d22bfb4

File tree

3 files changed

+180
-137
lines changed

3 files changed

+180
-137
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

Lines changed: 49 additions & 0 deletions
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,54 @@ 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+
pub(crate) fn simplify_expressions(&mut self) {
135+
// The set of expressions that either were optimized out entirely, or
136+
// have zero as both of their operands, and will therefore always have
137+
// a value of zero. Other expressions that refer to these as operands
138+
// can have those operands replaced with `Operand::Zero`.
139+
let mut zero_expressions = FxIndexSet::default();
140+
141+
// For each expression, perform simplifications based on lower-numbered
142+
// expressions, and then update the set of always-zero expressions if
143+
// necessary.
144+
// (By construction, expressions can only refer to other expressions
145+
// that have lower IDs, so one simplification pass is sufficient.)
146+
for (id, maybe_expression) in self.expressions.iter_enumerated_mut() {
147+
let Some(expression) = maybe_expression else {
148+
// If an expression is missing, it must have been optimized away,
149+
// so any operand that refers to it can be replaced with zero.
150+
zero_expressions.insert(id);
151+
continue;
152+
};
153+
154+
// If an operand refers to an expression that is always zero, then
155+
// that operand can be replaced with `Operand::Zero`.
156+
let maybe_set_operand_to_zero = |operand: &mut Operand| match &*operand {
157+
Operand::Expression(id) if zero_expressions.contains(id) => {
158+
*operand = Operand::Zero;
159+
}
160+
_ => (),
161+
};
162+
maybe_set_operand_to_zero(&mut expression.lhs);
163+
maybe_set_operand_to_zero(&mut expression.rhs);
164+
165+
// Coverage counter values cannot be negative, so if an expression
166+
// involves subtraction from zero, assume that its RHS must also be zero.
167+
// (Do this after simplifications that could set the LHS to zero.)
168+
if let Expression { lhs: Operand::Zero, op: Op::Subtract, .. } = expression {
169+
expression.rhs = Operand::Zero;
170+
}
171+
172+
// After the above simplifications, if both operands are zero, then
173+
// we know that this expression is always zero too.
174+
if let Expression { lhs: Operand::Zero, rhs: Operand::Zero, .. } = expression {
175+
zero_expressions.insert(id);
176+
}
177+
}
178+
}
179+
131180
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
132181
/// or not the source code structure changed between different compilations.
133182
pub fn source_hash(&self) -> u64 {

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
5959

6060
// Encode coverage mappings and generate function records
6161
let mut function_data = Vec::new();
62-
for (instance, function_coverage) in function_coverage_map {
62+
for (instance, mut function_coverage) in function_coverage_map {
6363
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
64+
function_coverage.simplify_expressions();
65+
6466
let mangled_function_name = tcx.symbol_name(instance).name;
6567
let source_hash = function_coverage.source_hash();
6668
let is_used = function_coverage.is_used();

0 commit comments

Comments
 (0)