Skip to content

Commit 6fd5a5b

Browse files
authored
Unrolled build for rust-lang#136053
Rollup merge of rust-lang#136053 - Zalathar:defer-counters, r=saethlin coverage: Defer part of counter-creation until codegen Follow-up to rust-lang#135481 and rust-lang#135873. One of the pleasant properties of the new counter-assignment algorithm is that we can stop partway through the process, store the intermediate state in MIR, and then resume the rest of the algorithm during codegen. This lets it take into account which parts of the control-flow graph were eliminated by MIR opts, resulting in fewer physical counters and simpler counter expressions. Those improvements end up completely obsoleting much larger chunks of code that were previously responsible for cleaning up the coverage metadata after MIR opts, while also doing a more thorough cleanup job. (That change also unlocks some further simplifications that I've kept out of this PR to limit its scope.)
2 parents 80c0919 + bd855b6 commit 6fd5a5b

28 files changed

+355
-664
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs

+24-31
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use rustc_codegen_ssa::traits::{
1111
BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods,
1212
};
1313
use rustc_middle::mir::coverage::{
14-
CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping, MappingKind, Op,
14+
BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping,
15+
MappingKind, Op,
1516
};
1617
use rustc_middle::ty::{Instance, TyCtxt};
1718
use rustc_span::Span;
@@ -53,7 +54,7 @@ pub(crate) fn prepare_covfun_record<'tcx>(
5354
let fn_cov_info = tcx.instance_mir(instance.def).function_coverage_info.as_deref()?;
5455
let ids_info = tcx.coverage_ids_info(instance.def)?;
5556

56-
let expressions = prepare_expressions(fn_cov_info, ids_info, is_used);
57+
let expressions = prepare_expressions(ids_info);
5758

5859
let mut covfun = CovfunRecord {
5960
mangled_function_name: tcx.symbol_name(instance).name,
@@ -75,26 +76,14 @@ pub(crate) fn prepare_covfun_record<'tcx>(
7576
}
7677

7778
/// Convert the function's coverage-counter expressions into a form suitable for FFI.
78-
fn prepare_expressions(
79-
fn_cov_info: &FunctionCoverageInfo,
80-
ids_info: &CoverageIdsInfo,
81-
is_used: bool,
82-
) -> Vec<ffi::CounterExpression> {
83-
// If any counters or expressions were removed by MIR opts, replace their
84-
// terms with zero.
85-
let counter_for_term = |term| {
86-
if !is_used || ids_info.is_zero_term(term) {
87-
ffi::Counter::ZERO
88-
} else {
89-
ffi::Counter::from_term(term)
90-
}
91-
};
79+
fn prepare_expressions(ids_info: &CoverageIdsInfo) -> Vec<ffi::CounterExpression> {
80+
let counter_for_term = ffi::Counter::from_term;
9281

9382
// We know that LLVM will optimize out any unused expressions before
9483
// producing the final coverage map, so there's no need to do the same
9584
// thing on the Rust side unless we're confident we can do much better.
9685
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
97-
fn_cov_info
86+
ids_info
9887
.expressions
9988
.iter()
10089
.map(move |&Expression { lhs, op, rhs }| ffi::CounterExpression {
@@ -136,11 +125,16 @@ fn fill_region_tables<'tcx>(
136125

137126
// For each counter/region pair in this function+file, convert it to a
138127
// form suitable for FFI.
139-
let is_zero_term = |term| !covfun.is_used || ids_info.is_zero_term(term);
140128
for &Mapping { ref kind, span } in &fn_cov_info.mappings {
141-
// If the mapping refers to counters/expressions that were removed by
142-
// MIR opts, replace those occurrences with zero.
143-
let kind = kind.map_terms(|term| if is_zero_term(term) { CovTerm::Zero } else { term });
129+
// If this function is unused, replace all counters with zero.
130+
let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
131+
let term = if covfun.is_used {
132+
ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
133+
} else {
134+
CovTerm::Zero
135+
};
136+
ffi::Counter::from_term(term)
137+
};
144138

145139
// Convert the `Span` into coordinates that we can pass to LLVM, or
146140
// discard the span if conversion fails. In rare, cases _all_ of a
@@ -154,23 +148,22 @@ fn fill_region_tables<'tcx>(
154148
continue;
155149
}
156150

157-
match kind {
158-
MappingKind::Code(term) => {
159-
code_regions
160-
.push(ffi::CodeRegion { cov_span, counter: ffi::Counter::from_term(term) });
151+
match *kind {
152+
MappingKind::Code { bcb } => {
153+
code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) });
161154
}
162-
MappingKind::Branch { true_term, false_term } => {
155+
MappingKind::Branch { true_bcb, false_bcb } => {
163156
branch_regions.push(ffi::BranchRegion {
164157
cov_span,
165-
true_counter: ffi::Counter::from_term(true_term),
166-
false_counter: ffi::Counter::from_term(false_term),
158+
true_counter: counter_for_bcb(true_bcb),
159+
false_counter: counter_for_bcb(false_bcb),
167160
});
168161
}
169-
MappingKind::MCDCBranch { true_term, false_term, mcdc_params } => {
162+
MappingKind::MCDCBranch { true_bcb, false_bcb, mcdc_params } => {
170163
mcdc_branch_regions.push(ffi::MCDCBranchRegion {
171164
cov_span,
172-
true_counter: ffi::Counter::from_term(true_term),
173-
false_counter: ffi::Counter::from_term(false_term),
165+
true_counter: counter_for_bcb(true_bcb),
166+
false_counter: counter_for_bcb(false_bcb),
174167
mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params),
175168
});
176169
}

compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs

+6-17
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,21 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
160160
CoverageKind::SpanMarker | CoverageKind::BlockMarker { .. } => unreachable!(
161161
"marker statement {kind:?} should have been removed by CleanupPostBorrowck"
162162
),
163-
CoverageKind::CounterIncrement { id } => {
164-
// The number of counters passed to `llvm.instrprof.increment` might
165-
// be smaller than the number originally inserted by the instrumentor,
166-
// if some high-numbered counters were removed by MIR optimizations.
167-
// If so, LLVM's profiler runtime will use fewer physical counters.
168-
let num_counters = ids_info.num_counters_after_mir_opts();
169-
assert!(
170-
num_counters as usize <= function_coverage_info.num_counters,
171-
"num_counters disagreement: query says {num_counters} but function info only has {}",
172-
function_coverage_info.num_counters
173-
);
174-
163+
CoverageKind::VirtualCounter { bcb }
164+
if let Some(&id) = ids_info.phys_counter_for_node.get(&bcb) =>
165+
{
175166
let fn_name = bx.get_pgo_func_name_var(instance);
176167
let hash = bx.const_u64(function_coverage_info.function_source_hash);
177-
let num_counters = bx.const_u32(num_counters);
168+
let num_counters = bx.const_u32(ids_info.num_counters);
178169
let index = bx.const_u32(id.as_u32());
179170
debug!(
180171
"codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})",
181172
fn_name, hash, num_counters, index,
182173
);
183174
bx.instrprof_increment(fn_name, hash, num_counters, index);
184175
}
185-
CoverageKind::ExpressionUsed { id: _ } => {
186-
// Expression-used statements are markers that are handled by
187-
// `coverage_ids_info`, so there's nothing to codegen here.
188-
}
176+
// If a BCB doesn't have an associated physical counter, there's nothing to codegen.
177+
CoverageKind::VirtualCounter { .. } => {}
189178
CoverageKind::CondBitmapUpdate { index, decision_depth } => {
190179
let cond_bitmap = coverage_cx
191180
.try_get_mcdc_condition_bitmap(&instance, decision_depth)

compiler/rustc_codegen_llvm/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#![feature(extern_types)]
1414
#![feature(file_buffered)]
1515
#![feature(hash_raw_entry)]
16+
#![feature(if_let_guard)]
1617
#![feature(impl_trait_in_assoc_type)]
1718
#![feature(iter_intersperse)]
1819
#![feature(let_chains)]

compiler/rustc_middle/src/mir/coverage.rs

+66-73
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
33
use std::fmt::{self, Debug, Formatter};
44

5-
use rustc_index::IndexVec;
6-
use rustc_index::bit_set::DenseBitSet;
5+
use rustc_data_structures::fx::FxIndexMap;
6+
use rustc_index::{Idx, IndexVec};
77
use rustc_macros::{HashStable, TyDecodable, TyEncodable};
88
use rustc_span::Span;
99

@@ -103,23 +103,12 @@ pub enum CoverageKind {
103103
/// Should be erased before codegen (at some point after `InstrumentCoverage`).
104104
BlockMarker { id: BlockMarkerId },
105105

106-
/// Marks the point in MIR control flow represented by a coverage counter.
106+
/// Marks its enclosing basic block with the ID of the coverage graph node
107+
/// that it was part of during the `InstrumentCoverage` MIR pass.
107108
///
108-
/// This is eventually lowered to `llvm.instrprof.increment` in LLVM IR.
109-
///
110-
/// If this statement does not survive MIR optimizations, any mappings that
111-
/// refer to this counter can have those references simplified to zero.
112-
CounterIncrement { id: CounterId },
113-
114-
/// Marks the point in MIR control-flow represented by a coverage expression.
115-
///
116-
/// If this statement does not survive MIR optimizations, any mappings that
117-
/// refer to this expression can have those references simplified to zero.
118-
///
119-
/// (This is only inserted for expression IDs that are directly used by
120-
/// mappings. Intermediate expressions with no direct mappings are
121-
/// retained/zeroed based on whether they are transitively used.)
122-
ExpressionUsed { id: ExpressionId },
109+
/// During codegen, this might be lowered to `llvm.instrprof.increment` or
110+
/// to a no-op, depending on the outcome of counter-creation.
111+
VirtualCounter { bcb: BasicCoverageBlock },
123112

124113
/// Marks the point in MIR control flow represented by a evaluated condition.
125114
///
@@ -138,8 +127,7 @@ impl Debug for CoverageKind {
138127
match self {
139128
SpanMarker => write!(fmt, "SpanMarker"),
140129
BlockMarker { id } => write!(fmt, "BlockMarker({:?})", id.index()),
141-
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
142-
ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
130+
VirtualCounter { bcb } => write!(fmt, "VirtualCounter({bcb:?})"),
143131
CondBitmapUpdate { index, decision_depth } => {
144132
write!(fmt, "CondBitmapUpdate(index={:?}, depth={:?})", index, decision_depth)
145133
}
@@ -179,34 +167,19 @@ pub struct Expression {
179167
#[derive(TyEncodable, TyDecodable, Hash, HashStable)]
180168
pub enum MappingKind {
181169
/// Associates a normal region of code with a counter/expression/zero.
182-
Code(CovTerm),
170+
Code { bcb: BasicCoverageBlock },
183171
/// Associates a branch region with separate counters for true and false.
184-
Branch { true_term: CovTerm, false_term: CovTerm },
172+
Branch { true_bcb: BasicCoverageBlock, false_bcb: BasicCoverageBlock },
185173
/// Associates a branch region with separate counters for true and false.
186-
MCDCBranch { true_term: CovTerm, false_term: CovTerm, mcdc_params: ConditionInfo },
174+
MCDCBranch {
175+
true_bcb: BasicCoverageBlock,
176+
false_bcb: BasicCoverageBlock,
177+
mcdc_params: ConditionInfo,
178+
},
187179
/// Associates a decision region with a bitmap and number of conditions.
188180
MCDCDecision(DecisionInfo),
189181
}
190182

191-
impl MappingKind {
192-
/// Returns a copy of this mapping kind, in which all coverage terms have
193-
/// been replaced with ones returned by the given function.
194-
pub fn map_terms(&self, map_fn: impl Fn(CovTerm) -> CovTerm) -> Self {
195-
match *self {
196-
Self::Code(term) => Self::Code(map_fn(term)),
197-
Self::Branch { true_term, false_term } => {
198-
Self::Branch { true_term: map_fn(true_term), false_term: map_fn(false_term) }
199-
}
200-
Self::MCDCBranch { true_term, false_term, mcdc_params } => Self::MCDCBranch {
201-
true_term: map_fn(true_term),
202-
false_term: map_fn(false_term),
203-
mcdc_params,
204-
},
205-
Self::MCDCDecision(param) => Self::MCDCDecision(param),
206-
}
207-
}
208-
}
209-
210183
#[derive(Clone, Debug)]
211184
#[derive(TyEncodable, TyDecodable, Hash, HashStable)]
212185
pub struct Mapping {
@@ -222,10 +195,15 @@ pub struct Mapping {
222195
pub struct FunctionCoverageInfo {
223196
pub function_source_hash: u64,
224197
pub body_span: Span,
225-
pub num_counters: usize,
226-
pub mcdc_bitmap_bits: usize,
227-
pub expressions: IndexVec<ExpressionId, Expression>,
198+
199+
/// Used in conjunction with `priority_list` to create physical counters
200+
/// and counter expressions, after MIR optimizations.
201+
pub node_flow_data: NodeFlowData<BasicCoverageBlock>,
202+
pub priority_list: Vec<BasicCoverageBlock>,
203+
228204
pub mappings: Vec<Mapping>,
205+
206+
pub mcdc_bitmap_bits: usize,
229207
/// The depth of the deepest decision is used to know how many
230208
/// temp condbitmaps should be allocated for the function.
231209
pub mcdc_num_condition_bitmaps: usize,
@@ -292,40 +270,55 @@ pub struct MCDCDecisionSpan {
292270
pub num_conditions: usize,
293271
}
294272

295-
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
296-
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
297-
/// have had a chance to potentially remove some of them.
273+
/// Contains information needed during codegen, obtained by inspecting the
274+
/// function's MIR after MIR optimizations.
298275
///
299-
/// Used by the `coverage_ids_info` query.
276+
/// Returned by the `coverage_ids_info` query.
300277
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable)]
301278
pub struct CoverageIdsInfo {
302-
pub counters_seen: DenseBitSet<CounterId>,
303-
pub zero_expressions: DenseBitSet<ExpressionId>,
279+
pub num_counters: u32,
280+
pub phys_counter_for_node: FxIndexMap<BasicCoverageBlock, CounterId>,
281+
pub term_for_bcb: IndexVec<BasicCoverageBlock, Option<CovTerm>>,
282+
pub expressions: IndexVec<ExpressionId, Expression>,
304283
}
305284

306-
impl CoverageIdsInfo {
307-
/// Coverage codegen needs to know how many coverage counters are ever
308-
/// incremented within a function, so that it can set the `num-counters`
309-
/// argument of the `llvm.instrprof.increment` intrinsic.
285+
rustc_index::newtype_index! {
286+
/// During the `InstrumentCoverage` MIR pass, a BCB is a node in the
287+
/// "coverage graph", which is a refinement of the MIR control-flow graph
288+
/// that merges or omits some blocks that aren't relevant to coverage.
310289
///
311-
/// This may be less than the highest counter ID emitted by the
312-
/// InstrumentCoverage MIR pass, if the highest-numbered counter increments
313-
/// were removed by MIR optimizations.
314-
pub fn num_counters_after_mir_opts(&self) -> u32 {
315-
// FIXME(Zalathar): Currently this treats an unused counter as "used"
316-
// if its ID is less than that of the highest counter that really is
317-
// used. Fixing this would require adding a renumbering step somewhere.
318-
self.counters_seen.last_set_in(..).map_or(0, |max| max.as_u32() + 1)
290+
/// After that pass is complete, the coverage graph no longer exists, so a
291+
/// BCB is effectively an opaque ID.
292+
#[derive(HashStable)]
293+
#[encodable]
294+
#[orderable]
295+
#[debug_format = "bcb{}"]
296+
pub struct BasicCoverageBlock {
297+
const START_BCB = 0;
319298
}
299+
}
320300

321-
/// Returns `true` if the given term is known to have a value of zero, taking
322-
/// into account knowledge of which counters are unused and which expressions
323-
/// are always zero.
324-
pub fn is_zero_term(&self, term: CovTerm) -> bool {
325-
match term {
326-
CovTerm::Zero => true,
327-
CovTerm::Counter(id) => !self.counters_seen.contains(id),
328-
CovTerm::Expression(id) => self.zero_expressions.contains(id),
329-
}
330-
}
301+
/// Data representing a view of some underlying graph, in which each node's
302+
/// successors have been merged into a single "supernode".
303+
///
304+
/// The resulting supernodes have no obvious meaning on their own.
305+
/// However, merging successor nodes means that a node's out-edges can all
306+
/// be combined into a single out-edge, whose flow is the same as the flow
307+
/// (execution count) of its corresponding node in the original graph.
308+
///
309+
/// With all node flows now in the original graph now represented as edge flows
310+
/// in the merged graph, it becomes possible to analyze the original node flows
311+
/// using techniques for analyzing edge flows.
312+
#[derive(Clone, Debug)]
313+
#[derive(TyEncodable, TyDecodable, Hash, HashStable)]
314+
pub struct NodeFlowData<Node: Idx> {
315+
/// Maps each node to the supernode that contains it, indicated by some
316+
/// arbitrary "root" node that is part of that supernode.
317+
pub supernodes: IndexVec<Node, Node>,
318+
/// For each node, stores the single supernode that all of its successors
319+
/// have been merged into.
320+
///
321+
/// (Note that each node in a supernode can potentially have a _different_
322+
/// successor supernode from its peers.)
323+
pub succ_supernodes: IndexVec<Node, Node>,
331324
}

compiler/rustc_middle/src/mir/pretty.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -619,13 +619,9 @@ fn write_function_coverage_info(
619619
function_coverage_info: &coverage::FunctionCoverageInfo,
620620
w: &mut dyn io::Write,
621621
) -> io::Result<()> {
622-
let coverage::FunctionCoverageInfo { body_span, expressions, mappings, .. } =
623-
function_coverage_info;
622+
let coverage::FunctionCoverageInfo { body_span, mappings, .. } = function_coverage_info;
624623

625624
writeln!(w, "{INDENT}coverage body span: {body_span:?}")?;
626-
for (id, expression) in expressions.iter_enumerated() {
627-
writeln!(w, "{INDENT}coverage {id:?} => {expression:?};")?;
628-
}
629625
for coverage::Mapping { kind, span } in mappings {
630626
writeln!(w, "{INDENT}coverage {kind:?} => {span:?};")?;
631627
}

compiler/rustc_middle/src/query/mod.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -614,9 +614,16 @@ rustc_queries! {
614614
feedable
615615
}
616616

617-
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
618-
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
619-
/// have had a chance to potentially remove some of them.
617+
/// Scans through a function's MIR after MIR optimizations, to prepare the
618+
/// information needed by codegen when `-Cinstrument-coverage` is active.
619+
///
620+
/// This includes the details of where to insert `llvm.instrprof.increment`
621+
/// intrinsics, and the expression tables to be embedded in the function's
622+
/// coverage metadata.
623+
///
624+
/// FIXME(Zalathar): This query's purpose has drifted a bit and should
625+
/// probably be renamed, but that can wait until after the potential
626+
/// follow-ups to #136053 have settled down.
620627
///
621628
/// Returns `None` for functions that were not instrumented.
622629
query coverage_ids_info(key: ty::InstanceKind<'tcx>) -> Option<&'tcx mir::coverage::CoverageIdsInfo> {

0 commit comments

Comments
 (0)