Source code
Revision control
Copy as Markdown
Other Tools
From 88de0d5aed552bffe56a9eedde21894e349abd00 Mon Sep 17 00:00:00 2001
From: Nicolas Qiu Guichard <nicolas@guichard.eu>
Date: Fri, 23 Oct 2026 17:11:04 +0200
Subject: [PATCH] [gcov] Add support for -fprofile-abs-path (#230077)
GCC has a -fprofile-abs-path option to use absolute paths to source
files in GCNO files. Citing GCC's documentation:
> Automatically convert relative source file names to absolute path
names in the .gcno files. This allows gcov to find the correct sources
in projects where compilations occur with different working directories.
This is particularly useful when doing unity builds where a generated
source file includes many original source files, as otherwise the GCNO
refers to the original source files by relative paths.
When the -fprofile-abs-path flag is passed to clang/clang++, the new
-coverage-notes-abs-paths flag is passed to cc1, which calls
vfs::FileSystem::makeAbsolute to get the absolute path.
getFilename is moved to a member function in GCOVProfiler so that it can
access the GCOVOptions. This also removes the need for its VFS argument
and the getVirtualFileSystem member function.
---
clang/include/clang/Basic/CodeGenOptions.h | 3 ++
clang/include/clang/Options/Options.td | 6 ++++
clang/lib/CodeGen/BackendUtil.cpp | 1 +
clang/lib/Driver/ToolChains/Clang.cpp | 6 ++++
.../CodeGen/code-coverage-absolute-paths.c | 13 +++++++++
clang/test/Driver/coverage.c | 8 +++++
.../llvm/Transforms/Utils/Instrumentation.h | 3 ++
.../Instrumentation/GCOVProfiling.cpp | 29 ++++++++++++-------
8 files changed, 58 insertions(+), 11 deletions(-)
create mode 100644 clang/test/CodeGen/code-coverage-absolute-paths.c
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index c12434135a19..72d173b1a698 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -255,6 +255,9 @@ public:
/// The filename with path we use for coverage notes files.
std::string CoverageNotesFile;
+ /// Whether to force absolute paths in coverage notes files.
+ bool UseAbsolutePathsInNotesFile;
+
/// Regexes separated by a semi-colon to filter the files to instrument.
std::string ProfileFilterFiles;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index f893d858d526..606c2cc6020a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1927,6 +1927,10 @@ def fno_profile_use : Flag<["-"], "fno-profile-use">,
def ftest_coverage : Flag<["-"], "ftest-coverage">, Group<f_Group>,
HelpText<"Produce gcov notes files (*.gcno)">;
def fno_test_coverage : Flag<["-"], "fno-test-coverage">, Group<f_Group>;
+def fprofile_abs_path : Flag<["-"], "fprofile-abs-path">,
+ Group<f_Group>, Visibility<[ClangOption, CLOption]>,
+ HelpText<"Use absolute paths in gcov notes files (*.gcno)">;
+def fno_profile_abs_path : Flag<["-"], "fno-profile-abs-path">, Group<f_Group>;
def fprofile_arcs : Flag<["-"], "fprofile-arcs">, Group<f_Group>,
HelpText<"Instrument code to produce gcov data files (*.gcda)">;
def fno_profile_arcs : Flag<["-"], "fno-profile-arcs">, Group<f_Group>;
@@ -8028,6 +8032,8 @@ def : Joined<["-"], "coverage-data-file=">,
MarshallingInfoString<CodeGenOpts<"CoverageDataFile">>;
def : Joined<["-"], "coverage-notes-file=">,
MarshallingInfoString<CodeGenOpts<"CoverageNotesFile">>;
+def : Joined<["-"], "coverage-notes-abs-paths">,
+ MarshallingInfoFlag<CodeGenOpts<"UseAbsolutePathsInNotesFile">>;
def coverage_version_EQ : Joined<["-"], "coverage-version=">,
HelpText<"Four-byte version string for gcov files.">;
def dump_coverage_mapping : Flag<["-"], "dump-coverage-mapping">,
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index e3ae446fbe81..5e5f6b099b14 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -565,6 +565,7 @@ getGCOVOptions(const CodeGenOptions &CodeGenOpts, const LangOptions &LangOpts) {
// LLVM's -default-gcov-version flag is set to something invalid.
GCOVOptions Options;
Options.EmitNotes = !CodeGenOpts.CoverageNotesFile.empty();
+ Options.UseAbsolutePathsInNotes = CodeGenOpts.UseAbsolutePathsInNotesFile;
Options.EmitData = !CodeGenOpts.CoverageDataFile.empty();
llvm::copy(CodeGenOpts.CoverageVersion, std::begin(Options.Version));
Options.NoRedZone = CodeGenOpts.DisableRedZone;
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index db7893c547ee..5ba61b6adf3b 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -689,6 +689,12 @@ static void addPGOAndCoverageFlags(const ToolChain &TC, Compilation &C,
if (EmitCovNotes) {
CmdArgs.push_back(
Args.MakeArgString("-coverage-notes-file=" + CoverageFilename));
+
+ bool UseAbsolutePathsInNotes =
+ Args.hasFlag(options::OPT_fprofile_abs_path,
+ options::OPT_fno_profile_abs_path, false);
+ if (UseAbsolutePathsInNotes)
+ CmdArgs.push_back(Args.MakeArgString("-coverage-notes-abs-paths"));
}
if (EmitCovData) {
diff --git a/clang/test/CodeGen/code-coverage-absolute-paths.c b/clang/test/CodeGen/code-coverage-absolute-paths.c
new file mode 100644
index 000000000000..839fcda8774d
--- /dev/null
+++ b/clang/test/CodeGen/code-coverage-absolute-paths.c
@@ -0,0 +1,13 @@
+// RUN: rm -rf %t && mkdir %t
+// RUN: cd %S
+
+// RUN: %clang_cc1 -emit-llvm-bc -o /dev/null -coverage-notes-file=%t/notes-relative.gcno %{s:basename}
+// RUN: FileCheck --check-prefix=RELATIVE %s --input-file %t/notes-relative.gcno -DABSOLUTE_PATH=%s
+
+// RUN: %clang_cc1 -emit-llvm-bc -o /dev/null -coverage-notes-file=%t/notes-absolute.gcno -coverage-notes-abs-paths %{s:basename}
+// RUN: FileCheck --check-prefix=ABSOLUTE %s --input-file %t/notes-absolute.gcno -DABSOLUTE_PATH=%s
+
+void test() {}
+
+// RELATIVE-NOT: [[ABSOLUTE_PATH]]
+// ABSOLUTE: [[ABSOLUTE_PATH]]
diff --git a/clang/test/Driver/coverage.c b/clang/test/Driver/coverage.c
index ab791ada2d35..4aee8720470d 100644
--- a/clang/test/Driver/coverage.c
+++ b/clang/test/Driver/coverage.c
@@ -32,6 +32,14 @@
// PROFILE-DIR: "-coverage-data-file=abc
+/// Test -fprofile-abs-path
+// RUN: %clang -### -S -ftest-coverage %s 2>&1 | FileCheck --check-prefix=NO-PROFILE-ABS-PATH %s
+// RUN: %clang -### -S -fprofile-abs-path %s 2>&1 | FileCheck --check-prefix=NO-PROFILE-ABS-PATH %s
+// RUN: %clang -### -S -ftest-coverage -fprofile-abs-path %s 2>&1 | FileCheck --check-prefix=PROFILE-ABS-PATH %s
+
+// NO-PROFILE-ABS-PATH-NOT: "-coverage-notes-abs-paths"
+// PROFILE-ABS-PATH: "-coverage-notes-abs-paths"
+
/// These should only get passed if any of --coverage, -ftest-coverage, or
/// -fprofile-arcs is passed.
// RUN: %clang -### -c %s 2>&1 | FileCheck --check-prefix=NO-COV %s
diff --git a/llvm/include/llvm/Transforms/Utils/Instrumentation.h b/llvm/include/llvm/Transforms/Utils/Instrumentation.h
index 95a985ba3f0c..8921304ba677 100644
--- a/llvm/include/llvm/Transforms/Utils/Instrumentation.h
+++ b/llvm/include/llvm/Transforms/Utils/Instrumentation.h
@@ -67,6 +67,9 @@ struct GCOVOptions {
// Specify whether to emit .gcno files.
bool EmitNotes;
+ // Specify whether to force absolute paths in coverage notes files.
+ bool UseAbsolutePathsInNotes;
+
// Specify whether to modify the program to emit .gcda files when run.
bool EmitData;
diff --git a/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp b/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp
index 9f47c43bb9a0..8970b5f25814 100644
--- a/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp
+++ b/llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp
@@ -76,6 +76,7 @@ static unsigned wordsOfString(StringRef s) {
GCOVOptions GCOVOptions::getDefault() {
GCOVOptions Options;
Options.EmitNotes = true;
+ Options.UseAbsolutePathsInNotes = false;
Options.EmitData = true;
Options.NoRedZone = false;
Options.Atomic = AtomicCounter;
@@ -113,7 +114,7 @@ public:
os->write_zeros(4 - s.size() % 4);
}
void writeBytes(const char *Bytes, int Size) { os->write(Bytes, Size); }
- vfs::FileSystem &getVirtualFileSystem() const { return VFS; }
+ SmallString<128> getFilename(const DIScope *SP);
private:
// Create the .gcno files for the Module based on DebugInfo.
@@ -216,16 +217,22 @@ static StringRef getFunctionName(const DISubprogram *SP) {
/// Extract a filename for a DIScope.
///
-/// Prefer relative paths in the coverage notes. Clang also may split
-/// up absolute paths into a directory and filename component. When
-/// the relative path doesn't exist, reconstruct the absolute path.
-static SmallString<128> getFilename(const DIScope *SP, vfs::FileSystem &VFS) {
+/// Unless -fprofile-abs-path has been passed, prefer relative paths in the
+/// coverage notes. Clang also may split up absolute paths into a directory and
+/// filename component. When the relative path doesn't exist, reconstruct the
+/// absolute path.
+SmallString<128> GCOVProfiler::getFilename(const DIScope *SP) {
SmallString<128> Path;
StringRef RelPath = SP->getFilename();
- if (VFS.exists(RelPath))
+ if (VFS.exists(RelPath)) {
Path = RelPath;
- else
+ if (Options.UseAbsolutePathsInNotes) {
+ VFS.makeAbsolute(Path);
+ sys::path::remove_dots(Path, true);
+ }
+ } else {
sys::path::append(Path, SP->getDirectory(), SP->getFilename());
+ }
return Path;
}
@@ -362,7 +369,7 @@ namespace {
void writeOut(uint32_t CfgChecksum) {
write(GCOV_TAG_FUNCTION);
- SmallString<128> Filename = getFilename(SP, P->getVirtualFileSystem());
+ SmallString<128> Filename = P->getFilename(SP);
uint32_t BlockLen = 3 + wordsOfString(getFunctionName(SP));
BlockLen += 1 + wordsOfString(Filename) + 4;
@@ -460,7 +467,7 @@ bool GCOVProfiler::isFunctionInstrumented(const Function &F) {
if (FilterRe.empty() && ExcludeRe.empty()) {
return true;
}
- SmallString<128> Filename = getFilename(F.getSubprogram(), VFS);
+ SmallString<128> Filename = getFilename(F.getSubprogram());
auto It = InstrumentedFiles.find(Filename);
if (It != InstrumentedFiles.end()) {
return It->second;
@@ -795,7 +802,7 @@ bool GCOVProfiler::emitProfileNotes(
// Add the function line number to the lines of the entry block
// to have a counter for the function definition.
uint32_t Line = SP->getLine();
- auto Filename = getFilename(SP, VFS);
+ auto Filename = getFilename(SP);
BranchProbabilityInfo *BPI = GetBPI(F);
BlockFrequencyInfo *BFI = GetBFI(F);
@@ -887,7 +894,7 @@ bool GCOVProfiler::emitProfileNotes(
if (SP != getDISubprogram(Scope))
continue;
- GCOVLines &Lines = Block.getFile(getFilename(Loc->getScope(), VFS));
+ GCOVLines &Lines = Block.getFile(getFilename(Loc->getScope()));
Lines.addLine(Loc.getLine());
}
Line = 0;
--
2.56.0