[llvm] [llvm-profdata][llvm-cov]Fix double-counted coverage for same-named functions across binaries (PR #153679)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 14 13:51:49 PDT 2025
https://github.com/awearden created https://github.com/llvm/llvm-project/pull/153679
This PR is addressing this specific issue: #148279
Right now, the way llvm-profdata handles the merging of coverage reports with two binaries with the same function name leads to errors with the coverage counts. Looking at the mentioned issue, we can see that the coverage counts on the main function are counted twice for each binary the main function ran on, but because the main function only ran once on each binary, the coverage count should be “1”. This can cause issues when comparing coverage across architectures where the same function with different implementation exists in multiple binaries.
This patch fixes this issue by adding the --object-aware-hashing flag.
Example usage:
`llvm-profdata merge --object-aware-hashing=exec1 exec1.profraw --object-aware-hashing=exec2 exec2.profraw -o merged.profdata`
By adding this flag, we can associate each profraw file with the executable it comes from. This will hash the function names with the object file and separate the functions by object file. Then by adding the --merge-binary-coverage flag to llvm-cov, it will merge the coverage counts from the different binaries by considering the object file in the hash.
Example usage:
`llvm-cov show --instr-profile=merged.profdata --object=exec1 --object=exec2 --merge-binary-coverage.`
Using these two flags in conjunction with each other will give the correct counts on each function.
@quic-akaryaki
@quic-areg
@quic-seaswara
@evodius96
@chapuni
>From 40e13df500f6df6e9b02a4f648cbaf3f6f59b910 Mon Sep 17 00:00:00 2001
From: Andres Wearden <andreswearden5 at gmail.com>
Date: Thu, 14 Aug 2025 13:15:38 -0700
Subject: [PATCH] [llvm-profdata][llvm-cov]Fix double-counted coverage for
same-named functions across binaries
---
.../ProfileData/Coverage/CoverageMapping.h | 12 ++-
llvm/include/llvm/ProfileData/InstrProf.h | 19 +++-
.../llvm/ProfileData/InstrProfWriter.h | 6 +-
.../ProfileData/Coverage/CoverageMapping.cpp | 91 +++++++++++++++----
llvm/lib/ProfileData/InstrProfWriter.cpp | 14 ++-
.../llvm-cov/Inputs/merge-same-func-bin1-2.c | 5 +
.../llvm-cov/Inputs/merge-same-func-bin1.c | 8 ++
.../llvm-cov/Inputs/merge-same-func-bin2.c | 2 +
.../tools/llvm-cov/merge-same-func-diff-bin.c | 38 ++++++++
llvm/tools/llvm-cov/CodeCoverage.cpp | 14 ++-
llvm/tools/llvm-cov/CoverageViewOptions.h | 1 +
llvm/tools/llvm-profdata/llvm-profdata.cpp | 69 +++++++++-----
12 files changed, 220 insertions(+), 59 deletions(-)
create mode 100644 llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1-2.c
create mode 100644 llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1.c
create mode 100644 llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin2.c
create mode 100644 llvm/test/tools/llvm-cov/merge-same-func-diff-bin.c
diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
index 7d1a85ba528fc..8b87b4337a75e 100644
--- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
+++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
@@ -993,6 +993,7 @@ class CoverageMapping {
std::vector<FunctionRecord> Functions;
DenseMap<size_t, SmallVector<unsigned, 0>> FilenameHash2RecordIndices;
std::vector<std::pair<std::string, uint64_t>> FuncHashMismatches;
+ DenseMap<std::pair<size_t, hash_code>, unsigned> RecordIndices;
std::optional<bool> SingleByteCoverage;
@@ -1003,7 +1004,8 @@ class CoverageMapping {
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
- CoverageMapping &Coverage);
+ CoverageMapping &Coverage, StringRef ObjectFilename = "",
+ bool MergeBinaryCoverage = false);
// Load coverage records from file.
static Error
@@ -1011,13 +1013,15 @@ class CoverageMapping {
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
CoverageMapping &Coverage, bool &DataFound,
- SmallVectorImpl<object::BuildID> *FoundBinaryIDs = nullptr);
+ SmallVectorImpl<object::BuildID> *FoundBinaryIDs = nullptr,
+ StringRef ObjectFilename = "", bool MergeBinaryCoverage = false);
/// Add a function record corresponding to \p Record.
Error loadFunctionRecord(
const CoverageMappingRecord &Record,
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
- &ProfileReader);
+ &ProfileReader,
+ StringRef ObjectFilename = "", bool MergeBinaryCoverage = false);
/// Look up the indices for function records which are at least partially
/// defined in the specified file. This is guaranteed to return a superset of
@@ -1044,7 +1048,7 @@ class CoverageMapping {
std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
ArrayRef<StringRef> Arches = {}, StringRef CompilationDir = "",
const object::BuildIDFetcher *BIDFetcher = nullptr,
- bool CheckBinaryIDs = false);
+ bool CheckBinaryIDs = false, bool MergeBinaryCoverage = false);
/// The number of functions that couldn't have their profiles mapped.
///
diff --git a/llvm/include/llvm/ProfileData/InstrProf.h b/llvm/include/llvm/ProfileData/InstrProf.h
index 85a9efe73855b..a41756e7d0dff 100644
--- a/llvm/include/llvm/ProfileData/InstrProf.h
+++ b/llvm/include/llvm/ProfileData/InstrProf.h
@@ -513,6 +513,7 @@ class InstrProfSymtab {
LLVM_ABI static StringRef getCanonicalName(StringRef PGOName);
private:
+ StringRef ObjectFilename;
using AddrIntervalMap =
IntervalMap<uint64_t, uint64_t, 4, IntervalMapHalfOpenInfo<uint64_t>>;
StringRef Data;
@@ -640,10 +641,18 @@ class InstrProfSymtab {
// Insert into NameTab so that MD5NameMap (a vector that will be sorted)
// won't have duplicated entries in the first place.
+ uint64_t HashValue = IndexedInstrProf::ComputeHash(SymbolName);
+ std::string HashStr(std::to_string(HashValue));
+ // if ObjectFilename is not empty from the --object-aware-hashing flag, add
+ // ObjectFilename to hash context.
+ if (!ObjectFilename.empty()) {
+ std::string CombinedStr = HashStr + ":" + ObjectFilename.str();
+ StringRef HashRef = CombinedStr;
+ HashValue = IndexedInstrProf::ComputeHash(HashRef);
+ }
auto Ins = NameTab.insert(SymbolName);
if (Ins.second) {
- MD5NameMap.push_back(std::make_pair(
- IndexedInstrProf::ComputeHash(SymbolName), Ins.first->getKey()));
+ MD5NameMap.push_back(std::make_pair(HashValue, Ins.first->getKey()));
Sorted = false;
}
return Error::success();
@@ -777,6 +786,12 @@ StringRef InstrProfSymtab::getFuncOrVarNameIfDefined(uint64_t MD5Hash) const {
StringRef InstrProfSymtab::getFuncOrVarName(uint64_t MD5Hash) const {
finalizeSymtab();
+ std::string TempMD5HashStr = std::to_string(MD5Hash);
+ if (!ObjectFilename.empty()) {
+ std::string CombinedHashStr = TempMD5HashStr + ":" + ObjectFilename.str();
+ llvm::StringRef CombinedHashRef(CombinedHashStr);
+ MD5Hash = IndexedInstrProf::ComputeHash(CombinedHashRef);
+ }
auto Result = llvm::lower_bound(MD5NameMap, MD5Hash,
[](const std::pair<uint64_t, StringRef> &LHS,
uint64_t RHS) { return LHS.first < RHS; });
diff --git a/llvm/include/llvm/ProfileData/InstrProfWriter.h b/llvm/include/llvm/ProfileData/InstrProfWriter.h
index 1b24425e68a9e..4375b280e50d3 100644
--- a/llvm/include/llvm/ProfileData/InstrProfWriter.h
+++ b/llvm/include/llvm/ProfileData/InstrProfWriter.h
@@ -113,7 +113,8 @@ class InstrProfWriter {
/// for this function and the hash and number of counts match, each counter is
/// summed. Optionally scale counts by \p Weight.
LLVM_ABI void addRecord(NamedInstrProfRecord &&I, uint64_t Weight,
- function_ref<void(Error)> Warn);
+ function_ref<void(Error)> Warn,
+ StringRef ObjectFilename = "");
void addRecord(NamedInstrProfRecord &&I, function_ref<void(Error)> Warn) {
addRecord(std::move(I), 1, Warn);
}
@@ -224,7 +225,8 @@ class InstrProfWriter {
private:
void addRecord(StringRef Name, uint64_t Hash, InstrProfRecord &&I,
- uint64_t Weight, function_ref<void(Error)> Warn);
+ uint64_t Weight, function_ref<void(Error)> Warn,
+ StringRef ObjectFilename = "");
bool shouldEncodeData(const ProfilingData &PD);
/// Add a memprof record for a function identified by its \p Id.
diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
index 429ec5c19f1f8..8476800e443e3 100644
--- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
+++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
@@ -824,7 +824,8 @@ class MCDCDecisionRecorder {
Error CoverageMapping::loadFunctionRecord(
const CoverageMappingRecord &Record,
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
- &ProfileReader) {
+ &ProfileReader,
+ StringRef ObjectFilename, bool MergeBinaryCoverage) {
StringRef OrigFuncName = Record.FunctionName;
if (OrigFuncName.empty())
return make_error<CoverageMapError>(coveragemap_error::malformed,
@@ -837,14 +838,21 @@ Error CoverageMapping::loadFunctionRecord(
CounterMappingContext Ctx(Record.Expressions);
+ uint64_t FuncObjectHash = Record.FunctionHash;
+ if (!ObjectFilename.empty() && MergeBinaryCoverage) {
+ std::string HashStr =
+ std::to_string(Record.FunctionHash) + ":" + ObjectFilename.str();
+ llvm::StringRef HashRef(HashStr);
+ FuncObjectHash = IndexedInstrProf::ComputeHash(HashRef);
+ }
std::vector<uint64_t> Counts;
if (ProfileReader) {
if (Error E = ProfileReader.value().get().getFunctionCounts(
- Record.FunctionName, Record.FunctionHash, Counts)) {
+ Record.FunctionName, FuncObjectHash, Counts)) {
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
if (IPE == instrprof_error::hash_mismatch) {
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
- Record.FunctionHash);
+ FuncObjectHash);
return Error::success();
}
if (IPE != instrprof_error::unknown_function)
@@ -863,11 +871,11 @@ Error CoverageMapping::loadFunctionRecord(
BitVector Bitmap;
if (ProfileReader) {
if (Error E = ProfileReader.value().get().getFunctionBitmap(
- Record.FunctionName, Record.FunctionHash, Bitmap)) {
+ Record.FunctionName, FuncObjectHash, Bitmap)) {
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
if (IPE == instrprof_error::hash_mismatch) {
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
- Record.FunctionHash);
+ FuncObjectHash);
return Error::success();
}
if (IPE != instrprof_error::unknown_function)
@@ -942,11 +950,46 @@ Error CoverageMapping::loadFunctionRecord(
Function.pushMCDCRecord(std::move(*Record));
}
- // Don't create records for (filenames, function) pairs we've already seen.
auto FilenamesHash = hash_combine_range(Record.Filenames);
- if (!RecordProvenance[FilenamesHash].insert(hash_value(OrigFuncName)).second)
- return Error::success();
+ std::string HashStr = OrigFuncName.str();
+ auto LogicalFuncKey = std::make_pair(FilenamesHash, hash_value(OrigFuncName));
+ auto It = RecordIndices.find(LogicalFuncKey);
+ if (It != RecordIndices.end()) {
+ auto &ExistingFunction = Functions[It->second];
+ // Create a map of existing regions for lookup.
+ // The key uniquely identifies the source region.
+ using RegionKey =
+ std::tuple<unsigned, unsigned, unsigned, unsigned, unsigned>;
+ std::map<RegionKey, CountedRegion *> ExistingRegionsMap;
+ for (auto &ExistingRegion : ExistingFunction.CountedRegions) {
+ RegionKey Key = {ExistingRegion.FileID, ExistingRegion.LineStart,
+ ExistingRegion.ColumnStart, ExistingRegion.LineEnd,
+ ExistingRegion.ColumnEnd};
+ ExistingRegionsMap[Key] = &ExistingRegion;
+ }
+ // Merge the new regions into the existing function's regions.
+ for (const auto &NewRegion : Function.CountedRegions) {
+ RegionKey Key = {NewRegion.FileID, NewRegion.LineStart,
+ NewRegion.ColumnStart, NewRegion.LineEnd,
+ NewRegion.ColumnEnd};
+ auto MapIt = ExistingRegionsMap.find(Key);
+ if (MapIt != ExistingRegionsMap.end()) {
+ // Region already exists, merge counts by summing the counts.
+ CountedRegion *ExistingRegion = MapIt->second;
+ ExistingRegion->ExecutionCount += NewRegion.ExecutionCount;
+ } else {
+ ExistingFunction.CountedRegions.push_back(NewRegion);
+ }
+ }
+ return Error::success();
+ }
+ RecordIndices.insert({LogicalFuncKey, Functions.size()});
+ // Don't create records for (filenames, function) pairs we've already seen.
+ StringRef Hash(HashStr);
+ if (!RecordProvenance[FilenamesHash].insert(hash_value(Hash)).second) {
+ return Error::success();
+ }
Functions.push_back(std::move(Function));
// Performance optimization: keep track of the indices of the function records
@@ -971,7 +1014,8 @@ Error CoverageMapping::loadFromReaders(
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
- CoverageMapping &Coverage) {
+ CoverageMapping &Coverage, StringRef ObjectFilename,
+ bool MergeBinaryCoverage) {
assert(!Coverage.SingleByteCoverage || !ProfileReader ||
*Coverage.SingleByteCoverage ==
ProfileReader.value().get().hasSingleByteCoverage());
@@ -982,7 +1026,8 @@ Error CoverageMapping::loadFromReaders(
if (Error E = RecordOrErr.takeError())
return E;
const auto &Record = *RecordOrErr;
- if (Error E = Coverage.loadFunctionRecord(Record, ProfileReader))
+ if (Error E = Coverage.loadFunctionRecord(
+ Record, ProfileReader, ObjectFilename, MergeBinaryCoverage))
return E;
}
}
@@ -1013,7 +1058,8 @@ Error CoverageMapping::loadFromFile(
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
CoverageMapping &Coverage, bool &DataFound,
- SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
+ SmallVectorImpl<object::BuildID> *FoundBinaryIDs, StringRef ObjectFilename,
+ bool MergeBinaryCoverage) {
auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN(
Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false);
if (std::error_code EC = CovMappingBufOrErr.getError())
@@ -1043,16 +1089,19 @@ Error CoverageMapping::loadFromFile(
}));
}
DataFound |= !Readers.empty();
- if (Error E = loadFromReaders(Readers, ProfileReader, Coverage))
+ if (Error E = loadFromReaders(Readers, ProfileReader, Coverage,
+ ObjectFilename, MergeBinaryCoverage))
return createFileError(Filename, std::move(E));
return Error::success();
}
-Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
- ArrayRef<StringRef> ObjectFilenames,
- std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
- ArrayRef<StringRef> Arches, StringRef CompilationDir,
- const object::BuildIDFetcher *BIDFetcher, bool CheckBinaryIDs) {
+Expected<std::unique_ptr<CoverageMapping>>
+CoverageMapping::load(ArrayRef<StringRef> ObjectFilenames,
+ std::optional<StringRef> ProfileFilename,
+ vfs::FileSystem &FS, ArrayRef<StringRef> Arches,
+ StringRef CompilationDir,
+ const object::BuildIDFetcher *BIDFetcher,
+ bool CheckBinaryIDs, bool MergeBinaryCoverage) {
std::unique_ptr<IndexedInstrProfReader> ProfileReader;
if (ProfileFilename) {
auto ProfileReaderOrErr =
@@ -1079,9 +1128,11 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
SmallVector<object::BuildID> FoundBinaryIDs;
for (const auto &File : llvm::enumerate(ObjectFilenames)) {
- if (Error E = loadFromFile(File.value(), GetArch(File.index()),
- CompilationDir, ProfileReaderRef, *Coverage,
- DataFound, &FoundBinaryIDs))
+ if (Error E = loadFromFile(
+ File.value(), GetArch(File.index()), CompilationDir,
+ ProfileReaderRef, *Coverage, DataFound, &FoundBinaryIDs,
+ MergeBinaryCoverage ? ObjectFilenames[File.index()] : "",
+ MergeBinaryCoverage))
return std::move(E);
}
diff --git a/llvm/lib/ProfileData/InstrProfWriter.cpp b/llvm/lib/ProfileData/InstrProfWriter.cpp
index df807fc02b910..c4661652cb133 100644
--- a/llvm/lib/ProfileData/InstrProfWriter.cpp
+++ b/llvm/lib/ProfileData/InstrProfWriter.cpp
@@ -154,10 +154,11 @@ void InstrProfWriter::setValueProfDataEndianness(llvm::endianness Endianness) {
void InstrProfWriter::setOutputSparse(bool Sparse) { this->Sparse = Sparse; }
void InstrProfWriter::addRecord(NamedInstrProfRecord &&I, uint64_t Weight,
- function_ref<void(Error)> Warn) {
+ function_ref<void(Error)> Warn,
+ StringRef ObjectFilename) {
auto Name = I.Name;
auto Hash = I.Hash;
- addRecord(Name, Hash, std::move(I), Weight, Warn);
+ addRecord(Name, Hash, std::move(I), Weight, Warn, ObjectFilename);
}
void InstrProfWriter::overlapRecord(NamedInstrProfRecord &&Other,
@@ -193,9 +194,16 @@ void InstrProfWriter::overlapRecord(NamedInstrProfRecord &&Other,
void InstrProfWriter::addRecord(StringRef Name, uint64_t Hash,
InstrProfRecord &&I, uint64_t Weight,
- function_ref<void(Error)> Warn) {
+ function_ref<void(Error)> Warn,
+ StringRef ObjectFilename) {
auto &ProfileDataMap = FunctionData[Name];
+ // Add object file name to hash value if --object-aware-hashing flag is used.
+ if (!ObjectFilename.empty()) {
+ std::string HashStr = std::to_string(Hash) + ":" + ObjectFilename.str();
+ llvm::StringRef HashRef(HashStr);
+ Hash = IndexedInstrProf::ComputeHash(HashRef);
+ }
auto [Where, NewFunc] = ProfileDataMap.try_emplace(Hash);
InstrProfRecord &Dest = Where->second;
diff --git a/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1-2.c b/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1-2.c
new file mode 100644
index 0000000000000..428cbfd163a72
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1-2.c
@@ -0,0 +1,5 @@
+int foo() { return 0; }
+
+int bar() { return 0; }
+
+int bun() { return 0; }
diff --git a/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1.c b/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1.c
new file mode 100644
index 0000000000000..863b2baa017f6
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin1.c
@@ -0,0 +1,8 @@
+extern int foo();
+extern int bar();
+extern int bun();
+
+
+int main() {
+ return foo() + bar() + bun();
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin2.c b/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin2.c
new file mode 100644
index 0000000000000..2fcea0fce9546
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/merge-same-func-bin2.c
@@ -0,0 +1,2 @@
+int baz() { return 0; }
+int main() { return 1; }
diff --git a/llvm/test/tools/llvm-cov/merge-same-func-diff-bin.c b/llvm/test/tools/llvm-cov/merge-same-func-diff-bin.c
new file mode 100644
index 0000000000000..f09192daa175c
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/merge-same-func-diff-bin.c
@@ -0,0 +1,38 @@
+// RUN: clang -O0 -fprofile-instr-generate -fcoverage-mapping \
+// RUN: %S/Inputs/merge-same-func-bin1.c %S/Inputs/merge-same-func-bin1-2.c -o %t.bin1
+// RUN: %t.bin1
+// RUN: cp default.profraw %t.bin1.profraw
+//
+
+// RUN: clang -O0 -fprofile-instr-generate -fcoverage-mapping \
+// RUN: %S/Inputs/merge-same-func-bin2.c %S/Inputs/merge-same-func-bin1-2.c -o %t.bin2
+// RUN: %t.bin2 || true
+// RUN: cp default.profraw %t.bin2.profraw
+//---------------- merge the raw profiles ------------------------------------//
+// RUN: llvm-profdata merge --object-aware-hashing=%t.bin2 %t.bin2.profraw \
+// RUN: --object-aware-hashing=%t.bin1 %t.bin1.profraw \
+// RUN: -o %t.profdata
+//
+
+// RUN: llvm-cov show -instr-profile=%t.profdata --object=%t.bin2 --object=%t.bin1 --merge-binary-coverage | FileCheck %s
+//
+// CHECK-LABEL: {{.*merge-same-func-bin1-2\.c}}:
+// CHECK: 1| 1|int foo() { return 0; }
+// CHECK: 2| |
+// CHECK: 3| 1|int bar() { return 0; }
+// CHECK: 4| |
+// CHECK: 5| 1|int bun() { return 0; }
+//
+// CHECK-LABEL: {{.*merge-same-func-bin1\.c}}:
+// CHECK: 1| |extern int foo();
+// CHECK: 2| |extern int bar();
+// CHECK: 3| |extern int bun();
+// CHECK: 4| |
+// CHECK: 5| |
+// CHECK: 6| 1|int main()
+// CHECK: 7| 1| return foo() + bar() + bun();
+// CHECK: 8| 1|}
+//
+// CHECK-LABEL: {{.*merge-same-func-bin2\.c}}:
+// CHECK: 1| 0|int baz() { return 0; }
+// CHECK: 2| 1|int main() { return 1; }
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
index 6c66858c4de8c..f827a6c3620b8 100644
--- a/llvm/tools/llvm-cov/CodeCoverage.cpp
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -462,9 +462,10 @@ std::unique_ptr<CoverageMapping> CodeCoverageTool::load() {
ObjectFilename);
}
auto FS = vfs::getRealFileSystem();
- auto CoverageOrErr = CoverageMapping::load(
- ObjectFilenames, PGOFilename, *FS, CoverageArches,
- ViewOpts.CompilationDirectory, BIDFetcher.get(), CheckBinaryIDs);
+ auto CoverageOrErr =
+ CoverageMapping::load(ObjectFilenames, PGOFilename, *FS, CoverageArches,
+ ViewOpts.CompilationDirectory, BIDFetcher.get(),
+ CheckBinaryIDs, ViewOpts.MergeBinaryCoverage);
if (Error E = CoverageOrErr.takeError()) {
error("failed to load coverage: " + toString(std::move(E)));
return nullptr;
@@ -801,6 +802,12 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
"check-binary-ids", cl::desc("Fail if an object couldn't be found for a "
"binary ID in the profile"));
+ cl::opt<bool> MergeBinaryCoverage(
+ "merge-binary-coverage",
+ cl::desc("Enable merging of coverage profiles from binaries compiled for "
+ "different architectures"),
+ cl::init(false));
+
auto commandLineParser = [&, this](int argc, const char **argv) -> int {
cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
ViewOpts.Debug = DebugDump;
@@ -966,6 +973,7 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
ViewOpts.ExportSummaryOnly = SummaryOnly;
ViewOpts.NumThreads = NumThreads;
ViewOpts.CompilationDirectory = CompilationDirectory;
+ ViewOpts.MergeBinaryCoverage = MergeBinaryCoverage;
return 0;
};
diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h
index 1f6ad570f86f2..22bec8df26409 100644
--- a/llvm/tools/llvm-cov/CoverageViewOptions.h
+++ b/llvm/tools/llvm-cov/CoverageViewOptions.h
@@ -47,6 +47,7 @@ struct CoverageViewOptions {
bool SkipFunctions;
bool SkipBranches;
bool BinaryCounters;
+ bool MergeBinaryCoverage;
OutputFormat Format;
BranchOutputType ShowBranches;
std::string ShowOutputDirectory;
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index 207ae2ddd4cf2..3854f3ba08f77 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -196,6 +196,12 @@ static cl::opt<std::string> RemappingFile("remapping-file",
cl::desc("Symbol remapping file"));
static cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"),
cl::aliasopt(RemappingFile));
+static cl::list<std::string> ObjectAwareHashing(
+ "object-aware-hashing",
+ cl::desc("Includes the object file name when hashing function names "
+ "and control flow hashes, allowing functions from different "
+ "binaries to be distinguished"),
+ cl::sub(MergeSubcommand));
static cl::opt<bool>
UseMD5("use-md5", cl::init(false), cl::Hidden,
cl::desc("Choose to use MD5 to represent string in name table (only "
@@ -694,19 +700,27 @@ static void
loadInput(const WeightedFile &Input, SymbolRemapper *Remapper,
const InstrProfCorrelator *Correlator, const StringRef ProfiledBinary,
WriterContext *WC, const object::BuildIDFetcher *BIDFetcher = nullptr,
- const ProfCorrelatorKind *BIDFetcherCorrelatorKind = nullptr) {
+ const ProfCorrelatorKind *BIDFetcherCorrelatorKind = nullptr,
+ StringRef ObjectAwareHashing = "") {
std::unique_lock<std::mutex> CtxGuard{WC->Lock};
// Copy the filename, because llvm::ThreadPool copied the input "const
// WeightedFile &" by value, making a reference to the filename within it
// invalid outside of this packaged task.
std::string Filename = Input.Filename;
+ std::string ProfileFile = Input.Filename;
+ StringRef ObjectFilename = "";
+
+ StringRef FilenameRef = Filename;
+ if (!ObjectAwareHashing.empty()) {
+ ObjectFilename = ObjectAwareHashing.data();
+ }
using ::llvm::memprof::RawMemProfReader;
- if (RawMemProfReader::hasFormat(Input.Filename)) {
- auto ReaderOrErr = RawMemProfReader::create(Input.Filename, ProfiledBinary);
+ if (RawMemProfReader::hasFormat(ProfileFile)) {
+ auto ReaderOrErr = RawMemProfReader::create(ProfileFile, ProfiledBinary);
if (!ReaderOrErr) {
- exitWithError(ReaderOrErr.takeError(), Input.Filename);
+ exitWithError(ReaderOrErr.takeError(), ProfileFile);
}
std::unique_ptr<RawMemProfReader> Reader = std::move(ReaderOrErr.get());
// Check if the profile types can be merged, e.g. clang frontend profiles
@@ -790,7 +804,7 @@ loadInput(const WeightedFile &Input, SymbolRemapper *Remapper,
const ProfCorrelatorKind CorrelatorKind = BIDFetcherCorrelatorKind
? *BIDFetcherCorrelatorKind
: ProfCorrelatorKind::NONE;
- auto ReaderOrErr = InstrProfReader::create(Input.Filename, *FS, Correlator,
+ auto ReaderOrErr = InstrProfReader::create(ProfileFile, *FS, Correlator,
BIDFetcher, CorrelatorKind, Warn);
if (Error E = ReaderOrErr.takeError()) {
// Skip the empty profiles by returning silently.
@@ -817,19 +831,22 @@ loadInput(const WeightedFile &Input, SymbolRemapper *Remapper,
I.Name = (*Remapper)(I.Name);
const StringRef FuncName = I.Name;
bool Reported = false;
- WC->Writer.addRecord(std::move(I), Input.Weight, [&](Error E) {
- if (Reported) {
- consumeError(std::move(E));
- return;
- }
- Reported = true;
- // Only show hint the first time an error occurs.
- auto [ErrCode, Msg] = InstrProfError::take(std::move(E));
- std::unique_lock<std::mutex> ErrGuard{WC->ErrLock};
- bool firstTime = WC->WriterErrorCodes.insert(ErrCode).second;
- handleMergeWriterError(make_error<InstrProfError>(ErrCode, Msg),
- Input.Filename, FuncName, firstTime);
- });
+ WC->Writer.addRecord(
+ std::move(I), Input.Weight,
+ [&](Error E) {
+ if (Reported) {
+ consumeError(std::move(E));
+ return;
+ }
+ Reported = true;
+ // Only show hint the first time an error occurs.
+ auto [ErrCode, Msg] = InstrProfError::take(std::move(E));
+ std::unique_lock<std::mutex> ErrGuard{WC->ErrLock};
+ bool firstTime = WC->WriterErrorCodes.insert(ErrCode).second;
+ handleMergeWriterError(make_error<InstrProfError>(ErrCode, Msg),
+ Input.Filename, FuncName, firstTime);
+ },
+ ObjectFilename);
}
if (KeepVTableSymbols) {
@@ -1038,18 +1055,20 @@ static void mergeInstrProfile(const WeightedFileVector &Inputs,
MaxTraceLength));
if (NumThreads == 1) {
- for (const auto &Input : Inputs)
- loadInput(Input, Remapper, Correlator.get(), ProfiledBinary,
- Contexts[0].get(), BIDFetcher.get(), &BIDFetcherCorrelateKind);
+ for (int I = 0; I < int(Inputs.size()); ++I)
+ loadInput(Inputs[I], Remapper, Correlator.get(), ProfiledBinary,
+ Contexts[0].get(), BIDFetcher.get(), &BIDFetcherCorrelateKind,
+ !ObjectAwareHashing.empty() ? ObjectAwareHashing[I] : "");
} else {
DefaultThreadPool Pool(hardware_concurrency(NumThreads));
// Load the inputs in parallel (N/NumThreads serial steps).
unsigned Ctx = 0;
- for (const auto &Input : Inputs) {
- Pool.async(loadInput, Input, Remapper, Correlator.get(), ProfiledBinary,
- Contexts[Ctx].get(), BIDFetcher.get(),
- &BIDFetcherCorrelateKind);
+ for (int I = 0; I < int(Inputs.size()); ++I) {
+ Pool.async(loadInput, Inputs[I], Remapper, Correlator.get(),
+ ProfiledBinary, Contexts[Ctx].get(), BIDFetcher.get(),
+ &BIDFetcherCorrelateKind,
+ !ObjectAwareHashing.empty() ? ObjectAwareHashing[I] : "");
Ctx = (Ctx + 1) % NumThreads;
}
Pool.wait();
More information about the llvm-commits
mailing list