This reverts https://github.com/rust-lang/rust/commit/659e20fa7524f8fd217476daf5ecbbe366b2ae61 and https://github.com/rust-lang/rust/pull/131829 to restore support for -Zprofile (gcov-style coverage) that was removed from rustc 1.84.0 diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index a25ce9e5a90..5fb2f7d7e7b 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -269,6 +269,11 @@ fn probestack_attr<'ll, 'tcx>(cx: &SimpleCx<'ll>, tcx: TyCtxt<'tcx>) -> Option<& return None; } + // probestack doesn't play nice either with gcov profiling. + if tcx.sess.opts.unstable_opts.profile { + return None; + } + let attr_value = match tcx.sess.target.stack_probes { StackProbeType::None => return None, // Request LLVM to generate the probes inline. If the given LLVM version does not support diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index d87de8b3846..46c57f22f34 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -762,6 +762,7 @@ fn handle_offload<'ll>(cx: &'ll SimpleCx<'_>, old_fn: &llvm::Value) { pgo_use_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), config.instrument_coverage, instr_profile_output_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), + config.instrument_gcov, pgo_sample_use_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), config.debug_info_for_profiling, llvm_selfprofiler, diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index dc941cb41c5..e6757d5b981 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::fmt::{self, Write}; use std::hash::{Hash, Hasher}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::{iter, ptr}; @@ -9,6 +9,7 @@ use rustc_abi::{Align, Size}; use rustc_codegen_ssa::debuginfo::type_names::{VTableNameKind, cpp_like_debuginfo}; use rustc_codegen_ssa::traits::*; +use rustc_fs_util::path_to_c_string; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::bug; @@ -939,8 +940,33 @@ pub(crate) fn build_compile_unit_di_node<'ll, 'tcx>( debug_name_table_kind, ); + if tcx.sess.opts.unstable_opts.profile { + let default_gcda_path = &output_filenames.with_extension("gcda"); + let gcda_path = + tcx.sess.opts.unstable_opts.profile_emit.as_ref().unwrap_or(default_gcda_path); + + let gcov_cu_info = [ + path_to_mdstring(debug_context.llcontext, &output_filenames.with_extension("gcno")), + path_to_mdstring(debug_context.llcontext, gcda_path), + unit_metadata, + ]; + let gcov_metadata = llvm::LLVMMDNodeInContext2( + debug_context.llcontext, + gcov_cu_info.as_ptr(), + gcov_cu_info.len(), + ); + let val = llvm::LLVMMetadataAsValue(debug_context.llcontext, gcov_metadata); + + llvm::LLVMAddNamedMetadataOperand(debug_context.llmod, c"llvm.gcov".as_ptr(), val); + } + return unit_metadata; }; + + fn path_to_mdstring<'ll>(llcx: &'ll llvm::Context, path: &Path) -> &'ll llvm::Metadata { + let path_str = path_to_c_string(path); + unsafe { llvm::LLVMMDStringInContext2(llcx, path_str.as_ptr(), path_str.as_bytes().len()) } + } } /// Creates a `DW_TAG_member` entry inside the DIE represented by the given `type_di_node`. diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 1c63bbcd171..74f563e7470 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -55,6 +55,7 @@ /// A context object for maintaining all state needed by the debuginfo module. pub(crate) struct CodegenUnitDebugContext<'ll, 'tcx> { + llcontext: &'ll llvm::Context, llmod: &'ll llvm::Module, builder: DIBuilderBox<'ll>, created_files: RefCell, &'ll DIFile>>, @@ -70,7 +71,9 @@ pub(crate) fn new(llmod: &'ll llvm::Module) -> Self { debug!("CodegenUnitDebugContext::new"); let builder = DIBuilderBox::new(llmod); // DIBuilder inherits context from the module, so we'd better use the same one + let llcontext = unsafe { llvm::LLVMGetModuleContext(llmod) }; CodegenUnitDebugContext { + llcontext, llmod, builder, created_files: Default::default(), diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 75b3e5955b7..0e7c703777a 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -876,6 +876,7 @@ pub(crate) fn LLVMModuleCreateWithNameInContext( ModuleID: *const c_char, C: &Context, ) -> &Module; + pub(crate) fn LLVMGetModuleContext(M: &Module) -> &Context; pub(crate) safe fn LLVMCloneModule(M: &Module) -> &Module; /// Data layout. See Module::getDataLayout. @@ -2382,6 +2383,7 @@ pub(crate) fn LLVMRustOptimize<'a>( PGOUsePath: *const c_char, InstrumentCoverage: bool, InstrProfileOutput: *const c_char, + InstrumentGCOV: bool, PGOSampleUsePath: *const c_char, DebugInfoForProfiling: bool, llvm_selfprofiler: *mut c_void, diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 3e36bd8552b..e64a0110a08 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -84,6 +84,7 @@ pub struct ModuleConfig { pub pgo_sample_use: Option, pub debug_info_for_profiling: bool, pub instrument_coverage: bool, + pub instrument_gcov: bool, pub sanitizer: SanitizerSet, pub sanitizer_recover: SanitizerSet, @@ -116,7 +117,12 @@ pub struct ModuleConfig { } impl ModuleConfig { - fn new(kind: ModuleKind, tcx: TyCtxt<'_>, no_builtins: bool) -> ModuleConfig { + fn new( + kind: ModuleKind, + tcx: TyCtxt<'_>, + no_builtins: bool, + is_compiler_builtins: bool, + ) -> ModuleConfig { // If it's a regular module, use `$regular`, otherwise use `$other`. // `$regular` and `$other` are evaluated lazily. macro_rules! if_regular { @@ -175,6 +181,13 @@ macro_rules! if_regular { pgo_sample_use: if_regular!(sess.opts.unstable_opts.profile_sample_use.clone(), None), debug_info_for_profiling: sess.opts.unstable_opts.debug_info_for_profiling, instrument_coverage: if_regular!(sess.instrument_coverage(), false), + instrument_gcov: if_regular!( + // compiler_builtins overrides the codegen-units settings, + // which is incompatible with -Zprofile which requires that + // only a single codegen unit is used per crate. + sess.opts.unstable_opts.profile && !is_compiler_builtins, + false + ), sanitizer: if_regular!(sess.sanitizers(), SanitizerSet::empty()), sanitizer_dataflow_abilist: if_regular!( @@ -436,11 +449,14 @@ pub(crate) fn start_async_codegen( let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID); let no_builtins = attr::contains_name(crate_attrs, sym::no_builtins); + let is_compiler_builtins = attr::contains_name(crate_attrs, sym::compiler_builtins); let crate_info = CrateInfo::new(tcx, target_cpu); - let regular_config = ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins); - let allocator_config = ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins); + let regular_config = + ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins, is_compiler_builtins); + let allocator_config = + ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins, is_compiler_builtins); let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); let (codegen_worker_send, codegen_worker_receive) = channel(); diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index d075f94ef85..01cf5d63bff 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -851,6 +851,8 @@ macro_rules! tracked { tracked!(plt, Some(true)); tracked!(polonius, Polonius::Legacy); tracked!(precise_enum_drop_elaboration, false); + tracked!(profile, true); + tracked!(profile_emit, Some(PathBuf::from("abc"))); tracked!(profile_sample_use, Some(PathBuf::from("abc"))); tracked!(profiler_runtime, "abc".to_string()); tracked!(reg_struct_return, true); diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index 95cbec1b37b..10507f5bd51 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -38,6 +38,7 @@ #include "llvm/Transforms/IPO/ThinLTOBitcodeWriter.h" #include "llvm/Transforms/Instrumentation/AddressSanitizer.h" #include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h" +#include "llvm/Transforms/Instrumentation/GCOVProfiler.h" #include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h" #include "llvm/Transforms/Instrumentation/InstrProfiling.h" #include "llvm/Transforms/Instrumentation/MemorySanitizer.h" @@ -564,8 +565,9 @@ extern "C" LLVMRustResult LLVMRustOptimize( bool PrintBeforeEnzyme, bool PrintAfterEnzyme, bool PrintPasses, LLVMRustSanitizerOptions *SanitizerOptions, const char *PGOGenPath, const char *PGOUsePath, bool InstrumentCoverage, - const char *InstrProfileOutput, const char *PGOSampleUsePath, - bool DebugInfoForProfiling, void *LlvmSelfProfiler, + const char *InstrProfileOutput, bool InstrumentGCOV, + const char *PGOSampleUsePath, bool DebugInfoForProfiling, + void *LlvmSelfProfiler, LLVMRustSelfProfileBeforePassCallback BeforePassCallback, LLVMRustSelfProfileAfterPassCallback AfterPassCallback, const char *ExtraPasses, size_t ExtraPassesLen, const char *LLVMPlugins, @@ -707,6 +709,13 @@ extern "C" LLVMRustResult LLVMRustOptimize( }); } + if (InstrumentGCOV) { + PipelineStartEPCallbacks.push_back( + [](ModulePassManager &MPM, OptimizationLevel Level) { + MPM.addPass(GCOVProfilerPass(GCOVOptions::getDefault())); + }); + } + if (InstrumentCoverage) { PipelineStartEPCallbacks.push_back( [InstrProfileOutput](ModulePassManager &MPM, OptimizationLevel Level) { diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 4000f12459a..dfceb47a1af 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -1003,7 +1003,7 @@ fn inject_panic_runtime(&mut self, tcx: TyCtxt<'_>, krate: &ast::Crate) { fn inject_profiler_runtime(&mut self, tcx: TyCtxt<'_>) { let needs_profiler_runtime = - tcx.sess.instrument_coverage() || tcx.sess.opts.cg.profile_generate.enabled(); + tcx.sess.instrument_coverage() || tcx.sess.opts.unstable_opts.profile || tcx.sess.opts.cg.profile_generate.enabled(); if !needs_profiler_runtime || tcx.sess.opts.unstable_opts.no_profiler_runtime { return; } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index f326442c087..0750a085672 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2473,7 +2473,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let output_types = parse_output_types(early_dcx, &unstable_opts, matches); let mut cg = CodegenOptions::build(early_dcx, matches, &mut target_modifiers); - let (disable_local_thinlto, codegen_units) = should_override_cgus_and_disable_thinlto( + let (disable_local_thinlto, mut codegen_units) = should_override_cgus_and_disable_thinlto( early_dcx, &output_types, matches, @@ -2492,6 +2492,18 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let assert_incr_state = parse_assert_incr_state(early_dcx, &unstable_opts.assert_incr_state); + if unstable_opts.profile && incremental.is_some() { + early_dcx.early_fatal("can't instrument with gcov profiling when compiling incrementally"); + } + if unstable_opts.profile { + match codegen_units { + Some(1) => {} + None => codegen_units = Some(1), + Some(_) => early_dcx + .early_fatal("can't instrument with gcov profiling with multiple codegen units"), + } + } + if cg.profile_generate.enabled() && cg.profile_use.is_some() { early_dcx.early_fatal("options `-C profile-generate` and `-C profile-use` are exclusive"); } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 2b83d1225c9..7b940cbfeee 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2581,8 +2581,13 @@ pub(crate) fn parse_align(slot: &mut Option, v: Option<&str>) -> bool { proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, parse_proc_macro_execution_strategy, [UNTRACKED], "how to run proc-macro code (default: same-thread)"), + profile: bool = (false, parse_bool, [TRACKED], + "insert profiling code (default: no)"), profile_closures: bool = (false, parse_no_value, [UNTRACKED], "profile size of closures"), + profile_emit: Option = (None, parse_opt_pathbuf, [TRACKED], + "file path to emit profiling data at runtime when using 'profile' \ + (default based on relative source path)"), profile_sample_use: Option = (None, parse_opt_pathbuf, [TRACKED], "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], diff --git a/src/doc/rustc/src/instrument-coverage.md b/src/doc/rustc/src/instrument-coverage.md index 57679f82f48..0f88bec2c71 100644 --- a/src/doc/rustc/src/instrument-coverage.md +++ b/src/doc/rustc/src/instrument-coverage.md @@ -2,8 +2,12 @@ ## Introduction -This document describes how to enable and use LLVM instrumentation-based coverage, -via the `-C instrument-coverage` compiler flag. +The Rust compiler includes two code coverage implementations: + +- A GCC-compatible, gcov-based coverage implementation, enabled with `-Z profile`, which derives coverage data based on DebugInfo. +- A source-based code coverage implementation, enabled with `-C instrument-coverage`, which uses LLVM's native, efficient coverage instrumentation to generate very precise coverage data. + +This document describes how to enable and use the LLVM instrumentation-based coverage, via the `-C instrument-coverage` compiler flag. ## How it works diff --git a/src/doc/unstable-book/src/compiler-flags/profile.md b/src/doc/unstable-book/src/compiler-flags/profile.md new file mode 100644 index 00000000000..71303bfaff2 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/profile.md @@ -0,0 +1,27 @@ +# `profile` + +The tracking issue for this feature is: [#42524](https://github.com/rust-lang/rust/issues/42524). + +------------------------ + +This feature allows the generation of code coverage reports. + +Set the `-Zprofile` compiler flag in order to enable gcov profiling. + +For example: +```Bash +cargo new testgcov --bin +cd testgcov +export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +export CARGO_INCREMENTAL=0 +cargo build +cargo run +``` + +Once you've built and run your program, files with the `gcno` (after build) and `gcda` (after execution) extensions will be created. +You can parse them with [llvm-cov gcov](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-gcov) or [grcov](https://github.com/mozilla/grcov). + +Please note that `RUSTFLAGS` by default applies to everything that cargo builds and runs during a build! +When the `--target` flag is explicitly passed to cargo, the `RUSTFLAGS` no longer apply to build scripts and procedural macros. +For more fine-grained control consider passing a `RUSTC_WRAPPER` program to cargo that only adds the profiling flags to +rustc for the specific crates you want to profile. diff --git a/tests/run-make/profile/rmake.rs b/tests/run-make/profile/rmake.rs new file mode 100644 index 00000000000..58a1b53c040 --- /dev/null +++ b/tests/run-make/profile/rmake.rs @@ -0,0 +1,21 @@ +// This test revolves around the rustc flag -Z profile, which should +// generate a .gcno file (initial profiling information) as well +// as a .gcda file (branch counters). The path where these are emitted +// should also be configurable with -Z profile-emit. This test checks +// that the files are produced, and then that the latter flag is respected. +// See https://github.com/rust-lang/rust/pull/42433 + +//@ ignore-cross-compile +//@ needs-profiler-runtime + +use run_make_support::{path, run, rustc}; + +fn main() { + rustc().arg("-g").arg("-Zprofile").input("test.rs").run(); + run("test"); + assert!(path("test.gcno").exists(), "no .gcno file"); + assert!(path("test.gcda").exists(), "no .gcda file"); + rustc().arg("-g").arg("-Zprofile").arg("-Zprofile-emit=abc/abc.gcda").input("test.rs").run(); + run("test"); + assert!(path("abc/abc.gcda").exists(), "gcda file not emitted to defined path"); +} diff --git a/tests/run-make/profile/test.rs b/tests/run-make/profile/test.rs new file mode 100644 index 00000000000..f328e4d9d04 --- /dev/null +++ b/tests/run-make/profile/test.rs @@ -0,0 +1 @@ +fn main() {}