diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 8c9dfcfd18c2c..d1cb95507d91c 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -1,3 +1,4 @@ +use std::assert_matches::assert_matches; use std::sync::Arc; use itertools::Itertools; @@ -5,6 +6,7 @@ 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; @@ -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. /// @@ -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()); @@ -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. @@ -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, ); diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs index b704cf2b1cd46..fd1e7f7f160ab 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs @@ -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, @@ -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, @@ -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()); @@ -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) } }; @@ -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); @@ -184,6 +188,7 @@ pub(crate) fn generate_covfun_record<'tcx>( covfun: &CovfunRecord<'tcx>, ) { let &CovfunRecord { + _instance, mangled_function_name, source_hash, is_used, diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 8771bb4405049..86faab62d03a7 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -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()]); diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index b4b4d0416fb99..b0e24cf2bdb86 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -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; @@ -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)); diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index ddeae093df5b7..0ee42abb19564 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -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}; @@ -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, + code_mappings: &mut Vec, ) { + 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); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 8f624e0fb2fcb..cfeadf3c7595a 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -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 diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 44b35e8921ec3..880b08d444414 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -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"; @@ -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, } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index e7097ec832753..b94636fea94e1 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -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; @@ -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 { diff --git a/tests/coverage/branch/no-mir-spans.cov-map b/tests/coverage/branch/no-mir-spans.cov-map deleted file mode 100644 index 4f893cba1f815..0000000000000 --- a/tests/coverage/branch/no-mir-spans.cov-map +++ /dev/null @@ -1,63 +0,0 @@ -Function name: no_mir_spans::while_cond -Raw bytes (18): 0x[01, 01, 01, 05, 01, 02, 01, 10, 01, 00, 10, 20, 02, 01, 04, 0b, 00, 10] -Number of files: 1 -- file 0 => $DIR/no-mir-spans.rs -Number of expressions: 1 -- expression 0 operands: lhs = Counter(1), rhs = Counter(0) -Number of file 0 mappings: 2 -- Code(Counter(0)) at (prev + 16, 1) to (start + 0, 16) -- Branch { true: Expression(0, Sub), false: Counter(0) } at (prev + 4, 11) to (start + 0, 16) - true = (c1 - c0) - false = c0 -Highest counter ID seen: c0 - -Function name: no_mir_spans::while_cond_not -Raw bytes (18): 0x[01, 01, 01, 05, 01, 02, 01, 19, 01, 00, 14, 20, 02, 01, 04, 0b, 00, 14] -Number of files: 1 -- file 0 => $DIR/no-mir-spans.rs -Number of expressions: 1 -- expression 0 operands: lhs = Counter(1), rhs = Counter(0) -Number of file 0 mappings: 2 -- Code(Counter(0)) at (prev + 25, 1) to (start + 0, 20) -- Branch { true: Expression(0, Sub), false: Counter(0) } at (prev + 4, 11) to (start + 0, 20) - true = (c1 - c0) - false = c0 -Highest counter ID seen: c0 - -Function name: no_mir_spans::while_op_and -Raw bytes (31): 0x[01, 01, 04, 09, 05, 09, 01, 0f, 09, 01, 05, 03, 01, 22, 01, 00, 12, 20, 05, 02, 05, 0b, 00, 10, 20, 06, 0a, 00, 14, 00, 19] -Number of files: 1 -- file 0 => $DIR/no-mir-spans.rs -Number of expressions: 4 -- expression 0 operands: lhs = Counter(2), rhs = Counter(1) -- expression 1 operands: lhs = Counter(2), rhs = Counter(0) -- expression 2 operands: lhs = Expression(3, Add), rhs = Counter(2) -- expression 3 operands: lhs = Counter(0), rhs = Counter(1) -Number of file 0 mappings: 3 -- Code(Counter(0)) at (prev + 34, 1) to (start + 0, 18) -- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 5, 11) to (start + 0, 16) - true = c1 - false = (c2 - c1) -- Branch { true: Expression(1, Sub), false: Expression(2, Sub) } at (prev + 0, 20) to (start + 0, 25) - true = (c2 - c0) - false = ((c0 + c1) - c2) -Highest counter ID seen: c1 - -Function name: no_mir_spans::while_op_or -Raw bytes (29): 0x[01, 01, 03, 09, 05, 09, 0b, 01, 05, 03, 01, 2d, 01, 00, 11, 20, 05, 02, 05, 0b, 00, 10, 20, 06, 01, 00, 14, 00, 19] -Number of files: 1 -- file 0 => $DIR/no-mir-spans.rs -Number of expressions: 3 -- expression 0 operands: lhs = Counter(2), rhs = Counter(1) -- expression 1 operands: lhs = Counter(2), rhs = Expression(2, Add) -- expression 2 operands: lhs = Counter(0), rhs = Counter(1) -Number of file 0 mappings: 3 -- Code(Counter(0)) at (prev + 45, 1) to (start + 0, 17) -- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 5, 11) to (start + 0, 16) - true = c1 - false = (c2 - c1) -- Branch { true: Expression(1, Sub), false: Counter(0) } at (prev + 0, 20) to (start + 0, 25) - true = (c2 - (c0 + c1)) - false = c0 -Highest counter ID seen: c1 - diff --git a/tests/coverage/branch/no-mir-spans.coverage b/tests/coverage/branch/no-mir-spans.coverage deleted file mode 100644 index 2cae98ed3ff4f..0000000000000 --- a/tests/coverage/branch/no-mir-spans.coverage +++ /dev/null @@ -1,77 +0,0 @@ - LL| |#![feature(coverage_attribute)] - LL| |//@ edition: 2021 - LL| |//@ compile-flags: -Zcoverage-options=branch,no-mir-spans - LL| |//@ llvm-cov-flags: --show-branches=count - LL| | - LL| |// Tests the behaviour of the `-Zcoverage-options=no-mir-spans` debugging flag. - LL| |// The actual code below is just some non-trivial code copied from another test - LL| |// (`while.rs`), and has no particular significance. - LL| | - LL| |macro_rules! no_merge { - LL| | () => { - LL| | for _ in 0..1 {} - LL| | }; - LL| |} - LL| | - LL| 1|fn while_cond() { - LL| | no_merge!(); - LL| | - LL| | let mut a = 8; - LL| | while a > 0 { - ------------------ - | Branch (LL:11): [True: 8, False: 1] - ------------------ - LL| | a -= 1; - LL| | } - LL| |} - LL| | - LL| 1|fn while_cond_not() { - LL| | no_merge!(); - LL| | - LL| | let mut a = 8; - LL| | while !(a == 0) { - ------------------ - | Branch (LL:11): [True: 8, False: 1] - ------------------ - LL| | a -= 1; - LL| | } - LL| |} - LL| | - LL| 1|fn while_op_and() { - LL| | no_merge!(); - LL| | - LL| | let mut a = 8; - LL| | let mut b = 4; - LL| | while a > 0 && b > 0 { - ------------------ - | Branch (LL:11): [True: 5, False: 0] - | Branch (LL:20): [True: 4, False: 1] - ------------------ - LL| | a -= 1; - LL| | b -= 1; - LL| | } - LL| |} - LL| | - LL| 1|fn while_op_or() { - LL| | no_merge!(); - LL| | - LL| | let mut a = 4; - LL| | let mut b = 8; - LL| | while a > 0 || b > 0 { - ------------------ - | Branch (LL:11): [True: 4, False: 5] - | Branch (LL:20): [True: 4, False: 1] - ------------------ - LL| | a -= 1; - LL| | b -= 1; - LL| | } - LL| |} - LL| | - LL| |#[coverage(off)] - LL| |fn main() { - LL| | while_cond(); - LL| | while_cond_not(); - LL| | while_op_and(); - LL| | while_op_or(); - LL| |} - diff --git a/tests/coverage/branch/no-mir-spans.rs b/tests/coverage/branch/no-mir-spans.rs deleted file mode 100644 index acb268f2d4554..0000000000000 --- a/tests/coverage/branch/no-mir-spans.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![feature(coverage_attribute)] -//@ edition: 2021 -//@ compile-flags: -Zcoverage-options=branch,no-mir-spans -//@ llvm-cov-flags: --show-branches=count - -// Tests the behaviour of the `-Zcoverage-options=no-mir-spans` debugging flag. -// The actual code below is just some non-trivial code copied from another test -// (`while.rs`), and has no particular significance. - -macro_rules! no_merge { - () => { - for _ in 0..1 {} - }; -} - -fn while_cond() { - no_merge!(); - - let mut a = 8; - while a > 0 { - a -= 1; - } -} - -fn while_cond_not() { - no_merge!(); - - let mut a = 8; - while !(a == 0) { - a -= 1; - } -} - -fn while_op_and() { - no_merge!(); - - let mut a = 8; - let mut b = 4; - while a > 0 && b > 0 { - a -= 1; - b -= 1; - } -} - -fn while_op_or() { - no_merge!(); - - let mut a = 4; - let mut b = 8; - while a > 0 || b > 0 { - a -= 1; - b -= 1; - } -} - -#[coverage(off)] -fn main() { - while_cond(); - while_cond_not(); - while_op_and(); - while_op_or(); -} diff --git a/tests/ui/instrument-coverage/coverage-options.bad.stderr b/tests/ui/instrument-coverage/coverage-options.bad.stderr index 1a6b30dc8324b..4a272cf97fb00 100644 --- a/tests/ui/instrument-coverage/coverage-options.bad.stderr +++ b/tests/ui/instrument-coverage/coverage-options.bad.stderr @@ -1,2 +1,2 @@ -error: incorrect value `bad` for unstable option `coverage-options` - `block` | `branch` | `condition` | `mcdc` | `no-mir-spans` was expected +error: incorrect value `bad` for unstable option `coverage-options` - `block` | `branch` | `condition` | `mcdc` was expected