Skip to content

Commit 12ddd60

Browse files
committed
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.
1 parent c4e1734 commit 12ddd60

File tree

13 files changed

+597
-585
lines changed

13 files changed

+597
-585
lines changed

src/librustc_codegen_llvm/coverageinfo/mapgen.rs

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

65
use log::debug;
76
use rustc_codegen_ssa::coverageinfo::map::*;
8-
use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods, MiscMethods};
7+
use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods};
98
use rustc_data_structures::fx::FxHashMap;
109
use rustc_llvm::RustString;
11-
use rustc_middle::ty::Instance;
12-
use rustc_middle::{bug, mir};
1310

14-
use std::collections::BTreeMap;
1511
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.).
2112

2213
/// Generates and exports the Coverage Map.
2314
///
@@ -32,174 +23,123 @@ use std::path::PathBuf;
3223
/// undocumented details in Clang's implementation (that may or may not be important) were also
3324
/// replicated for Rust's Coverage Map.
3425
pub fn finalize<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {
35-
let mut coverage_writer = CoverageMappingWriter::new(cx);
36-
3726
let function_coverage_map = cx.coverage_context().take_function_coverage_map();
27+
if function_coverage_map.len() == 0 {
28+
// This module has no functions with coverage instrumentation
29+
return;
30+
}
31+
32+
let mut mapgen = CoverageMapGenerator::new();
3833

3934
// Encode coverage mappings and generate function records
4035
let mut function_records = Vec::<&'ll llvm::Value>::new();
4136
let coverage_mappings_buffer = llvm::build_byte_buffer(|coverage_mappings_buffer| {
4237
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-
}
38+
debug!("Generate coverage map for: {:?}", instance);
39+
40+
let mangled_function_name = cx.tcx.symbol_name(instance).to_string();
41+
let function_source_hash = function_coverage.source_hash();
42+
let (expressions, counter_regions) =
43+
function_coverage.get_expressions_and_counter_regions();
44+
45+
let old_len = coverage_mappings_buffer.len();
46+
mapgen.write_coverage_mappings(expressions, counter_regions, coverage_mappings_buffer);
47+
let mapping_data_size = coverage_mappings_buffer.len() - old_len;
48+
debug_assert!(
49+
mapping_data_size > 0,
50+
"Every `FunctionCoverage` should have at least one counter"
51+
);
52+
53+
let function_record = mapgen.make_function_record(
54+
cx,
55+
mangled_function_name,
56+
function_source_hash,
57+
mapping_data_size,
58+
);
59+
function_records.push(function_record);
5060
}
5161
});
5262

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

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-
}
68+
// Generate the LLVM IR representation of the coverage map and store it in a well-known global
69+
mapgen.save_generated_coverage_map(
70+
cx,
71+
function_records,
72+
filenames_buffer,
73+
coverage_mappings_buffer,
74+
);
7075
}
7176

72-
struct CoverageMappingWriter<'a, 'll, 'tcx> {
73-
cx: &'a CodegenCx<'ll, 'tcx>,
77+
struct CoverageMapGenerator {
7478
filenames: Vec<CString>,
7579
filename_to_index: FxHashMap<CString, u32>,
7680
}
7781

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() }
82+
impl CoverageMapGenerator {
83+
fn new() -> Self {
84+
Self { filenames: Vec::new(), filename_to_index: FxHashMap::<CString, u32>::default() }
8185
}
8286

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(
87+
/// Using the `expressions` and `counter_regions` collected for the current function, generate
88+
/// the `mapping_regions` and `virtual_file_mapping`, and capture any new filenames. Then use
89+
/// LLVM APIs to encode the `virtual_file_mapping`, `expressions`, and `mapping_regions` into
90+
/// the given `coverage_mappings` byte buffer, compliant with the LLVM Coverage Mapping format.
91+
fn write_coverage_mappings(
8692
&mut self,
87-
instance: Instance<'tcx>,
88-
mut function_coverage: FunctionCoverage,
93+
expressions: Vec<CounterExpression>,
94+
counter_regions: impl Iterator<Item = (Counter, &'a Region)>,
8995
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;
96+
) {
97+
let mut counter_regions = counter_regions.collect::<Vec<_>>();
98+
if counter_regions.len() == 0 {
99+
return;
102100
}
103101

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-
) {
133102
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
103+
let mut mapping_regions = Vec::new();
104+
let mut current_file_path = None;
105+
let mut current_file_id = 0;
106+
107+
// Convert the list of (Counter, Region) pairs to an array of `CounterMappingRegion`, sorted
108+
// by filename and position. Capture any new files to compute the `CounterMappingRegion`s
109+
// `file_id` (indexing files referenced by the current function), and construct the
110+
// function-specific `virtual_file_mapping` from `file_id` to its index in the module's
111+
// `filenames` array.
112+
counter_regions.sort_by_key(|(_counter, region)| *region);
113+
for (counter, region) in counter_regions {
114+
let (file_path, start_line, start_col, end_line, end_col) = region.file_start_and_end();
115+
let same_file = current_file_path.as_ref().map_or(false, |p| p == file_path);
116+
if !same_file {
117+
if current_file_path.is_some() {
118+
current_file_id += 1;
151119
}
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
120+
current_file_path = Some(file_path.clone());
121+
let filename = CString::new(file_path.to_string_lossy().to_string())
122+
.expect("null error converting filename to C string");
123+
debug!(" file_id: {} = '{:?}'", current_file_id, filename);
124+
let filenames_index = match self.filename_to_index.get(&filename) {
125+
Some(index) => *index,
126+
None => {
127+
let index = self.filenames.len() as u32;
128+
self.filenames.push(filename.clone());
129+
self.filename_to_index.insert(filename.clone(), index);
130+
index
200131
}
201-
}
132+
};
133+
virtual_file_mapping.push(filenames_index);
202134
}
135+
mapping_regions.push(coverageinfo::CounterMappingRegion::code_region(
136+
counter,
137+
current_file_id,
138+
start_line,
139+
start_col,
140+
end_line,
141+
end_col,
142+
));
203143
}
204144

205145
// Encode and append the current function's coverage mapping data
@@ -211,14 +151,35 @@ impl<'a, 'll, 'tcx> CoverageMappingWriter<'a, 'll, 'tcx> {
211151
);
212152
}
213153

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

0 commit comments

Comments
 (0)