[clang] [llvm] [gcov] Add support for -fprofile-abs-path (PR #230077)
Nicolas Qiu Guichard via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 8 04:56:10 PDT 2026
https://github.com/nicolas-guichard created https://github.com/llvm/llvm-project/pull/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.
>From aa77838ad96ae0e8e9edac5c4ba007d7a4fca889 Mon Sep 17 00:00:00 2001
From: Nicolas Guichard <nicolas.guichard at 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 c3a23415c922a..99a76b4edbf0e 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -292,6 +292,9 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// 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 88fbd72f4eae5..82aba4d4da4ff 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 c754c75aa59de..6ba8ebd2bc4d8 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 63e7c42549d3f..df29f8d9c98f0 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 0000000000000..839fcda8774d5
--- /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 ab791ada2d351..4aee8720470d4 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 8405245b2bc0a..e8279ae58e2cb 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 78b5dcf21cd03..0c96adddc72cd 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 @@ class GCOVProfiler {
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;
More information about the cfe-commits
mailing list