Skip to content

Commit 725afd2

Browse files
committed
Auto merge of rust-lang#115671 - Zalathar:mapgen, r=wesleywiser
coverage: Clean up encoding of per-function coverage mapping payloads This PR contains several small improvements to the code in `rustc_codegen_llvm::coverageinfo::mapgen` that prepares a function's coverage mappings for FFI, and passes them over to LLVM to be encoded into a vector of bytes. These changes are in preparation for some future changes to the coverage implementation, but they should all stand on their own as worthwhile. There shouldn't be any changes to the resulting coverage mappings, as verified by the existing `tests/coverage-map` and `tests/run-coverage` suites. The changes are mostly independent of each other, though they are indirectly affected by the indentation changes made when introducing `GlobalFileTable`.
2 parents b4e54c6 + 6e968b1 commit 725afd2

File tree

2 files changed

+103
-89
lines changed

2 files changed

+103
-89
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+102-89
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::common::CodegenCx;
22
use crate::coverageinfo;
3-
use crate::coverageinfo::ffi::{Counter, CounterExpression, CounterMappingRegion};
3+
use crate::coverageinfo::ffi::CounterMappingRegion;
4+
use crate::coverageinfo::map_data::FunctionCoverage;
45
use crate::llvm;
56

67
use rustc_codegen_ssa::traits::ConstMethods;
78
use rustc_data_structures::fx::FxIndexSet;
89
use rustc_hir::def::DefKind;
910
use rustc_hir::def_id::DefId;
10-
use rustc_llvm::RustString;
11+
use rustc_index::IndexVec;
1112
use rustc_middle::bug;
1213
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
1314
use rustc_middle::mir::coverage::CodeRegion;
@@ -55,7 +56,7 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
5556
return;
5657
}
5758

58-
let mut mapgen = CoverageMapGenerator::new(tcx);
59+
let mut global_file_table = GlobalFileTable::new(tcx);
5960

6061
// Encode coverage mappings and generate function records
6162
let mut function_data = Vec::new();
@@ -64,12 +65,9 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
6465
let mangled_function_name = tcx.symbol_name(instance).name;
6566
let source_hash = function_coverage.source_hash();
6667
let is_used = function_coverage.is_used();
67-
let (expressions, counter_regions) =
68-
function_coverage.get_expressions_and_counter_regions();
6968

70-
let coverage_mapping_buffer = llvm::build_byte_buffer(|coverage_mapping_buffer| {
71-
mapgen.write_coverage_mapping(expressions, counter_regions, coverage_mapping_buffer);
72-
});
69+
let coverage_mapping_buffer =
70+
encode_mappings_for_function(&mut global_file_table, &function_coverage);
7371

7472
if coverage_mapping_buffer.is_empty() {
7573
if function_coverage.is_used() {
@@ -87,19 +85,14 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
8785
}
8886

8987
// Encode all filenames referenced by counters/expressions in this module
90-
let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| {
91-
coverageinfo::write_filenames_section_to_buffer(
92-
mapgen.filenames.iter().map(Symbol::as_str),
93-
filenames_buffer,
94-
);
95-
});
88+
let filenames_buffer = global_file_table.into_filenames_buffer();
9689

9790
let filenames_size = filenames_buffer.len();
9891
let filenames_val = cx.const_bytes(&filenames_buffer);
9992
let filenames_ref = coverageinfo::hash_bytes(&filenames_buffer);
10093

10194
// Generate the LLVM IR representation of the coverage map and store it in a well-known global
102-
let cov_data_val = mapgen.generate_coverage_map(cx, version, filenames_size, filenames_val);
95+
let cov_data_val = generate_coverage_map(cx, version, filenames_size, filenames_val);
10396

10497
let covfun_section_name = coverageinfo::covfun_section_name(cx);
10598
for (mangled_function_name, source_hash, is_used, coverage_mapping_buffer) in function_data {
@@ -118,13 +111,13 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
118111
coverageinfo::save_cov_data_to_mod(cx, cov_data_val);
119112
}
120113

121-
struct CoverageMapGenerator {
122-
filenames: FxIndexSet<Symbol>,
114+
struct GlobalFileTable {
115+
global_file_table: FxIndexSet<Symbol>,
123116
}
124117

125-
impl CoverageMapGenerator {
118+
impl GlobalFileTable {
126119
fn new(tcx: TyCtxt<'_>) -> Self {
127-
let mut filenames = FxIndexSet::default();
120+
let mut global_file_table = FxIndexSet::default();
128121
// LLVM Coverage Mapping Format version 6 (zero-based encoded as 5)
129122
// requires setting the first filename to the compilation directory.
130123
// Since rustc generates coverage maps with relative paths, the
@@ -133,94 +126,114 @@ impl CoverageMapGenerator {
133126
let working_dir = Symbol::intern(
134127
&tcx.sess.opts.working_dir.remapped_path_if_available().to_string_lossy(),
135128
);
136-
filenames.insert(working_dir);
137-
Self { filenames }
129+
global_file_table.insert(working_dir);
130+
Self { global_file_table }
138131
}
139132

140-
/// Using the `expressions` and `counter_regions` collected for the current function, generate
141-
/// the `mapping_regions` and `virtual_file_mapping`, and capture any new filenames. Then use
142-
/// LLVM APIs to encode the `virtual_file_mapping`, `expressions`, and `mapping_regions` into
143-
/// the given `coverage_mapping` byte buffer, compliant with the LLVM Coverage Mapping format.
144-
fn write_coverage_mapping<'a>(
145-
&mut self,
146-
expressions: Vec<CounterExpression>,
147-
counter_regions: impl Iterator<Item = (Counter, &'a CodeRegion)>,
148-
coverage_mapping_buffer: &RustString,
149-
) {
150-
let mut counter_regions = counter_regions.collect::<Vec<_>>();
151-
if counter_regions.is_empty() {
152-
return;
153-
}
133+
fn global_file_id_for_file_name(&mut self, file_name: Symbol) -> u32 {
134+
let (global_file_id, _) = self.global_file_table.insert_full(file_name);
135+
global_file_id as u32
136+
}
154137

155-
let mut virtual_file_mapping = Vec::new();
156-
let mut mapping_regions = Vec::new();
157-
let mut current_file_name = None;
158-
let mut current_file_id = 0;
159-
160-
// Convert the list of (Counter, CodeRegion) pairs to an array of `CounterMappingRegion`, sorted
161-
// by filename and position. Capture any new files to compute the `CounterMappingRegion`s
162-
// `file_id` (indexing files referenced by the current function), and construct the
163-
// function-specific `virtual_file_mapping` from `file_id` to its index in the module's
164-
// `filenames` array.
165-
counter_regions.sort_unstable_by_key(|(_counter, region)| *region);
166-
for (counter, region) in counter_regions {
167-
let CodeRegion { file_name, start_line, start_col, end_line, end_col } = *region;
168-
let same_file = current_file_name.is_some_and(|p| p == file_name);
169-
if !same_file {
170-
if current_file_name.is_some() {
171-
current_file_id += 1;
172-
}
173-
current_file_name = Some(file_name);
174-
debug!(" file_id: {} = '{:?}'", current_file_id, file_name);
175-
let (filenames_index, _) = self.filenames.insert_full(file_name);
176-
virtual_file_mapping.push(filenames_index as u32);
177-
}
178-
debug!("Adding counter {:?} to map for {:?}", counter, region);
138+
fn into_filenames_buffer(self) -> Vec<u8> {
139+
// This method takes `self` so that the caller can't accidentally
140+
// modify the original file table after encoding it into a buffer.
141+
142+
llvm::build_byte_buffer(|buffer| {
143+
coverageinfo::write_filenames_section_to_buffer(
144+
self.global_file_table.iter().map(Symbol::as_str),
145+
buffer,
146+
);
147+
})
148+
}
149+
}
150+
151+
/// Using the expressions and counter regions collected for a single function,
152+
/// generate the variable-sized payload of its corresponding `__llvm_covfun`
153+
/// entry. The payload is returned as a vector of bytes.
154+
///
155+
/// Newly-encountered filenames will be added to the global file table.
156+
fn encode_mappings_for_function(
157+
global_file_table: &mut GlobalFileTable,
158+
function_coverage: &FunctionCoverage<'_>,
159+
) -> Vec<u8> {
160+
let (expressions, counter_regions) = function_coverage.get_expressions_and_counter_regions();
161+
162+
let mut counter_regions = counter_regions.collect::<Vec<_>>();
163+
if counter_regions.is_empty() {
164+
return Vec::new();
165+
}
166+
167+
let mut virtual_file_mapping = IndexVec::<u32, u32>::new();
168+
let mut mapping_regions = Vec::with_capacity(counter_regions.len());
169+
170+
// Sort the list of (counter, region) mapping pairs by region, so that they
171+
// can be grouped by filename. Prepare file IDs for each filename, and
172+
// prepare the mapping data so that we can pass it through FFI to LLVM.
173+
counter_regions.sort_by_key(|(_counter, region)| *region);
174+
for counter_regions_for_file in
175+
counter_regions.group_by(|(_, a), (_, b)| a.file_name == b.file_name)
176+
{
177+
// Look up (or allocate) the global file ID for this filename.
178+
let file_name = counter_regions_for_file[0].1.file_name;
179+
let global_file_id = global_file_table.global_file_id_for_file_name(file_name);
180+
181+
// Associate that global file ID with a local file ID for this function.
182+
let local_file_id: u32 = virtual_file_mapping.push(global_file_id);
183+
debug!(" file id: local {local_file_id} => global {global_file_id} = '{file_name:?}'");
184+
185+
// For each counter/region pair in this function+file, convert it to a
186+
// form suitable for FFI.
187+
for &(counter, region) in counter_regions_for_file {
188+
let CodeRegion { file_name: _, start_line, start_col, end_line, end_col } = *region;
189+
190+
debug!("Adding counter {counter:?} to map for {region:?}");
179191
mapping_regions.push(CounterMappingRegion::code_region(
180192
counter,
181-
current_file_id,
193+
local_file_id,
182194
start_line,
183195
start_col,
184196
end_line,
185197
end_col,
186198
));
187199
}
200+
}
188201

189-
// Encode and append the current function's coverage mapping data
202+
// Encode the function's coverage mappings into a buffer.
203+
llvm::build_byte_buffer(|buffer| {
190204
coverageinfo::write_mapping_to_buffer(
191-
virtual_file_mapping,
205+
virtual_file_mapping.raw,
192206
expressions,
193207
mapping_regions,
194-
coverage_mapping_buffer,
208+
buffer,
195209
);
196-
}
210+
})
211+
}
197212

198-
/// Construct coverage map header and the array of function records, and combine them into the
199-
/// coverage map. Save the coverage map data into the LLVM IR as a static global using a
200-
/// specific, well-known section and name.
201-
fn generate_coverage_map<'ll>(
202-
self,
203-
cx: &CodegenCx<'ll, '_>,
204-
version: u32,
205-
filenames_size: usize,
206-
filenames_val: &'ll llvm::Value,
207-
) -> &'ll llvm::Value {
208-
debug!("cov map: filenames_size = {}, 0-based version = {}", filenames_size, version);
209-
210-
// Create the coverage data header (Note, fields 0 and 2 are now always zero,
211-
// as of `llvm::coverage::CovMapVersion::Version4`.)
212-
let zero_was_n_records_val = cx.const_u32(0);
213-
let filenames_size_val = cx.const_u32(filenames_size as u32);
214-
let zero_was_coverage_size_val = cx.const_u32(0);
215-
let version_val = cx.const_u32(version);
216-
let cov_data_header_val = cx.const_struct(
217-
&[zero_was_n_records_val, filenames_size_val, zero_was_coverage_size_val, version_val],
218-
/*packed=*/ false,
219-
);
213+
/// Construct coverage map header and the array of function records, and combine them into the
214+
/// coverage map. Save the coverage map data into the LLVM IR as a static global using a
215+
/// specific, well-known section and name.
216+
fn generate_coverage_map<'ll>(
217+
cx: &CodegenCx<'ll, '_>,
218+
version: u32,
219+
filenames_size: usize,
220+
filenames_val: &'ll llvm::Value,
221+
) -> &'ll llvm::Value {
222+
debug!("cov map: filenames_size = {}, 0-based version = {}", filenames_size, version);
223+
224+
// Create the coverage data header (Note, fields 0 and 2 are now always zero,
225+
// as of `llvm::coverage::CovMapVersion::Version4`.)
226+
let zero_was_n_records_val = cx.const_u32(0);
227+
let filenames_size_val = cx.const_u32(filenames_size as u32);
228+
let zero_was_coverage_size_val = cx.const_u32(0);
229+
let version_val = cx.const_u32(version);
230+
let cov_data_header_val = cx.const_struct(
231+
&[zero_was_n_records_val, filenames_size_val, zero_was_coverage_size_val, version_val],
232+
/*packed=*/ false,
233+
);
220234

221-
// Create the complete LLVM coverage data value to add to the LLVM IR
222-
cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false)
223-
}
235+
// Create the complete LLVM coverage data value to add to the LLVM IR
236+
cx.const_struct(&[cov_data_header_val, filenames_val], /*packed=*/ false)
224237
}
225238

226239
/// Construct a function record and combine it with the function's coverage mapping data.

compiler/rustc_codegen_llvm/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![feature(iter_intersperse)]
1111
#![feature(let_chains)]
1212
#![feature(never_type)]
13+
#![feature(slice_group_by)]
1314
#![feature(impl_trait_in_assoc_type)]
1415
#![recursion_limit = "256"]
1516
#![allow(rustc::potential_query_instability)]

0 commit comments

Comments
 (0)