Source code
Revision control
Copy as Markdown
Other Tools
From aa77838ad96ae0e8e9edac5c4ba007d7a4fca889 Mon Sep 17 00:00:00 2001
From: Nicolas Guichard <nicolas.guichard@kdab.com>
Date: Wed, 7 Oct 2026 09:33:10 +0200
Subject: [PATCH] [gcov] Add support for -fprofile-abs-path
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 c3a23415c922..99a76b4edbf0 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -292,6 +292,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 88fbd72f4eae..82aba4d4da4f 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2020,6 +2020,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>;
@@ -8246,6 +8250,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 c754c75aa59d..6ba8ebd2bc4d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -527,6 +527,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 63e7c42549d3..df29f8d9c98f 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -688,6 +688,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 8405245b2bc0..e8279ae58e2c 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 78b5dcf21cd0..0c96adddc72c 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.55.0