Skip to content

Commit dc9e300

Browse files
authored
[llvm-cov] Add support for baseline coverage (#117910)
When no profile is provided, but the new --empty-profile option is specifed, the export/report/show commands now emit coverage data equivalent to that obtained from a profile with all zero counters ("baseline coverage"). This is useful for build systems (e.g. Bazel) that can track coverage information for each build target, even those that are never linked into tests and thus don't have runtime coverage data recorded. By merging in baseline coverage, lines in files that aren't linked into tests are correctly reported as uncovered.
1 parent ca50409 commit dc9e300

File tree

6 files changed

+195
-86
lines changed

6 files changed

+195
-86
lines changed

llvm/docs/CommandGuide/llvm-cov.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,11 @@ OPTIONS
380380
Fail if an object file cannot be found for a binary ID present in the profile,
381381
neither on the command line nor via binary ID lookup.
382382

383+
.. option:: -empty-profile
384+
385+
Display the baseline coverage of the binaries with all zero execution counts.
386+
Mutually exclusive with -instr-profile.
387+
383388
.. program:: llvm-cov report
384389

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

478+
.. option:: -empty-profile
479+
480+
Display the baseline coverage of the binaries with all zero execution counts.
481+
Mutually exclusive with -instr-profile.
482+
473483
.. program:: llvm-cov export
474484

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

575+
.. option:: -empty-profile
576+
577+
Export the baseline coverage of the binaries with all zero execution counts.
578+
Mutually exclusive with -instr-profile.
579+
565580
CONVERT-FOR-TESTING COMMAND
566581
---------------------------
567582

llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -991,18 +991,23 @@ class CoverageMapping {
991991
// Load coverage records from readers.
992992
static Error loadFromReaders(
993993
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
994-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage);
994+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
995+
&ProfileReader,
996+
CoverageMapping &Coverage);
995997

996998
// Load coverage records from file.
997999
static Error
9981000
loadFromFile(StringRef Filename, StringRef Arch, StringRef CompilationDir,
999-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
1000-
bool &DataFound,
1001+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
1002+
&ProfileReader,
1003+
CoverageMapping &Coverage, bool &DataFound,
10011004
SmallVectorImpl<object::BuildID> *FoundBinaryIDs = nullptr);
10021005

10031006
/// Add a function record corresponding to \p Record.
1004-
Error loadFunctionRecord(const CoverageMappingRecord &Record,
1005-
IndexedInstrProfReader &ProfileReader);
1007+
Error loadFunctionRecord(
1008+
const CoverageMappingRecord &Record,
1009+
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
1010+
&ProfileReader);
10061011

10071012
/// Look up the indices for function records which are at least partially
10081013
/// defined in the specified file. This is guaranteed to return a superset of
@@ -1018,15 +1023,16 @@ class CoverageMapping {
10181023
/// Load the coverage mapping using the given readers.
10191024
LLVM_ABI static Expected<std::unique_ptr<CoverageMapping>>
10201025
load(ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
1021-
IndexedInstrProfReader &ProfileReader);
1026+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
1027+
&ProfileReader);
10221028

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

llvm/lib/ProfileData/Coverage/CoverageMapping.cpp

Lines changed: 75 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,8 @@ class MCDCDecisionRecorder {
823823

824824
Error CoverageMapping::loadFunctionRecord(
825825
const CoverageMappingRecord &Record,
826-
IndexedInstrProfReader &ProfileReader) {
826+
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
827+
&ProfileReader) {
827828
StringRef OrigFuncName = Record.FunctionName;
828829
if (OrigFuncName.empty())
829830
return make_error<CoverageMapError>(coveragemap_error::malformed,
@@ -837,35 +838,44 @@ Error CoverageMapping::loadFunctionRecord(
837838
CounterMappingContext Ctx(Record.Expressions);
838839

839840
std::vector<uint64_t> Counts;
840-
if (Error E = ProfileReader.getFunctionCounts(Record.FunctionName,
841-
Record.FunctionHash, Counts)) {
842-
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
843-
if (IPE == instrprof_error::hash_mismatch) {
844-
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
845-
Record.FunctionHash);
846-
return Error::success();
841+
if (ProfileReader) {
842+
if (Error E = ProfileReader.value().get().getFunctionCounts(
843+
Record.FunctionName, Record.FunctionHash, Counts)) {
844+
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
845+
if (IPE == instrprof_error::hash_mismatch) {
846+
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
847+
Record.FunctionHash);
848+
return Error::success();
849+
}
850+
if (IPE != instrprof_error::unknown_function)
851+
return make_error<InstrProfError>(IPE);
852+
Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0);
847853
}
848-
if (IPE != instrprof_error::unknown_function)
849-
return make_error<InstrProfError>(IPE);
854+
} else {
850855
Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0);
851856
}
852857
Ctx.setCounts(Counts);
853858

854859
bool IsVersion11 =
855-
ProfileReader.getVersion() < IndexedInstrProf::ProfVersion::Version12;
860+
ProfileReader && ProfileReader.value().get().getVersion() <
861+
IndexedInstrProf::ProfVersion::Version12;
856862

857863
BitVector Bitmap;
858-
if (Error E = ProfileReader.getFunctionBitmap(Record.FunctionName,
859-
Record.FunctionHash, Bitmap)) {
860-
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
861-
if (IPE == instrprof_error::hash_mismatch) {
862-
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
863-
Record.FunctionHash);
864-
return Error::success();
864+
if (ProfileReader) {
865+
if (Error E = ProfileReader.value().get().getFunctionBitmap(
866+
Record.FunctionName, Record.FunctionHash, Bitmap)) {
867+
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
868+
if (IPE == instrprof_error::hash_mismatch) {
869+
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
870+
Record.FunctionHash);
871+
return Error::success();
872+
}
873+
if (IPE != instrprof_error::unknown_function)
874+
return make_error<InstrProfError>(IPE);
875+
Bitmap = BitVector(getMaxBitmapSize(Record, IsVersion11));
865876
}
866-
if (IPE != instrprof_error::unknown_function)
867-
return make_error<InstrProfError>(IPE);
868-
Bitmap = BitVector(getMaxBitmapSize(Record, IsVersion11));
877+
} else {
878+
Bitmap = BitVector(getMaxBitmapSize(Record, false));
869879
}
870880
Ctx.setBitmap(std::move(Bitmap));
871881

@@ -959,10 +969,14 @@ Error CoverageMapping::loadFunctionRecord(
959969
// of CoverageMappingReader instances.
960970
Error CoverageMapping::loadFromReaders(
961971
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
962-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage) {
963-
assert(!Coverage.SingleByteCoverage ||
964-
*Coverage.SingleByteCoverage == ProfileReader.hasSingleByteCoverage());
965-
Coverage.SingleByteCoverage = ProfileReader.hasSingleByteCoverage();
972+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
973+
&ProfileReader,
974+
CoverageMapping &Coverage) {
975+
assert(!Coverage.SingleByteCoverage || !ProfileReader ||
976+
*Coverage.SingleByteCoverage ==
977+
ProfileReader.value().get().hasSingleByteCoverage());
978+
Coverage.SingleByteCoverage =
979+
!ProfileReader || ProfileReader.value().get().hasSingleByteCoverage();
966980
for (const auto &CoverageReader : CoverageReaders) {
967981
for (auto RecordOrErr : *CoverageReader) {
968982
if (Error E = RecordOrErr.takeError())
@@ -977,7 +991,8 @@ Error CoverageMapping::loadFromReaders(
977991

978992
Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
979993
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
980-
IndexedInstrProfReader &ProfileReader) {
994+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
995+
&ProfileReader) {
981996
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
982997
if (Error E = loadFromReaders(CoverageReaders, ProfileReader, *Coverage))
983998
return std::move(E);
@@ -986,18 +1001,19 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
9861001

9871002
// If E is a no_data_found error, returns success. Otherwise returns E.
9881003
static Error handleMaybeNoDataFoundError(Error E) {
989-
return handleErrors(
990-
std::move(E), [](const CoverageMapError &CME) {
991-
if (CME.get() == coveragemap_error::no_data_found)
992-
return static_cast<Error>(Error::success());
993-
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
994-
});
1004+
return handleErrors(std::move(E), [](const CoverageMapError &CME) {
1005+
if (CME.get() == coveragemap_error::no_data_found)
1006+
return static_cast<Error>(Error::success());
1007+
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
1008+
});
9951009
}
9961010

9971011
Error CoverageMapping::loadFromFile(
9981012
StringRef Filename, StringRef Arch, StringRef CompilationDir,
999-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
1000-
bool &DataFound, SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
1013+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
1014+
&ProfileReader,
1015+
CoverageMapping &Coverage, bool &DataFound,
1016+
SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
10011017
auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN(
10021018
Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false);
10031019
if (std::error_code EC = CovMappingBufOrErr.getError())
@@ -1033,13 +1049,23 @@ Error CoverageMapping::loadFromFile(
10331049
}
10341050

10351051
Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
1036-
ArrayRef<StringRef> ObjectFilenames, StringRef ProfileFilename,
1037-
vfs::FileSystem &FS, ArrayRef<StringRef> Arches, StringRef CompilationDir,
1052+
ArrayRef<StringRef> ObjectFilenames,
1053+
std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
1054+
ArrayRef<StringRef> Arches, StringRef CompilationDir,
10381055
const object::BuildIDFetcher *BIDFetcher, bool CheckBinaryIDs) {
1039-
auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename, FS);
1040-
if (Error E = ProfileReaderOrErr.takeError())
1041-
return createFileError(ProfileFilename, std::move(E));
1042-
auto ProfileReader = std::move(ProfileReaderOrErr.get());
1056+
std::unique_ptr<IndexedInstrProfReader> ProfileReader;
1057+
if (ProfileFilename) {
1058+
auto ProfileReaderOrErr =
1059+
IndexedInstrProfReader::create(ProfileFilename.value(), FS);
1060+
if (Error E = ProfileReaderOrErr.takeError())
1061+
return createFileError(ProfileFilename.value(), std::move(E));
1062+
ProfileReader = std::move(ProfileReaderOrErr.get());
1063+
}
1064+
auto ProfileReaderRef =
1065+
ProfileReader
1066+
? std::optional<std::reference_wrapper<IndexedInstrProfReader>>(
1067+
*ProfileReader)
1068+
: std::nullopt;
10431069
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
10441070
bool DataFound = false;
10451071

@@ -1053,16 +1079,17 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10531079

10541080
SmallVector<object::BuildID> FoundBinaryIDs;
10551081
for (const auto &File : llvm::enumerate(ObjectFilenames)) {
1056-
if (Error E =
1057-
loadFromFile(File.value(), GetArch(File.index()), CompilationDir,
1058-
*ProfileReader, *Coverage, DataFound, &FoundBinaryIDs))
1082+
if (Error E = loadFromFile(File.value(), GetArch(File.index()),
1083+
CompilationDir, ProfileReaderRef, *Coverage,
1084+
DataFound, &FoundBinaryIDs))
10591085
return std::move(E);
10601086
}
10611087

10621088
if (BIDFetcher) {
10631089
std::vector<object::BuildID> ProfileBinaryIDs;
1064-
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
1065-
return createFileError(ProfileFilename, std::move(E));
1090+
if (ProfileReader)
1091+
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
1092+
return createFileError(ProfileFilename.value(), std::move(E));
10661093

10671094
SmallVector<object::BuildIDRef> BinaryIDsToFetch;
10681095
if (!ProfileBinaryIDs.empty()) {
@@ -1082,12 +1109,12 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10821109
if (PathOpt) {
10831110
std::string Path = std::move(*PathOpt);
10841111
StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef();
1085-
if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader,
1086-
*Coverage, DataFound))
1112+
if (Error E = loadFromFile(Path, Arch, CompilationDir, ProfileReaderRef,
1113+
*Coverage, DataFound))
10871114
return std::move(E);
10881115
} else if (CheckBinaryIDs) {
10891116
return createFileError(
1090-
ProfileFilename,
1117+
ProfileFilename.value(),
10911118
createStringError(errc::no_such_file_or_directory,
10921119
"Missing binary ID: " +
10931120
llvm::toHex(BinaryID, /*LowerCase=*/true)));
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// FULL: SF:{{.*}}showLineExecutionCounts.cpp
2+
// FULL: FN:6,main
3+
// FULL: FNDA:0,main
4+
// FULL: FNF:1
5+
// FULL: FNH:0
6+
int main() { // FULL: DA:[[@LINE]],0
7+
int x = 0; // FULL: DA:[[@LINE]],0
8+
// FULL: DA:[[@LINE]],0
9+
if (x) { // FULL: DA:[[@LINE]],0
10+
x = 0; // FULL: DA:[[@LINE]],0
11+
} else { // FULL: DA:[[@LINE]],0
12+
x = 1; // FULL: DA:[[@LINE]],0
13+
} // FULL: DA:[[@LINE]],0
14+
// FULL: DA:[[@LINE]],0
15+
for (int i = 0; i < 100; ++i) { // FULL: DA:[[@LINE]],0
16+
x = 1; // FULL: DA:[[@LINE]],0
17+
} // FULL: DA:[[@LINE]],0
18+
// FULL: DA:[[@LINE]],0
19+
x = x < 10 ? x + 1 : x - 1; // FULL: DA:[[@LINE]],0
20+
x = x > 10 ? // FULL: DA:[[@LINE]],0
21+
x - 1: // FULL: DA:[[@LINE]],0
22+
x + 1; // FULL: DA:[[@LINE]],0
23+
// FULL: DA:[[@LINE]],0
24+
return 0; // FULL: DA:[[@LINE]],0
25+
} // FULL: DA:[[@LINE]],0
26+
// FULL: LF:20
27+
// FULL: LH:0
28+
// FULL: end_of_record
29+
// RUN: llvm-cov export -format=lcov %S/Inputs/lineExecutionCounts.covmapping -empty-profile %s | FileCheck -check-prefixes=FULL %s
30+
31+
// RUN: llvm-cov export -format=lcov -summary-only %S/Inputs/lineExecutionCounts.covmapping -empty-profile %s | FileCheck -check-prefixes=SUMMARYONLY %s
32+
// SUMMARYONLY: SF:{{.*}}showLineExecutionCounts.cpp
33+
// SUMMARYONLY: FNF:1
34+
// SUMMARYONLY: FNH:0
35+
// SUMMARYONLY: LF:20
36+
// SUMMARYONLY: LH:0
37+
// SUMMARYONLY: end_of_record

0 commit comments

Comments
 (0)