Skip to content

Commit 2e36990

Browse files
committedMar 20, 2025
coverage: Convert and check span coordinates without a local file ID
For expansion region support, we will want to be able to convert and check spans before creating a corresponding local file ID. If we create local file IDs eagerly, but some expansion turns out to have no successfully-converted spans, LLVM will complain about that expansion's file ID having no regions.
1 parent d07ef5b commit 2e36990

File tree

2 files changed

+40
-27
lines changed

2 files changed

+40
-27
lines changed
 

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

+9-13
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,14 @@ fn fill_region_tables<'tcx>(
120120
// Associate that global file ID with a local file ID for this function.
121121
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
122122

123-
let make_cov_span =
124-
|span: Span| spans::make_coverage_span(local_file_id, source_map, &source_file, span);
123+
// In rare cases, _all_ of a function's spans are discarded, and coverage
124+
// codegen needs to handle that gracefully to avoid #133606.
125+
// It's hard for tests to trigger this organically, so instead we set
126+
// `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur.
125127
let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
128+
let make_coords = |span: Span| {
129+
if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
130+
};
126131

127132
let ffi::Regions {
128133
code_regions,
@@ -145,17 +150,8 @@ fn fill_region_tables<'tcx>(
145150
ffi::Counter::from_term(term)
146151
};
147152

148-
// Convert the `Span` into coordinates that we can pass to LLVM, or
149-
// discard the span if conversion fails. In rare, cases _all_ of a
150-
// function's spans are discarded, and the rest of coverage codegen
151-
// needs to handle that gracefully to avoid a repeat of #133606.
152-
// We don't have a good test case for triggering that organically, so
153-
// instead we set `-Zcoverage-options=discard-all-spans-in-codegen`
154-
// to force it to occur.
155-
let Some(cov_span) = make_cov_span(span) else { continue };
156-
if discard_all {
157-
continue;
158-
}
153+
let Some(coords) = make_coords(span) else { continue };
154+
let cov_span = coords.make_coverage_span(local_file_id);
159155

160156
match *kind {
161157
MappingKind::Code { bcb } => {

‎compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/spans.rs

+31-14
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,40 @@ use tracing::debug;
55
use crate::coverageinfo::ffi;
66
use crate::coverageinfo::mapgen::LocalFileId;
77

8+
/// Line and byte-column coordinates of a source code span within some file.
9+
/// The file itself must be tracked separately.
10+
#[derive(Clone, Copy, Debug)]
11+
pub(crate) struct Coords {
12+
/// 1-based starting line of the source code span.
13+
pub(crate) start_line: u32,
14+
/// 1-based starting column (in bytes) of the source code span.
15+
pub(crate) start_col: u32,
16+
/// 1-based ending line of the source code span.
17+
pub(crate) end_line: u32,
18+
/// 1-based ending column (in bytes) of the source code span. High bit must be unset.
19+
pub(crate) end_col: u32,
20+
}
21+
22+
impl Coords {
23+
/// Attaches a local file ID to these coordinates to produce an `ffi::CoverageSpan`.
24+
pub(crate) fn make_coverage_span(&self, local_file_id: LocalFileId) -> ffi::CoverageSpan {
25+
let &Self { start_line, start_col, end_line, end_col } = self;
26+
let file_id = local_file_id.as_u32();
27+
ffi::CoverageSpan { file_id, start_line, start_col, end_line, end_col }
28+
}
29+
}
30+
831
/// Converts the span into its start line and column, and end line and column.
932
///
1033
/// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by
1134
/// the compiler, these column numbers are denoted in **bytes**, because that's what
1235
/// LLVM's `llvm-cov` tool expects to see in coverage maps.
1336
///
14-
/// Returns `None` if the conversion failed for some reason. This shouldn't happen,
37+
/// Returns `None` if the conversion failed for some reason. This should be uncommon,
1538
/// but it's hard to rule out entirely (especially in the presence of complex macros
1639
/// or other expansions), and if it does happen then skipping a span or function is
1740
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
18-
pub(crate) fn make_coverage_span(
19-
file_id: LocalFileId,
20-
source_map: &SourceMap,
21-
file: &SourceFile,
22-
span: Span,
23-
) -> Option<ffi::CoverageSpan> {
41+
pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span) -> Option<Coords> {
2442
let span = ensure_non_empty_span(source_map, span)?;
2543

2644
let lo = span.lo();
@@ -44,8 +62,7 @@ pub(crate) fn make_coverage_span(
4462
start_line = source_map.doctest_offset_line(&file.name, start_line);
4563
end_line = source_map.doctest_offset_line(&file.name, end_line);
4664

47-
check_coverage_span(ffi::CoverageSpan {
48-
file_id: file_id.as_u32(),
65+
check_coords(Coords {
4966
start_line: start_line as u32,
5067
start_col: start_col as u32,
5168
end_line: end_line as u32,
@@ -80,8 +97,8 @@ fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
8097
/// it will immediately exit with a fatal error. To prevent that from happening,
8198
/// discard regions that are improperly ordered, or might be interpreted in a
8299
/// way that makes them improperly ordered.
83-
fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan> {
84-
let ffi::CoverageSpan { file_id: _, start_line, start_col, end_line, end_col } = cov_span;
100+
fn check_coords(coords: Coords) -> Option<Coords> {
101+
let Coords { start_line, start_col, end_line, end_col } = coords;
85102

86103
// Line/column coordinates are supposed to be 1-based. If we ever emit
87104
// coordinates of 0, `llvm-cov` might misinterpret them.
@@ -94,17 +111,17 @@ fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan>
94111
let is_ordered = (start_line, start_col) <= (end_line, end_col);
95112

96113
if all_nonzero && end_col_has_high_bit_unset && is_ordered {
97-
Some(cov_span)
114+
Some(coords)
98115
} else {
99116
debug!(
100-
?cov_span,
117+
?coords,
101118
?all_nonzero,
102119
?end_col_has_high_bit_unset,
103120
?is_ordered,
104121
"Skipping source region that would be misinterpreted or rejected by LLVM"
105122
);
106123
// If this happens in a debug build, ICE to make it easier to notice.
107-
debug_assert!(false, "Improper source region: {cov_span:?}");
124+
debug_assert!(false, "Improper source region: {coords:?}");
108125
None
109126
}
110127
}

0 commit comments

Comments
 (0)