Skip to content

Commit 8432019

Browse files
committed
Auto merge of rust-lang#134029 - Zalathar:zero, r=<try>
coverage: Use a query to find counters/expressions that must be zero As of rust-lang#133446, this query (`coverage_ids_info`) determines which counter/expression IDs are unused. So with only a little extra work, we can take the code that was using that information to determine which coverage counters/expressions must be zero, and move that inside the query as well. There should be no change in compiler output.
2 parents f33a8c6 + 3a35fb6 commit 8432019

File tree

7 files changed

+169
-223
lines changed

7 files changed

+169
-223
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,38 @@
11
use rustc_data_structures::captures::Captures;
2-
use rustc_data_structures::fx::FxIndexSet;
3-
use rustc_index::bit_set::BitSet;
4-
use rustc_middle::mir::CoverageIdsInfo;
52
use rustc_middle::mir::coverage::{
6-
CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping, MappingKind, Op,
3+
CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping, MappingKind, Op,
74
SourceRegion,
85
};
9-
use rustc_middle::ty::Instance;
10-
use tracing::debug;
116

127
use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
138

14-
/// Holds all of the coverage mapping data associated with a function instance,
15-
/// collected during traversal of `Coverage` statements in the function's MIR.
16-
#[derive(Debug)]
17-
pub(crate) struct FunctionCoverageCollector<'tcx> {
18-
/// Coverage info that was attached to this function by the instrumentor.
19-
function_coverage_info: &'tcx FunctionCoverageInfo,
20-
ids_info: &'tcx CoverageIdsInfo,
21-
is_used: bool,
9+
pub(crate) struct FunctionCoverage<'tcx> {
10+
pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo,
11+
/// If `None`, the corresponding function is unused.
12+
ids_info: Option<&'tcx CoverageIdsInfo>,
2213
}
2314

24-
impl<'tcx> FunctionCoverageCollector<'tcx> {
25-
/// Creates a new set of coverage data for a used (called) function.
26-
pub(crate) fn new(
27-
instance: Instance<'tcx>,
28-
function_coverage_info: &'tcx FunctionCoverageInfo,
29-
ids_info: &'tcx CoverageIdsInfo,
30-
) -> Self {
31-
Self::create(instance, function_coverage_info, ids_info, true)
32-
}
33-
34-
/// Creates a new set of coverage data for an unused (never called) function.
35-
pub(crate) fn unused(
36-
instance: Instance<'tcx>,
37-
function_coverage_info: &'tcx FunctionCoverageInfo,
38-
ids_info: &'tcx CoverageIdsInfo,
39-
) -> Self {
40-
Self::create(instance, function_coverage_info, ids_info, false)
41-
}
42-
43-
fn create(
44-
instance: Instance<'tcx>,
15+
impl<'tcx> FunctionCoverage<'tcx> {
16+
pub(crate) fn new_used(
4517
function_coverage_info: &'tcx FunctionCoverageInfo,
4618
ids_info: &'tcx CoverageIdsInfo,
47-
is_used: bool,
4819
) -> Self {
49-
let num_counters = function_coverage_info.num_counters;
50-
let num_expressions = function_coverage_info.expressions.len();
51-
debug!(
52-
"FunctionCoverage::create(instance={instance:?}) has \
53-
num_counters={num_counters}, num_expressions={num_expressions}, is_used={is_used}"
54-
);
55-
56-
Self { function_coverage_info, ids_info, is_used }
57-
}
58-
59-
/// Identify expressions that will always have a value of zero, and note
60-
/// their IDs in [`ZeroExpressions`]. Mappings that refer to a zero expression
61-
/// can instead become mappings to a constant zero value.
62-
///
63-
/// This method mainly exists to preserve the simplifications that were
64-
/// already being performed by the Rust-side expression renumbering, so that
65-
/// the resulting coverage mappings don't get worse.
66-
fn identify_zero_expressions(&self) -> ZeroExpressions {
67-
// The set of expressions that either were optimized out entirely, or
68-
// have zero as both of their operands, and will therefore always have
69-
// a value of zero. Other expressions that refer to these as operands
70-
// can have those operands replaced with `CovTerm::Zero`.
71-
let mut zero_expressions = ZeroExpressions::default();
72-
73-
// Simplify a copy of each expression based on lower-numbered expressions,
74-
// and then update the set of always-zero expressions if necessary.
75-
// (By construction, expressions can only refer to other expressions
76-
// that have lower IDs, so one pass is sufficient.)
77-
for (id, expression) in self.function_coverage_info.expressions.iter_enumerated() {
78-
if !self.is_used || !self.ids_info.expressions_seen.contains(id) {
79-
// If an expression was not seen, it must have been optimized away,
80-
// so any operand that refers to it can be replaced with zero.
81-
zero_expressions.insert(id);
82-
continue;
83-
}
84-
85-
// We don't need to simplify the actual expression data in the
86-
// expressions list; we can just simplify a temporary copy and then
87-
// use that to update the set of always-zero expressions.
88-
let Expression { mut lhs, op, mut rhs } = *expression;
89-
90-
// If an expression has an operand that is also an expression, the
91-
// operand's ID must be strictly lower. This is what lets us find
92-
// all zero expressions in one pass.
93-
let assert_operand_expression_is_lower = |operand_id: ExpressionId| {
94-
assert!(
95-
operand_id < id,
96-
"Operand {operand_id:?} should be less than {id:?} in {expression:?}",
97-
)
98-
};
99-
100-
// If an operand refers to a counter or expression that is always
101-
// zero, then that operand can be replaced with `CovTerm::Zero`.
102-
let maybe_set_operand_to_zero = |operand: &mut CovTerm| {
103-
if let CovTerm::Expression(id) = *operand {
104-
assert_operand_expression_is_lower(id);
105-
}
106-
107-
if is_zero_term(&self.ids_info.counters_seen, &zero_expressions, *operand) {
108-
*operand = CovTerm::Zero;
109-
}
110-
};
111-
maybe_set_operand_to_zero(&mut lhs);
112-
maybe_set_operand_to_zero(&mut rhs);
113-
114-
// Coverage counter values cannot be negative, so if an expression
115-
// involves subtraction from zero, assume that its RHS must also be zero.
116-
// (Do this after simplifications that could set the LHS to zero.)
117-
if lhs == CovTerm::Zero && op == Op::Subtract {
118-
rhs = CovTerm::Zero;
119-
}
120-
121-
// After the above simplifications, if both operands are zero, then
122-
// we know that this expression is always zero too.
123-
if lhs == CovTerm::Zero && rhs == CovTerm::Zero {
124-
zero_expressions.insert(id);
125-
}
126-
}
127-
128-
zero_expressions
20+
Self { function_coverage_info, ids_info: Some(ids_info) }
12921
}
13022

131-
pub(crate) fn into_finished(self) -> FunctionCoverage<'tcx> {
132-
let zero_expressions = self.identify_zero_expressions();
133-
let FunctionCoverageCollector { function_coverage_info, ids_info, is_used, .. } = self;
134-
135-
FunctionCoverage { function_coverage_info, ids_info, is_used, zero_expressions }
23+
pub(crate) fn new_unused(function_coverage_info: &'tcx FunctionCoverageInfo) -> Self {
24+
Self { function_coverage_info, ids_info: None }
13625
}
137-
}
138-
139-
pub(crate) struct FunctionCoverage<'tcx> {
140-
pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo,
141-
ids_info: &'tcx CoverageIdsInfo,
142-
is_used: bool,
143-
144-
zero_expressions: ZeroExpressions,
145-
}
14626

147-
impl<'tcx> FunctionCoverage<'tcx> {
14827
/// Returns true for a used (called) function, and false for an unused function.
14928
pub(crate) fn is_used(&self) -> bool {
150-
self.is_used
29+
self.ids_info.is_some()
15130
}
15231

15332
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
15433
/// or not the source code structure changed between different compilations.
15534
pub(crate) fn source_hash(&self) -> u64 {
156-
if self.is_used { self.function_coverage_info.function_source_hash } else { 0 }
35+
if self.is_used() { self.function_coverage_info.function_source_hash } else { 0 }
15736
}
15837

15938
/// Convert this function's coverage expression data into a form that can be
@@ -196,37 +75,10 @@ impl<'tcx> FunctionCoverage<'tcx> {
19675
}
19776

19877
fn is_zero_term(&self, term: CovTerm) -> bool {
199-
!self.is_used || is_zero_term(&self.ids_info.counters_seen, &self.zero_expressions, term)
200-
}
201-
}
202-
203-
/// Set of expression IDs that are known to always evaluate to zero.
204-
/// Any mapping or expression operand that refers to these expressions can have
205-
/// that reference replaced with a constant zero value.
206-
#[derive(Default)]
207-
struct ZeroExpressions(FxIndexSet<ExpressionId>);
208-
209-
impl ZeroExpressions {
210-
fn insert(&mut self, id: ExpressionId) {
211-
self.0.insert(id);
212-
}
213-
214-
fn contains(&self, id: ExpressionId) -> bool {
215-
self.0.contains(&id)
216-
}
217-
}
218-
219-
/// Returns `true` if the given term is known to have a value of zero, taking
220-
/// into account knowledge of which counters are unused and which expressions
221-
/// are always zero.
222-
fn is_zero_term(
223-
counters_seen: &BitSet<CounterId>,
224-
zero_expressions: &ZeroExpressions,
225-
term: CovTerm,
226-
) -> bool {
227-
match term {
228-
CovTerm::Zero => true,
229-
CovTerm::Counter(id) => !counters_seen.contains(id),
230-
CovTerm::Expression(id) => zero_expressions.contains(id),
78+
match self.ids_info {
79+
Some(ids_info) => ids_info.is_zero_term(term),
80+
// This function is unused, so all coverage counters/expressions are zero.
81+
None => true,
82+
}
23183
}
23284
}

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+5-15
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_target::spec::HasTargetSpec;
2020
use tracing::debug;
2121

2222
use crate::common::CodegenCx;
23-
use crate::coverageinfo::map_data::{FunctionCoverage, FunctionCoverageCollector};
23+
use crate::coverageinfo::map_data::FunctionCoverage;
2424
use crate::coverageinfo::{ffi, llvm_cov};
2525
use crate::llvm;
2626

@@ -63,16 +63,11 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
6363
None => return,
6464
};
6565
if function_coverage_map.is_empty() {
66-
// This module has no functions with coverage instrumentation
66+
// This CGU has no functions with coverage instrumentation.
6767
return;
6868
}
6969

70-
let function_coverage_entries = function_coverage_map
71-
.into_iter()
72-
.map(|(instance, function_coverage)| (instance, function_coverage.into_finished()))
73-
.collect::<Vec<_>>();
74-
75-
let all_file_names = function_coverage_entries
70+
let all_file_names = function_coverage_map
7671
.iter()
7772
.map(|(_, fn_cov)| fn_cov.function_coverage_info.body_span)
7873
.map(|span| span_file_name(tcx, span));
@@ -92,7 +87,7 @@ pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {
9287
let mut unused_function_names = Vec::new();
9388

9489
// Encode coverage mappings and generate function records
95-
for (instance, function_coverage) in function_coverage_entries {
90+
for (instance, function_coverage) in function_coverage_map {
9691
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
9792

9893
let mangled_function_name = tcx.symbol_name(instance).name;
@@ -536,11 +531,6 @@ fn add_unused_function_coverage<'tcx>(
536531
);
537532

538533
// An unused function's mappings will all be rewritten to map to zero.
539-
let function_coverage = FunctionCoverageCollector::unused(
540-
instance,
541-
function_coverage_info,
542-
tcx.coverage_ids_info(instance.def),
543-
);
544-
534+
let function_coverage = FunctionCoverage::new_unused(function_coverage_info);
545535
cx.coverage_cx().function_coverage_map.borrow_mut().insert(instance, function_coverage);
546536
}

compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tracing::{debug, instrument};
1313

1414
use crate::builder::Builder;
1515
use crate::common::CodegenCx;
16-
use crate::coverageinfo::map_data::FunctionCoverageCollector;
16+
use crate::coverageinfo::map_data::FunctionCoverage;
1717
use crate::llvm;
1818

1919
pub(crate) mod ffi;
@@ -24,8 +24,7 @@ mod mapgen;
2424
/// Extra per-CGU context/state needed for coverage instrumentation.
2525
pub(crate) struct CguCoverageContext<'ll, 'tcx> {
2626
/// Coverage data for each instrumented function identified by DefId.
27-
pub(crate) function_coverage_map:
28-
RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
27+
pub(crate) function_coverage_map: RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverage<'tcx>>>,
2928
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
3029
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
3130

@@ -42,9 +41,7 @@ impl<'ll, 'tcx> CguCoverageContext<'ll, 'tcx> {
4241
}
4342
}
4443

45-
fn take_function_coverage_map(
46-
&self,
47-
) -> FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>> {
44+
fn take_function_coverage_map(&self) -> FxIndexMap<Instance<'tcx>, FunctionCoverage<'tcx>> {
4845
self.function_coverage_map.replace(FxIndexMap::default())
4946
}
5047

@@ -161,8 +158,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
161158
// This includes functions that were not partitioned into this CGU,
162159
// but were MIR-inlined into one of this CGU's functions.
163160
coverage_cx.function_coverage_map.borrow_mut().entry(instance).or_insert_with(|| {
164-
FunctionCoverageCollector::new(
165-
instance,
161+
FunctionCoverage::new_used(
166162
function_coverage_info,
167163
bx.tcx.coverage_ids_info(instance.def),
168164
)

compiler/rustc_middle/src/mir/coverage.rs

+39
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::fmt::{self, Debug, Formatter};
44

55
use rustc_index::IndexVec;
6+
use rustc_index::bit_set::BitSet;
67
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
78
use rustc_span::Span;
89

@@ -310,3 +311,41 @@ pub struct MCDCDecisionSpan {
310311
pub decision_depth: u16,
311312
pub num_conditions: usize,
312313
}
314+
315+
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
316+
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
317+
/// have had a chance to potentially remove some of them.
318+
///
319+
/// Used by the `coverage_ids_info` query.
320+
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable)]
321+
pub struct CoverageIdsInfo {
322+
pub counters_seen: BitSet<CounterId>,
323+
pub zero_expressions: BitSet<ExpressionId>,
324+
}
325+
326+
impl CoverageIdsInfo {
327+
/// Coverage codegen needs to know how many coverage counters are ever
328+
/// incremented within a function, so that it can set the `num-counters`
329+
/// argument of the `llvm.instrprof.increment` intrinsic.
330+
///
331+
/// This may be less than the highest counter ID emitted by the
332+
/// InstrumentCoverage MIR pass, if the highest-numbered counter increments
333+
/// were removed by MIR optimizations.
334+
pub fn num_counters_after_mir_opts(&self) -> u32 {
335+
// FIXME(Zalathar): Currently this treats an unused counter as "used"
336+
// if its ID is less than that of the highest counter that really is
337+
// used. Fixing this would require adding a renumbering step somewhere.
338+
self.counters_seen.last_set_in(..).map_or(0, |max| max.as_u32() + 1)
339+
}
340+
341+
/// Returns `true` if the given term is known to have a value of zero, taking
342+
/// into account knowledge of which counters are unused and which expressions
343+
/// are always zero.
344+
pub fn is_zero_term(&self, term: CovTerm) -> bool {
345+
match term {
346+
CovTerm::Zero => true,
347+
CovTerm::Counter(id) => !self.counters_seen.contains(id),
348+
CovTerm::Expression(id) => self.zero_expressions.contains(id),
349+
}
350+
}
351+
}

0 commit comments

Comments
 (0)