Skip to content

Commit db0492a

Browse files
committed
Auto merge of #74733 - richkadel:llvm-coverage-map-gen-5, r=tmandry
Fixed coverage map issues; better aligned with LLVM APIs Found some problems with the coverage map encoding when testing with more than one counter per function. While debugging, I realized some better ways to structure the Rust implementation of the coverage mapping generator. I refactored somewhat, resulting in less code overall, expanded coverage of LLVM Coverage Map capabilities, and much closer alignment with LLVM data structures, APIs, and naming. This should be easier to follow and easier to maintain. r? @tmandry Rust compiler MCP rust-lang/compiler-team#278 Relevant issue: #34701 - Implement support for LLVMs code coverage instrumentation
2 parents 8611e52 + 5b2e2b2 commit db0492a

File tree

14 files changed

+794
-690
lines changed

14 files changed

+794
-690
lines changed

src/librustc_codegen_llvm/coverageinfo/mapgen.rs

+117-155
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
1-
use crate::llvm;
2-
31
use crate::common::CodegenCx;
42
use crate::coverageinfo;
3+
use crate::llvm;
54

5+
use llvm::coverageinfo::CounterMappingRegion;
66
use log::debug;
7-
use rustc_codegen_ssa::coverageinfo::map::*;
8-
use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods, MiscMethods};
7+
use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression, Region};
8+
use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods};
99
use rustc_data_structures::fx::FxHashMap;
1010
use rustc_llvm::RustString;
11-
use rustc_middle::ty::Instance;
12-
use rustc_middle::{bug, mir};
1311

14-
use std::collections::BTreeMap;
1512
use std::ffi::CString;
16-
use std::path::PathBuf;
17-
18-
// FIXME(richkadel): Complete all variations of generating and exporting the coverage map to LLVM.
19-
// The current implementation is an initial foundation with basic capabilities (Counters, but not
20-
// CounterExpressions, etc.).
2113

2214
/// Generates and exports the Coverage Map.
2315
///
@@ -32,174 +24,123 @@ use std::path::PathBuf;
3224
/// undocumented details in Clang's implementation (that may or may not be important) were also
3325
/// replicated for Rust's Coverage Map.
3426
pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {
35-
let mut coverage_writer = CoverageMappingWriter::new(cx);
36-
3727
let function_coverage_map = cx.coverage_context().take_function_coverage_map();
28+
if function_coverage_map.is_empty() {
29+
// This module has no functions with coverage instrumentation
30+
return;
31+
}
32+
33+
let mut mapgen = CoverageMapGenerator::new();
3834

3935
// Encode coverage mappings and generate function records
4036
let mut function_records = Vec::<&'ll llvm::Value>::new();
4137
let coverage_mappings_buffer = llvm::build_byte_buffer(|coverage_mappings_buffer| {
4238
for (instance, function_coverage) in function_coverage_map.into_iter() {
43-
if let Some(function_record) = coverage_writer.write_function_mappings_and_record(
44-
instance,
45-
function_coverage,
46-
coverage_mappings_buffer,
47-
) {
48-
function_records.push(function_record);
49-
}
39+
debug!("Generate coverage map for: {:?}", instance);
40+
41+
let mangled_function_name = cx.tcx.symbol_name(instance).to_string();
42+
let function_source_hash = function_coverage.source_hash();
43+
let (expressions, counter_regions) =
44+
function_coverage.get_expressions_and_counter_regions();
45+
46+
let old_len = coverage_mappings_buffer.len();
47+
mapgen.write_coverage_mappings(expressions, counter_regions, coverage_mappings_buffer);
48+
let mapping_data_size = coverage_mappings_buffer.len() - old_len;
49+
debug_assert!(
50+
mapping_data_size > 0,
51+
"Every `FunctionCoverage` should have at least one counter"
52+
);
53+
54+
let function_record = mapgen.make_function_record(
55+
cx,
56+
mangled_function_name,
57+
function_source_hash,
58+
mapping_data_size,
59+
);
60+
function_records.push(function_record);
5061
}
5162
});
5263

53-
// Encode all filenames covered in this module, ordered by `file_id`
64+
// Encode all filenames referenced by counters/expressions in this module
5465
let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| {
55-
coverageinfo::write_filenames_section_to_buffer(
56-
&coverage_writer.filenames,
57-
filenames_buffer,
58-
);
66+
coverageinfo::write_filenames_section_to_buffer(&mapgen.filenames, filenames_buffer);
5967
});
6068

61-
if coverage_mappings_buffer.len() > 0 {
62-
// Generate the LLVM IR representation of the coverage map and store it in a well-known
63-
// global constant.
64-
coverage_writer.write_coverage_map(
65-
function_records,
66-
filenames_buffer,
67-
coverage_mappings_buffer,
68-
);
69-
}
69+
// Generate the LLVM IR representation of the coverage map and store it in a well-known global
70+
mapgen.save_generated_coverage_map(
71+
cx,
72+
function_records,
73+
filenames_buffer,
74+
coverage_mappings_buffer,
75+
);
7076
}
7177

72-
struct CoverageMappingWriter<'a, 'll, 'tcx> {
73-
cx: &'a CodegenCx<'ll, 'tcx>,
78+
struct CoverageMapGenerator {
7479
filenames: Vec<CString>,
7580
filename_to_index: FxHashMap<CString, u32>,
7681
}
7782

78-
impl<'a, 'll, 'tcx> CoverageMappingWriter<'a, 'll, 'tcx> {
79-
fn new(cx: &'a CodegenCx<'ll, 'tcx>) -> Self {
80-
Self { cx, filenames: Vec::new(), filename_to_index: FxHashMap::<CString, u32>::default() }
83+
impl CoverageMapGenerator {
84+
fn new() -> Self {
85+
Self { filenames: Vec::new(), filename_to_index: FxHashMap::default() }
8186
}
8287

83-
/// For the given function, get the coverage region data, stream it to the given buffer, and
84-
/// then generate and return a new function record.
85-
fn write_function_mappings_and_record(
88+
/// Using the `expressions` and `counter_regions` collected for the current function, generate
89+
/// the `mapping_regions` and `virtual_file_mapping`, and capture any new filenames. Then use
90+
/// LLVM APIs to encode the `virtual_file_mapping`, `expressions`, and `mapping_regions` into
91+
/// the given `coverage_mappings` byte buffer, compliant with the LLVM Coverage Mapping format.
92+
fn write_coverage_mappings(
8693
&mut self,
87-
instance: Instance<'tcx>,
88-
mut function_coverage: FunctionCoverage,
94+
expressions: Vec<CounterExpression>,
95+
counter_regions: impl Iterator<Item = (Counter, &'a Region)>,
8996
coverage_mappings_buffer: &RustString,
90-
) -> Option<&'ll llvm::Value> {
91-
let cx = self.cx;
92-
let coverageinfo: &mir::CoverageInfo = cx.tcx.coverageinfo(instance.def_id());
93-
debug!(
94-
"Generate coverage map for: {:?}, num_counters: {}, num_expressions: {}",
95-
instance, coverageinfo.num_counters, coverageinfo.num_expressions
96-
);
97-
debug_assert!(coverageinfo.num_counters > 0);
98-
99-
let regions_in_file_order = function_coverage.regions_in_file_order(cx.sess().source_map());
100-
if regions_in_file_order.len() == 0 {
101-
return None;
97+
) {
98+
let mut counter_regions = counter_regions.collect::<Vec<_>>();
99+
if counter_regions.is_empty() {
100+
return;
102101
}
103102

104-
// Stream the coverage mapping regions for the function (`instance`) to the buffer, and
105-
// compute the data byte size used.
106-
let old_len = coverage_mappings_buffer.len();
107-
self.regions_to_mappings(regions_in_file_order, coverage_mappings_buffer);
108-
let mapping_data_size = coverage_mappings_buffer.len() - old_len;
109-
debug_assert!(mapping_data_size > 0);
110-
111-
let mangled_function_name = cx.tcx.symbol_name(instance).to_string();
112-
let name_ref = coverageinfo::compute_hash(&mangled_function_name);
113-
let function_source_hash = function_coverage.source_hash();
114-
115-
// Generate and return the function record
116-
let name_ref_val = cx.const_u64(name_ref);
117-
let mapping_data_size_val = cx.const_u32(mapping_data_size as u32);
118-
let func_hash_val = cx.const_u64(function_source_hash);
119-
Some(cx.const_struct(
120-
&[name_ref_val, mapping_data_size_val, func_hash_val],
121-
/*packed=*/ true,
122-
))
123-
}
124-
125-
/// For each coverage region, extract its coverage data from the earlier coverage analysis.
126-
/// Use LLVM APIs to convert the data into buffered bytes compliant with the LLVM Coverage
127-
/// Mapping format.
128-
fn regions_to_mappings(
129-
&mut self,
130-
regions_in_file_order: BTreeMap<PathBuf, BTreeMap<CoverageLoc, (usize, CoverageKind)>>,
131-
coverage_mappings_buffer: &RustString,
132-
) {
133103
let mut virtual_file_mapping = Vec::new();
134-
let mut mapping_regions = coverageinfo::SmallVectorCounterMappingRegion::new();
135-
let mut expressions = coverageinfo::SmallVectorCounterExpression::new();
136-
137-
for (file_id, (file_path, file_coverage_regions)) in
138-
regions_in_file_order.into_iter().enumerate()
139-
{
140-
let file_id = file_id as u32;
141-
let filename = CString::new(file_path.to_string_lossy().to_string())
142-
.expect("null error converting filename to C string");
143-
debug!(" file_id: {} = '{:?}'", file_id, filename);
144-
let filenames_index = match self.filename_to_index.get(&filename) {
145-
Some(index) => *index,
146-
None => {
147-
let index = self.filenames.len() as u32;
148-
self.filenames.push(filename.clone());
149-
self.filename_to_index.insert(filename, index);
150-
index
104+
let mut mapping_regions = Vec::new();
105+
let mut current_file_path = None;
106+
let mut current_file_id = 0;
107+
108+
// Convert the list of (Counter, Region) pairs to an array of `CounterMappingRegion`, sorted
109+
// by filename and position. Capture any new files to compute the `CounterMappingRegion`s
110+
// `file_id` (indexing files referenced by the current function), and construct the
111+
// function-specific `virtual_file_mapping` from `file_id` to its index in the module's
112+
// `filenames` array.
113+
counter_regions.sort_unstable_by_key(|(_counter, region)| *region);
114+
for (counter, region) in counter_regions {
115+
let (file_path, start_line, start_col, end_line, end_col) = region.file_start_and_end();
116+
let same_file = current_file_path.as_ref().map_or(false, |p| p == file_path);
117+
if !same_file {
118+
if current_file_path.is_some() {
119+
current_file_id += 1;
151120
}
152-
};
153-
virtual_file_mapping.push(filenames_index);
154-
155-
let mut mapping_indexes = vec![0 as u32; file_coverage_regions.len()];
156-
for (mapping_index, (region_id, _)) in file_coverage_regions.values().enumerate() {
157-
mapping_indexes[*region_id] = mapping_index as u32;
158-
}
159-
160-
for (region_loc, (region_id, region_kind)) in file_coverage_regions.into_iter() {
161-
let mapping_index = mapping_indexes[region_id];
162-
match region_kind {
163-
CoverageKind::Counter => {
164-
debug!(
165-
" Counter {}, file_id: {}, region_loc: {}",
166-
mapping_index, file_id, region_loc
167-
);
168-
mapping_regions.push_from(
169-
mapping_index,
170-
file_id,
171-
region_loc.start_line,
172-
region_loc.start_col,
173-
region_loc.end_line,
174-
region_loc.end_col,
175-
);
176-
}
177-
CoverageKind::CounterExpression(lhs, op, rhs) => {
178-
debug!(
179-
" CounterExpression {} = {} {:?} {}, file_id: {}, region_loc: {:?}",
180-
mapping_index, lhs, op, rhs, file_id, region_loc,
181-
);
182-
mapping_regions.push_from(
183-
mapping_index,
184-
file_id,
185-
region_loc.start_line,
186-
region_loc.start_col,
187-
region_loc.end_line,
188-
region_loc.end_col,
189-
);
190-
expressions.push_from(op, lhs, rhs);
191-
}
192-
CoverageKind::Unreachable => {
193-
debug!(
194-
" Unreachable region, file_id: {}, region_loc: {:?}",
195-
file_id, region_loc,
196-
);
197-
bug!("Unreachable region not expected and not yet handled!")
198-
// FIXME(richkadel): implement and call
199-
// mapping_regions.push_from(...) for unreachable regions
121+
current_file_path = Some(file_path.clone());
122+
let filename = CString::new(file_path.to_string_lossy().to_string())
123+
.expect("null error converting filename to C string");
124+
debug!(" file_id: {} = '{:?}'", current_file_id, filename);
125+
let filenames_index = match self.filename_to_index.get(&filename) {
126+
Some(index) => *index,
127+
None => {
128+
let index = self.filenames.len() as u32;
129+
self.filenames.push(filename.clone());
130+
self.filename_to_index.insert(filename.clone(), index);
131+
index
200132
}
201-
}
133+
};
134+
virtual_file_mapping.push(filenames_index);
202135
}
136+
mapping_regions.push(CounterMappingRegion::code_region(
137+
counter,
138+
current_file_id,
139+
start_line,
140+
start_col,
141+
end_line,
142+
end_col,
143+
));
203144
}
204145

205146
// Encode and append the current function's coverage mapping data
@@ -211,14 +152,35 @@ impl<'a, 'll, 'tcx> CoverageMappingWriter<'a, 'll, 'tcx> {
211152
);
212153
}
213154

214-
fn write_coverage_map(
155+
/// Generate and return the function record `Value`
156+
fn make_function_record(
157+
&mut self,
158+
cx: &CodegenCx<'ll, 'tcx>,
159+
mangled_function_name: String,
160+
function_source_hash: u64,
161+
mapping_data_size: usize,
162+
) -> &'ll llvm::Value {
163+
let name_ref = coverageinfo::compute_hash(&mangled_function_name);
164+
let name_ref_val = cx.const_u64(name_ref);
165+
let mapping_data_size_val = cx.const_u32(mapping_data_size as u32);
166+
let func_hash_val = cx.const_u64(function_source_hash);
167+
cx.const_struct(
168+
&[name_ref_val, mapping_data_size_val, func_hash_val],
169+
/*packed=*/ true,
170+
)
171+
}
172+
173+
/// Combine the filenames and coverage mappings buffers, construct coverage map header and the
174+
/// array of function records, and combine everything into the complete coverage map. Save the
175+
/// coverage map data into the LLVM IR as a static global using a specific, well-known section
176+
/// and name.
177+
fn save_generated_coverage_map(
215178
self,
179+
cx: &CodegenCx<'ll, 'tcx>,
216180
function_records: Vec<&'ll llvm::Value>,
217181
filenames_buffer: Vec<u8>,
218182
mut coverage_mappings_buffer: Vec<u8>,
219183
) {
220-
let cx = self.cx;
221-
222184
// Concatenate the encoded filenames and encoded coverage mappings, and add additional zero
223185
// bytes as-needed to ensure 8-byte alignment.
224186
let mut coverage_size = coverage_mappings_buffer.len();

0 commit comments

Comments
 (0)