1
Fork 0

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.
This commit is contained in:
Zalathar 2025-03-20 12:26:28 +11:00
parent d07ef5b0e1
commit 2e36990881
2 changed files with 40 additions and 27 deletions

View file

@ -120,9 +120,14 @@ fn fill_region_tables<'tcx>(
// Associate that global file ID with a local file ID for this function. // Associate that global file ID with a local file ID for this function.
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id); let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
let make_cov_span = // In rare cases, _all_ of a function's spans are discarded, and coverage
|span: Span| spans::make_coverage_span(local_file_id, source_map, &source_file, span); // codegen needs to handle that gracefully to avoid #133606.
// It's hard for tests to trigger this organically, so instead we set
// `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur.
let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen(); let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
let make_coords = |span: Span| {
if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
};
let ffi::Regions { let ffi::Regions {
code_regions, code_regions,
@ -145,17 +150,8 @@ fn fill_region_tables<'tcx>(
ffi::Counter::from_term(term) ffi::Counter::from_term(term)
}; };
// Convert the `Span` into coordinates that we can pass to LLVM, or let Some(coords) = make_coords(span) else { continue };
// discard the span if conversion fails. In rare, cases _all_ of a let cov_span = coords.make_coverage_span(local_file_id);
// function's spans are discarded, and the rest of coverage codegen
// needs to handle that gracefully to avoid a repeat of #133606.
// We don't have a good test case for triggering that organically, so
// instead we set `-Zcoverage-options=discard-all-spans-in-codegen`
// to force it to occur.
let Some(cov_span) = make_cov_span(span) else { continue };
if discard_all {
continue;
}
match *kind { match *kind {
MappingKind::Code { bcb } => { MappingKind::Code { bcb } => {

View file

@ -5,22 +5,40 @@ use tracing::debug;
use crate::coverageinfo::ffi; use crate::coverageinfo::ffi;
use crate::coverageinfo::mapgen::LocalFileId; use crate::coverageinfo::mapgen::LocalFileId;
/// Line and byte-column coordinates of a source code span within some file.
/// The file itself must be tracked separately.
#[derive(Clone, Copy, Debug)]
pub(crate) struct Coords {
/// 1-based starting line of the source code span.
pub(crate) start_line: u32,
/// 1-based starting column (in bytes) of the source code span.
pub(crate) start_col: u32,
/// 1-based ending line of the source code span.
pub(crate) end_line: u32,
/// 1-based ending column (in bytes) of the source code span. High bit must be unset.
pub(crate) end_col: u32,
}
impl Coords {
/// Attaches a local file ID to these coordinates to produce an `ffi::CoverageSpan`.
pub(crate) fn make_coverage_span(&self, local_file_id: LocalFileId) -> ffi::CoverageSpan {
let &Self { start_line, start_col, end_line, end_col } = self;
let file_id = local_file_id.as_u32();
ffi::CoverageSpan { file_id, start_line, start_col, end_line, end_col }
}
}
/// Converts the span into its start line and column, and end line and column. /// Converts the span into its start line and column, and end line and column.
/// ///
/// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by /// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by
/// the compiler, these column numbers are denoted in **bytes**, because that's what /// the compiler, these column numbers are denoted in **bytes**, because that's what
/// LLVM's `llvm-cov` tool expects to see in coverage maps. /// LLVM's `llvm-cov` tool expects to see in coverage maps.
/// ///
/// Returns `None` if the conversion failed for some reason. This shouldn't happen, /// Returns `None` if the conversion failed for some reason. This should be uncommon,
/// but it's hard to rule out entirely (especially in the presence of complex macros /// but it's hard to rule out entirely (especially in the presence of complex macros
/// or other expansions), and if it does happen then skipping a span or function is /// or other expansions), and if it does happen then skipping a span or function is
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid. /// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
pub(crate) fn make_coverage_span( pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span) -> Option<Coords> {
file_id: LocalFileId,
source_map: &SourceMap,
file: &SourceFile,
span: Span,
) -> Option<ffi::CoverageSpan> {
let span = ensure_non_empty_span(source_map, span)?; let span = ensure_non_empty_span(source_map, span)?;
let lo = span.lo(); let lo = span.lo();
@ -44,8 +62,7 @@ pub(crate) fn make_coverage_span(
start_line = source_map.doctest_offset_line(&file.name, start_line); start_line = source_map.doctest_offset_line(&file.name, start_line);
end_line = source_map.doctest_offset_line(&file.name, end_line); end_line = source_map.doctest_offset_line(&file.name, end_line);
check_coverage_span(ffi::CoverageSpan { check_coords(Coords {
file_id: file_id.as_u32(),
start_line: start_line as u32, start_line: start_line as u32,
start_col: start_col as u32, start_col: start_col as u32,
end_line: end_line as u32, end_line: end_line as u32,
@ -80,8 +97,8 @@ fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
/// it will immediately exit with a fatal error. To prevent that from happening, /// it will immediately exit with a fatal error. To prevent that from happening,
/// discard regions that are improperly ordered, or might be interpreted in a /// discard regions that are improperly ordered, or might be interpreted in a
/// way that makes them improperly ordered. /// way that makes them improperly ordered.
fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan> { fn check_coords(coords: Coords) -> Option<Coords> {
let ffi::CoverageSpan { file_id: _, start_line, start_col, end_line, end_col } = cov_span; let Coords { start_line, start_col, end_line, end_col } = coords;
// Line/column coordinates are supposed to be 1-based. If we ever emit // Line/column coordinates are supposed to be 1-based. If we ever emit
// coordinates of 0, `llvm-cov` might misinterpret them. // coordinates of 0, `llvm-cov` might misinterpret them.
@ -94,17 +111,17 @@ fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan>
let is_ordered = (start_line, start_col) <= (end_line, end_col); let is_ordered = (start_line, start_col) <= (end_line, end_col);
if all_nonzero && end_col_has_high_bit_unset && is_ordered { if all_nonzero && end_col_has_high_bit_unset && is_ordered {
Some(cov_span) Some(coords)
} else { } else {
debug!( debug!(
?cov_span, ?coords,
?all_nonzero, ?all_nonzero,
?end_col_has_high_bit_unset, ?end_col_has_high_bit_unset,
?is_ordered, ?is_ordered,
"Skipping source region that would be misinterpreted or rejected by LLVM" "Skipping source region that would be misinterpreted or rejected by LLVM"
); );
// If this happens in a debug build, ICE to make it easier to notice. // If this happens in a debug build, ICE to make it easier to notice.
debug_assert!(false, "Improper source region: {cov_span:?}"); debug_assert!(false, "Improper source region: {coords:?}");
None None
} }
} }