Skip to content

coverage: Various small cleanups #144877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::assert_matches::assert_matches;
use std::sync::Arc;

use itertools::Itertools;
use rustc_abi::Align;
use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, ConstCodegenMethods};
use rustc_data_structures::fx::FxIndexMap;
use rustc_index::IndexVec;
use rustc_macros::TryFromU32;
use rustc_middle::ty::TyCtxt;
use rustc_session::RemapFileNameExt;
use rustc_session::config::RemapPathScopeComponents;
Expand All @@ -20,6 +22,23 @@ mod covfun;
mod spans;
mod unused;

/// Version number that will be included the `__llvm_covmap` section header.
/// Corresponds to LLVM's `llvm::coverage::CovMapVersion` (in `CoverageMapping.h`),
/// or at least the subset that we know and care about.
///
/// Note that version `n` is encoded as `(n-1)`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, TryFromU32)]
enum CovmapVersion {
/// Used by LLVM 18 onwards.
Version7 = 6,
}

impl CovmapVersion {
fn to_u32(self) -> u32 {
self as u32
}
}

/// Generates and exports the coverage map, which is embedded in special
/// linker sections in the final binary.
///
Expand All @@ -29,19 +48,13 @@ pub(crate) fn finalize(cx: &mut CodegenCx<'_, '_>) {
let tcx = cx.tcx;

// Ensure that LLVM is using a version of the coverage mapping format that
// agrees with our Rust-side code. Expected versions (encoded as n-1) are:
// - `CovMapVersion::Version7` (6) used by LLVM 18-19
let covmap_version = {
let llvm_covmap_version = llvm_cov::mapping_version();
let expected_versions = 6..=6;
assert!(
expected_versions.contains(&llvm_covmap_version),
"Coverage mapping version exposed by `llvm-wrapper` is out of sync; \
expected {expected_versions:?} but was {llvm_covmap_version}"
);
// This is the version number that we will embed in the covmap section:
llvm_covmap_version
};
// agrees with our Rust-side code. Expected versions are:
// - `Version7` (6) used by LLVM 18 onwards.
let covmap_version =
CovmapVersion::try_from(llvm_cov::mapping_version()).unwrap_or_else(|raw_version: u32| {
panic!("unknown coverage mapping version reported by `llvm-wrapper`: {raw_version}")
});
assert_matches!(covmap_version, CovmapVersion::Version7);

debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name());

Expand Down Expand Up @@ -201,7 +214,11 @@ impl VirtualFileMapping {
/// Generates the contents of the covmap record for this CGU, which mostly
/// consists of a header and a list of filenames. The record is then stored
/// as a global variable in the `__llvm_covmap` section.
fn generate_covmap_record<'ll>(cx: &mut CodegenCx<'ll, '_>, version: u32, filenames_buffer: &[u8]) {
fn generate_covmap_record<'ll>(
cx: &mut CodegenCx<'ll, '_>,
version: CovmapVersion,
filenames_buffer: &[u8],
) {
// A covmap record consists of four target-endian u32 values, followed by
// the encoded filenames table. Two of the header fields are unused in
// modern versions of the LLVM coverage mapping format, and are always 0.
Expand All @@ -212,7 +229,7 @@ fn generate_covmap_record<'ll>(cx: &mut CodegenCx<'ll, '_>, version: u32, filena
cx.const_u32(0), // (unused)
cx.const_u32(filenames_buffer.len() as u32),
cx.const_u32(0), // (unused)
cx.const_u32(version),
cx.const_u32(version.to_u32()),
],
/* packed */ false,
);
Expand Down
29 changes: 17 additions & 12 deletions compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ use crate::llvm;
/// the final record that will be embedded in the `__llvm_covfun` section.
#[derive(Debug)]
pub(crate) struct CovfunRecord<'tcx> {
/// Not used directly, but helpful in debug messages.
_instance: Instance<'tcx>,

mangled_function_name: &'tcx str,
source_hash: u64,
is_used: bool,
Expand Down Expand Up @@ -55,6 +58,7 @@ pub(crate) fn prepare_covfun_record<'tcx>(
let expressions = prepare_expressions(ids_info);

let mut covfun = CovfunRecord {
_instance: instance,
mangled_function_name: tcx.symbol_name(instance).name,
source_hash: if is_used { fn_cov_info.function_source_hash } else { 0 },
is_used,
Expand Down Expand Up @@ -102,11 +106,21 @@ fn fill_region_tables<'tcx>(
ids_info: &'tcx CoverageIdsInfo,
covfun: &mut CovfunRecord<'tcx>,
) {
// If this function is unused, replace all counters with zero.
let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
let term = if covfun.is_used {
ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
} else {
CovTerm::Zero
};
ffi::Counter::from_term(term)
};

// Currently a function's mappings must all be in the same file, so use the
// first mapping's span to determine the file.
let source_map = tcx.sess.source_map();
let Some(first_span) = (try { fn_cov_info.mappings.first()?.span }) else {
debug_assert!(false, "function has no mappings: {:?}", covfun.mangled_function_name);
debug_assert!(false, "function has no mappings: {covfun:?}");
return;
};
let source_file = source_map.lookup_source_file(first_span.lo());
Expand All @@ -117,7 +131,7 @@ fn fill_region_tables<'tcx>(
// 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_options().discard_all_spans_in_codegen;
let make_coords = |span: Span| {
if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
};
Expand All @@ -133,16 +147,6 @@ fn fill_region_tables<'tcx>(
// For each counter/region pair in this function+file, convert it to a
// form suitable for FFI.
for &Mapping { ref kind, span } in &fn_cov_info.mappings {
// If this function is unused, replace all counters with zero.
let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
let term = if covfun.is_used {
ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
} else {
CovTerm::Zero
};
ffi::Counter::from_term(term)
};

let Some(coords) = make_coords(span) else { continue };
let cov_span = coords.make_coverage_span(local_file_id);

Expand Down Expand Up @@ -184,6 +188,7 @@ pub(crate) fn generate_covfun_record<'tcx>(
covfun: &CovfunRecord<'tcx>,
) {
let &CovfunRecord {
_instance,
mangled_function_name,
source_hash,
is_used,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,8 +778,8 @@ fn test_unstable_options_tracking_hash() {
coverage_options,
CoverageOptions {
level: CoverageLevel::Mcdc,
no_mir_spans: true,
discard_all_spans_in_codegen: true
// (don't collapse test-only options onto the same line)
discard_all_spans_in_codegen: true,
}
);
tracked!(crate_attr, vec!["abc".to_string()]);
Expand Down
20 changes: 3 additions & 17 deletions compiler/rustc_mir_transform/src/coverage/mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rustc_middle::ty::TyCtxt;
use rustc_span::Span;

use crate::coverage::ExtractedHirInfo;
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, START_BCB};
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
use crate::coverage::spans::extract_refined_covspans;
use crate::coverage::unexpand::unexpand_into_body_span;
use crate::errors::MCDCExceedsTestVectorLimit;
Expand Down Expand Up @@ -82,22 +82,8 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>(
let mut mcdc_degraded_branches = vec![];
let mut mcdc_mappings = vec![];

if hir_info.is_async_fn || tcx.sess.coverage_no_mir_spans() {
// An async function desugars into a function that returns a future,
// with the user code wrapped in a closure. Any spans in the desugared
// outer function will be unhelpful, so just keep the signature span
// and ignore all of the spans in the MIR body.
//
// When debugging flag `-Zcoverage-options=no-mir-spans` is set, we need
// to give the same treatment to _all_ functions, because `llvm-cov`
// seems to ignore functions that don't have any ordinary code spans.
if let Some(span) = hir_info.fn_sig_span {
code_mappings.push(CodeMapping { span, bcb: START_BCB });
}
} else {
// Extract coverage spans from MIR statements/terminators as normal.
extract_refined_covspans(tcx, mir_body, hir_info, graph, &mut code_mappings);
}
// Extract ordinary code mappings from MIR statement/terminator spans.
extract_refined_covspans(tcx, mir_body, hir_info, graph, &mut code_mappings);

branch_pairs.extend(extract_branch_pairs(mir_body, hir_info, graph));

Expand Down
14 changes: 13 additions & 1 deletion compiler/rustc_mir_transform/src/coverage/spans.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::mir;
use rustc_middle::mir::coverage::START_BCB;
use rustc_middle::ty::TyCtxt;
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span};
Expand All @@ -16,8 +17,19 @@ pub(super) fn extract_refined_covspans<'tcx>(
mir_body: &mir::Body<'tcx>,
hir_info: &ExtractedHirInfo,
graph: &CoverageGraph,
code_mappings: &mut impl Extend<mappings::CodeMapping>,
code_mappings: &mut Vec<mappings::CodeMapping>,
) {
if hir_info.is_async_fn {
// An async function desugars into a function that returns a future,
// with the user code wrapped in a closure. Any spans in the desugared
// outer function will be unhelpful, so just keep the signature span
// and ignore all of the spans in the MIR body.
if let Some(span) = hir_info.fn_sig_span {
code_mappings.push(mappings::CodeMapping { span, bcb: START_BCB });
}
return;
}

let &ExtractedHirInfo { body_span, .. } = hir_info;

let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
Expand Down
9 changes: 1 addition & 8 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,7 @@ pub enum InstrumentCoverage {
pub struct CoverageOptions {
pub level: CoverageLevel,

/// `-Zcoverage-options=no-mir-spans`: Don't extract block coverage spans
/// from MIR statements/terminators, making it easier to inspect/debug
/// branch and MC/DC coverage mappings.
///
/// For internal debugging only. If other code changes would make it hard
/// to keep supporting this flag, remove it.
pub no_mir_spans: bool,

/// **(internal test-only flag)**
/// `-Zcoverage-options=discard-all-spans-in-codegen`: During codegen,
/// discard all coverage spans as though they were invalid. Needed by
/// regression tests for #133606, because we don't have an easy way to
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,8 +755,7 @@ mod desc {
pub(crate) const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavorCli::one_of();
pub(crate) const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
pub(crate) const parse_instrument_coverage: &str = parse_bool;
pub(crate) const parse_coverage_options: &str =
"`block` | `branch` | `condition` | `mcdc` | `no-mir-spans`";
pub(crate) const parse_coverage_options: &str = "`block` | `branch` | `condition` | `mcdc`";
pub(crate) const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`";
pub(crate) const parse_unpretty: &str = "`string` or `string=string`";
pub(crate) const parse_treat_err_as_bug: &str = "either no value or a non-negative number";
Expand Down Expand Up @@ -1460,7 +1459,6 @@ pub mod parse {
"branch" => slot.level = CoverageLevel::Branch,
"condition" => slot.level = CoverageLevel::Condition,
"mcdc" => slot.level = CoverageLevel::Mcdc,
"no-mir-spans" => slot.no_mir_spans = true,
"discard-all-spans-in-codegen" => slot.discard_all_spans_in_codegen = true,
_ => return false,
}
Expand Down
17 changes: 7 additions & 10 deletions compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ use rustc_target::spec::{
use crate::code_stats::CodeStats;
pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
use crate::config::{
self, CoverageLevel, CrateType, DebugInfo, ErrorOutputType, FunctionReturn, Input,
InstrumentCoverage, OptLevel, OutFileName, OutputType, RemapPathScopeComponents,
self, CoverageLevel, CoverageOptions, CrateType, DebugInfo, ErrorOutputType, FunctionReturn,
Input, InstrumentCoverage, OptLevel, OutFileName, OutputType, RemapPathScopeComponents,
SwitchWithOptPath,
};
use crate::filesearch::FileSearch;
Expand Down Expand Up @@ -359,14 +359,11 @@ impl Session {
&& self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Mcdc
}

/// True if `-Zcoverage-options=no-mir-spans` was passed.
pub fn coverage_no_mir_spans(&self) -> bool {
self.opts.unstable_opts.coverage_options.no_mir_spans
}

/// True if `-Zcoverage-options=discard-all-spans-in-codegen` was passed.
pub fn coverage_discard_all_spans_in_codegen(&self) -> bool {
self.opts.unstable_opts.coverage_options.discard_all_spans_in_codegen
/// Provides direct access to the `CoverageOptions` struct, so that
/// individual flags for debugging/testing coverage instrumetation don't
/// need separate accessors.
pub fn coverage_options(&self) -> &CoverageOptions {
&self.opts.unstable_opts.coverage_options
}

pub fn is_sanitizer_cfi_enabled(&self) -> bool {
Expand Down
63 changes: 0 additions & 63 deletions tests/coverage/branch/no-mir-spans.cov-map

This file was deleted.

Loading
Loading