Skip to content

Reland "[llvm-cov] Add support for baseline coverage" #144130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions llvm/docs/CommandGuide/llvm-cov.rst
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,11 @@ OPTIONS
Fail if an object file cannot be found for a binary ID present in the profile,
neither on the command line nor via binary ID lookup.

.. option:: -empty-profile

Display the baseline coverage of the binaries with all zero execution counts.
Mutually exclusive with -instr-profile.

.. program:: llvm-cov report

.. _llvm-cov-report:
Expand Down Expand Up @@ -470,6 +475,11 @@ OPTIONS
Fail if an object file cannot be found for a binary ID present in the profile,
neither on the command line nor via binary ID lookup.

.. option:: -empty-profile

Display the baseline coverage of the binaries with all zero execution counts.
Mutually exclusive with -instr-profile.

.. program:: llvm-cov export

.. _llvm-cov-export:
Expand Down Expand Up @@ -562,6 +572,11 @@ OPTIONS
Fail if an object file cannot be found for a binary ID present in the profile,
neither on the command line nor via binary ID lookup.

.. option:: -empty-profile

Export the baseline coverage of the binaries with all zero execution counts.
Mutually exclusive with -instr-profile.

CONVERT-FOR-TESTING COMMAND
---------------------------

Expand Down
24 changes: 15 additions & 9 deletions llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -991,18 +991,23 @@ class CoverageMapping {
// Load coverage records from readers.
static Error loadFromReaders(
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage);
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
CoverageMapping &Coverage);

// Load coverage records from file.
static Error
loadFromFile(StringRef Filename, StringRef Arch, StringRef CompilationDir,
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
bool &DataFound,
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
CoverageMapping &Coverage, bool &DataFound,
SmallVectorImpl<object::BuildID> *FoundBinaryIDs = nullptr);

/// Add a function record corresponding to \p Record.
Error loadFunctionRecord(const CoverageMappingRecord &Record,
IndexedInstrProfReader &ProfileReader);
Error loadFunctionRecord(
const CoverageMappingRecord &Record,
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader);

/// 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
Expand All @@ -1018,15 +1023,16 @@ class CoverageMapping {
/// Load the coverage mapping using the given readers.
LLVM_ABI static Expected<std::unique_ptr<CoverageMapping>>
load(ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
IndexedInstrProfReader &ProfileReader);
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader);

/// Load the coverage mapping from the given object files and profile. If
/// \p Arches is non-empty, it must specify an architecture for each object.
/// Ignores non-instrumented object files unless all are not instrumented.
LLVM_ABI static Expected<std::unique_ptr<CoverageMapping>>
load(ArrayRef<StringRef> ObjectFilenames, StringRef ProfileFilename,
vfs::FileSystem &FS, ArrayRef<StringRef> Arches = {},
StringRef CompilationDir = "",
load(ArrayRef<StringRef> ObjectFilenames,
std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
ArrayRef<StringRef> Arches = {}, StringRef CompilationDir = "",
const object::BuildIDFetcher *BIDFetcher = nullptr,
bool CheckBinaryIDs = false);

Expand Down
123 changes: 75 additions & 48 deletions llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,8 @@ class MCDCDecisionRecorder {

Error CoverageMapping::loadFunctionRecord(
const CoverageMappingRecord &Record,
IndexedInstrProfReader &ProfileReader) {
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader) {
StringRef OrigFuncName = Record.FunctionName;
if (OrigFuncName.empty())
return make_error<CoverageMapError>(coveragemap_error::malformed,
Expand All @@ -837,35 +838,44 @@ Error CoverageMapping::loadFunctionRecord(
CounterMappingContext Ctx(Record.Expressions);

std::vector<uint64_t> Counts;
if (Error E = ProfileReader.getFunctionCounts(Record.FunctionName,
Record.FunctionHash, 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);
return Error::success();
if (ProfileReader) {
if (Error E = ProfileReader.value().get().getFunctionCounts(
Record.FunctionName, Record.FunctionHash, 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);
return Error::success();
}
if (IPE != instrprof_error::unknown_function)
return make_error<InstrProfError>(IPE);
Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0);
}
if (IPE != instrprof_error::unknown_function)
return make_error<InstrProfError>(IPE);
} else {
Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0);
}
Ctx.setCounts(Counts);

bool IsVersion11 =
ProfileReader.getVersion() < IndexedInstrProf::ProfVersion::Version12;
ProfileReader && ProfileReader.value().get().getVersion() <
IndexedInstrProf::ProfVersion::Version12;

BitVector Bitmap;
if (Error E = ProfileReader.getFunctionBitmap(Record.FunctionName,
Record.FunctionHash, 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);
return Error::success();
if (ProfileReader) {
if (Error E = ProfileReader.value().get().getFunctionBitmap(
Record.FunctionName, Record.FunctionHash, 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);
return Error::success();
}
if (IPE != instrprof_error::unknown_function)
return make_error<InstrProfError>(IPE);
Bitmap = BitVector(getMaxBitmapSize(Record, IsVersion11));
}
if (IPE != instrprof_error::unknown_function)
return make_error<InstrProfError>(IPE);
Bitmap = BitVector(getMaxBitmapSize(Record, IsVersion11));
} else {
Bitmap = BitVector(getMaxBitmapSize(Record, false));
}
Ctx.setBitmap(std::move(Bitmap));

Expand Down Expand Up @@ -959,10 +969,14 @@ Error CoverageMapping::loadFunctionRecord(
// of CoverageMappingReader instances.
Error CoverageMapping::loadFromReaders(
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage) {
assert(!Coverage.SingleByteCoverage ||
*Coverage.SingleByteCoverage == ProfileReader.hasSingleByteCoverage());
Coverage.SingleByteCoverage = ProfileReader.hasSingleByteCoverage();
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
CoverageMapping &Coverage) {
assert(!Coverage.SingleByteCoverage || !ProfileReader ||
*Coverage.SingleByteCoverage ==
ProfileReader.value().get().hasSingleByteCoverage());
Coverage.SingleByteCoverage =
!ProfileReader || ProfileReader.value().get().hasSingleByteCoverage();
for (const auto &CoverageReader : CoverageReaders) {
for (auto RecordOrErr : *CoverageReader) {
if (Error E = RecordOrErr.takeError())
Expand All @@ -977,7 +991,8 @@ Error CoverageMapping::loadFromReaders(

Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
IndexedInstrProfReader &ProfileReader) {
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader) {
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
if (Error E = loadFromReaders(CoverageReaders, ProfileReader, *Coverage))
return std::move(E);
Expand All @@ -986,18 +1001,19 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(

// If E is a no_data_found error, returns success. Otherwise returns E.
static Error handleMaybeNoDataFoundError(Error E) {
return handleErrors(
std::move(E), [](const CoverageMapError &CME) {
if (CME.get() == coveragemap_error::no_data_found)
return static_cast<Error>(Error::success());
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
});
return handleErrors(std::move(E), [](const CoverageMapError &CME) {
if (CME.get() == coveragemap_error::no_data_found)
return static_cast<Error>(Error::success());
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
});
}

Error CoverageMapping::loadFromFile(
StringRef Filename, StringRef Arch, StringRef CompilationDir,
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
bool &DataFound, SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
&ProfileReader,
CoverageMapping &Coverage, bool &DataFound,
SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN(
Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false);
if (std::error_code EC = CovMappingBufOrErr.getError())
Expand Down Expand Up @@ -1033,13 +1049,23 @@ Error CoverageMapping::loadFromFile(
}

Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
ArrayRef<StringRef> ObjectFilenames, StringRef ProfileFilename,
vfs::FileSystem &FS, ArrayRef<StringRef> Arches, StringRef CompilationDir,
ArrayRef<StringRef> ObjectFilenames,
std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
ArrayRef<StringRef> Arches, StringRef CompilationDir,
const object::BuildIDFetcher *BIDFetcher, bool CheckBinaryIDs) {
auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename, FS);
if (Error E = ProfileReaderOrErr.takeError())
return createFileError(ProfileFilename, std::move(E));
auto ProfileReader = std::move(ProfileReaderOrErr.get());
std::unique_ptr<IndexedInstrProfReader> ProfileReader;
if (ProfileFilename) {
auto ProfileReaderOrErr =
IndexedInstrProfReader::create(ProfileFilename.value(), FS);
if (Error E = ProfileReaderOrErr.takeError())
return createFileError(ProfileFilename.value(), std::move(E));
ProfileReader = std::move(ProfileReaderOrErr.get());
}
auto ProfileReaderRef =
ProfileReader
? std::optional<std::reference_wrapper<IndexedInstrProfReader>>(
*ProfileReader)
: std::nullopt;
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
bool DataFound = false;

Expand All @@ -1053,16 +1079,17 @@ 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,
*ProfileReader, *Coverage, DataFound, &FoundBinaryIDs))
if (Error E = loadFromFile(File.value(), GetArch(File.index()),
CompilationDir, ProfileReaderRef, *Coverage,
DataFound, &FoundBinaryIDs))
return std::move(E);
}

if (BIDFetcher) {
std::vector<object::BuildID> ProfileBinaryIDs;
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
return createFileError(ProfileFilename, std::move(E));
if (ProfileReader)
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
return createFileError(ProfileFilename.value(), std::move(E));

SmallVector<object::BuildIDRef> BinaryIDsToFetch;
if (!ProfileBinaryIDs.empty()) {
Expand All @@ -1082,12 +1109,12 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
if (PathOpt) {
std::string Path = std::move(*PathOpt);
StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef();
if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader,
*Coverage, DataFound))
if (Error E = loadFromFile(Path, Arch, CompilationDir, ProfileReaderRef,
*Coverage, DataFound))
return std::move(E);
} else if (CheckBinaryIDs) {
return createFileError(
ProfileFilename,
ProfileFilename.value(),
createStringError(errc::no_such_file_or_directory,
"Missing binary ID: " +
llvm::toHex(BinaryID, /*LowerCase=*/true)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// FULL: SF:{{.*}}showLineExecutionCounts.cpp
// FULL: FN:6,main
// FULL: FNDA:0,main
// FULL: FNF:1
// FULL: FNH:0
int main() { // FULL: DA:[[@LINE]],0
int x = 0; // FULL: DA:[[@LINE]],0
// FULL: DA:[[@LINE]],0
if (x) { // FULL: DA:[[@LINE]],0
x = 0; // FULL: DA:[[@LINE]],0
} else { // FULL: DA:[[@LINE]],0
x = 1; // FULL: DA:[[@LINE]],0
} // FULL: DA:[[@LINE]],0
// FULL: DA:[[@LINE]],0
for (int i = 0; i < 100; ++i) { // FULL: DA:[[@LINE]],0
x = 1; // FULL: DA:[[@LINE]],0
} // FULL: DA:[[@LINE]],0
// FULL: DA:[[@LINE]],0
x = x < 10 ? x + 1 : x - 1; // FULL: DA:[[@LINE]],0
x = x > 10 ? // FULL: DA:[[@LINE]],0
x - 1: // FULL: DA:[[@LINE]],0
x + 1; // FULL: DA:[[@LINE]],0
// FULL: DA:[[@LINE]],0
return 0; // FULL: DA:[[@LINE]],0
} // FULL: DA:[[@LINE]],0
// FULL: LF:20
// FULL: LH:0
// FULL: end_of_record
// RUN: llvm-cov export -format=lcov %S/Inputs/lineExecutionCounts.covmapping -empty-profile %s | FileCheck -check-prefixes=FULL %s

// RUN: llvm-cov export -format=lcov -summary-only %S/Inputs/lineExecutionCounts.covmapping -empty-profile %s | FileCheck -check-prefixes=SUMMARYONLY %s
// SUMMARYONLY: SF:{{.*}}showLineExecutionCounts.cpp
// SUMMARYONLY: FNF:1
// SUMMARYONLY: FNH:0
// SUMMARYONLY: LF:20
// SUMMARYONLY: LH:0
// SUMMARYONLY: end_of_record
Loading
Loading