Skip to content

Commit 2ee0a53

Browse files
committed
[llvm-cov] Add support for baseline coverage
When no profile is provided via --instr-profile, 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 f9dca5b commit 2ee0a53

File tree

6 files changed

+175
-81
lines changed

6 files changed

+175
-81
lines changed

llvm/docs/CommandGuide/llvm-cov.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ SHOW COMMAND
188188
SYNOPSIS
189189
^^^^^^^^
190190

191-
:program:`llvm-cov show` [*options*] -instr-profile *PROFILE* [*BIN*] [*-object BIN*]... [*-sources*] [*SOURCE*]...
191+
:program:`llvm-cov show` [*options*] [-instr-profile *PROFILE*] [*BIN*] [*-object BIN*]... [*-sources*] [*SOURCE*]...
192192

193193
DESCRIPTION
194194
^^^^^^^^^^^
@@ -200,6 +200,9 @@ filtered to only show the coverage for the files listed in *SOURCE*....
200200
*BIN* may be an executable, object file, dynamic library, or archive (thin or
201201
otherwise).
202202

203+
If *PROFILE* is not provided, the command displays the baseline coverage of the
204+
binaries with all zero execution counts.
205+
203206
To use :program:`llvm-cov show`, you need a program that is compiled with
204207
instrumentation to emit profile and coverage data. To build such a program with
205208
``clang`` use the ``-fprofile-instr-generate`` and ``-fcoverage-mapping``
@@ -390,7 +393,7 @@ REPORT COMMAND
390393
SYNOPSIS
391394
^^^^^^^^
392395

393-
:program:`llvm-cov report` [*options*] -instr-profile *PROFILE* [*BIN*] [*-object BIN*]... [*-sources*] [*SOURCE*]...
396+
:program:`llvm-cov report` [*options*] [-instr-profile *PROFILE*] [*BIN*] [*-object BIN*]... [*-sources*] [*SOURCE*]...
394397

395398
DESCRIPTION
396399
^^^^^^^^^^^
@@ -402,6 +405,9 @@ filtered to only show the coverage for the files listed in *SOURCE*....
402405
*BIN* may be an executable, object file, dynamic library, or archive (thin or
403406
otherwise).
404407

408+
If *PROFILE* is not provided, the command displays the baseline coverage of the
409+
binaries with all zero execution counts.
410+
405411
If no source files are provided, a summary line is printed for each file in the
406412
coverage data. If any files are provided, summaries can be shown for each
407413
function in the listed files if the ``-show-functions`` option is enabled.
@@ -480,7 +486,7 @@ EXPORT COMMAND
480486
SYNOPSIS
481487
^^^^^^^^
482488

483-
:program:`llvm-cov export` [*options*] -instr-profile *PROFILE* [*BIN*] [*-object BIN*]... [*-sources*] [*SOURCE*]...
489+
:program:`llvm-cov export` [*options*] [-instr-profile *PROFILE*] [*BIN*] [*-object BIN*]... [*-sources*] [*SOURCE*]...
484490

485491
DESCRIPTION
486492
^^^^^^^^^^^
@@ -489,6 +495,9 @@ The :program:`llvm-cov export` command exports coverage data of the binaries
489495
*BIN*... using the profile data *PROFILE* in either JSON or lcov trace file
490496
format.
491497

498+
If *PROFILE* is not provided, the command exports baseline coverage data
499+
with all zero execution counts.
500+
492501
When exporting JSON, the regions, functions, branches, expansions, and
493502
summaries of the coverage data will be exported. When exporting an lcov trace
494503
file, the line-based coverage, branch coverage, and summaries will be exported.

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

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -937,18 +937,23 @@ class CoverageMapping {
937937
// Load coverage records from readers.
938938
static Error loadFromReaders(
939939
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
940-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage);
940+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
941+
&ProfileReader,
942+
CoverageMapping &Coverage);
941943

942944
// Load coverage records from file.
943945
static Error
944946
loadFromFile(StringRef Filename, StringRef Arch, StringRef CompilationDir,
945-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
946-
bool &DataFound,
947+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
948+
&ProfileReader,
949+
CoverageMapping &Coverage, bool &DataFound,
947950
SmallVectorImpl<object::BuildID> *FoundBinaryIDs = nullptr);
948951

949952
/// Add a function record corresponding to \p Record.
950-
Error loadFunctionRecord(const CoverageMappingRecord &Record,
951-
IndexedInstrProfReader &ProfileReader);
953+
Error loadFunctionRecord(
954+
const CoverageMappingRecord &Record,
955+
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
956+
&ProfileReader);
952957

953958
/// Look up the indices for function records which are at least partially
954959
/// defined in the specified file. This is guaranteed to return a superset of
@@ -964,15 +969,16 @@ class CoverageMapping {
964969
/// Load the coverage mapping using the given readers.
965970
static Expected<std::unique_ptr<CoverageMapping>>
966971
load(ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
967-
IndexedInstrProfReader &ProfileReader);
972+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
973+
&ProfileReader);
968974

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

llvm/lib/ProfileData/Coverage/CoverageMapping.cpp

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,8 @@ class MCDCDecisionRecorder {
794794

795795
Error CoverageMapping::loadFunctionRecord(
796796
const CoverageMappingRecord &Record,
797-
IndexedInstrProfReader &ProfileReader) {
797+
const std::optional<std::reference_wrapper<IndexedInstrProfReader>>
798+
&ProfileReader) {
798799
StringRef OrigFuncName = Record.FunctionName;
799800
if (OrigFuncName.empty())
800801
return make_error<CoverageMapError>(coveragemap_error::malformed,
@@ -808,35 +809,44 @@ Error CoverageMapping::loadFunctionRecord(
808809
CounterMappingContext Ctx(Record.Expressions);
809810

810811
std::vector<uint64_t> Counts;
811-
if (Error E = ProfileReader.getFunctionCounts(Record.FunctionName,
812-
Record.FunctionHash, Counts)) {
813-
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
814-
if (IPE == instrprof_error::hash_mismatch) {
815-
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
816-
Record.FunctionHash);
817-
return Error::success();
812+
if (ProfileReader) {
813+
if (Error E = ProfileReader.value().get().getFunctionCounts(
814+
Record.FunctionName, Record.FunctionHash, Counts)) {
815+
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
816+
if (IPE == instrprof_error::hash_mismatch) {
817+
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
818+
Record.FunctionHash);
819+
return Error::success();
820+
}
821+
if (IPE != instrprof_error::unknown_function)
822+
return make_error<InstrProfError>(IPE);
823+
Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0);
818824
}
819-
if (IPE != instrprof_error::unknown_function)
820-
return make_error<InstrProfError>(IPE);
825+
} else {
821826
Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0);
822827
}
823828
Ctx.setCounts(Counts);
824829

825830
bool IsVersion11 =
826-
ProfileReader.getVersion() < IndexedInstrProf::ProfVersion::Version12;
831+
ProfileReader && ProfileReader.value().get().getVersion() <
832+
IndexedInstrProf::ProfVersion::Version12;
827833

828834
BitVector Bitmap;
829-
if (Error E = ProfileReader.getFunctionBitmap(Record.FunctionName,
830-
Record.FunctionHash, Bitmap)) {
831-
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
832-
if (IPE == instrprof_error::hash_mismatch) {
833-
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
834-
Record.FunctionHash);
835-
return Error::success();
835+
if (ProfileReader) {
836+
if (Error E = ProfileReader.value().get().getFunctionBitmap(
837+
Record.FunctionName, Record.FunctionHash, Bitmap)) {
838+
instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E)));
839+
if (IPE == instrprof_error::hash_mismatch) {
840+
FuncHashMismatches.emplace_back(std::string(Record.FunctionName),
841+
Record.FunctionHash);
842+
return Error::success();
843+
}
844+
if (IPE != instrprof_error::unknown_function)
845+
return make_error<InstrProfError>(IPE);
846+
Bitmap = BitVector(getMaxBitmapSize(Record, IsVersion11));
836847
}
837-
if (IPE != instrprof_error::unknown_function)
838-
return make_error<InstrProfError>(IPE);
839-
Bitmap = BitVector(getMaxBitmapSize(Record, IsVersion11));
848+
} else {
849+
Bitmap = BitVector(getMaxBitmapSize(Record, false));
840850
}
841851
Ctx.setBitmap(std::move(Bitmap));
842852

@@ -870,8 +880,9 @@ Error CoverageMapping::loadFunctionRecord(
870880
consumeError(std::move(E));
871881
return Error::success();
872882
}
873-
Function.pushRegion(Region, *ExecutionCount, *AltExecutionCount,
874-
ProfileReader.hasSingleByteCoverage());
883+
Function.pushRegion(
884+
Region, *ExecutionCount, *AltExecutionCount,
885+
ProfileReader && ProfileReader.value().get().hasSingleByteCoverage());
875886

876887
// Record ExpansionRegion.
877888
if (Region.Kind == CounterMappingRegion::ExpansionRegion) {
@@ -932,7 +943,9 @@ Error CoverageMapping::loadFunctionRecord(
932943
// of CoverageMappingReader instances.
933944
Error CoverageMapping::loadFromReaders(
934945
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
935-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage) {
946+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
947+
&ProfileReader,
948+
CoverageMapping &Coverage) {
936949
for (const auto &CoverageReader : CoverageReaders) {
937950
for (auto RecordOrErr : *CoverageReader) {
938951
if (Error E = RecordOrErr.takeError())
@@ -947,7 +960,8 @@ Error CoverageMapping::loadFromReaders(
947960

948961
Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
949962
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
950-
IndexedInstrProfReader &ProfileReader) {
963+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
964+
&ProfileReader) {
951965
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
952966
if (Error E = loadFromReaders(CoverageReaders, ProfileReader, *Coverage))
953967
return std::move(E);
@@ -956,18 +970,19 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
956970

957971
// If E is a no_data_found error, returns success. Otherwise returns E.
958972
static Error handleMaybeNoDataFoundError(Error E) {
959-
return handleErrors(
960-
std::move(E), [](const CoverageMapError &CME) {
961-
if (CME.get() == coveragemap_error::no_data_found)
962-
return static_cast<Error>(Error::success());
963-
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
964-
});
973+
return handleErrors(std::move(E), [](const CoverageMapError &CME) {
974+
if (CME.get() == coveragemap_error::no_data_found)
975+
return static_cast<Error>(Error::success());
976+
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
977+
});
965978
}
966979

967980
Error CoverageMapping::loadFromFile(
968981
StringRef Filename, StringRef Arch, StringRef CompilationDir,
969-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
970-
bool &DataFound, SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
982+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
983+
&ProfileReader,
984+
CoverageMapping &Coverage, bool &DataFound,
985+
SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
971986
auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN(
972987
Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false);
973988
if (std::error_code EC = CovMappingBufOrErr.getError())
@@ -1003,13 +1018,23 @@ Error CoverageMapping::loadFromFile(
10031018
}
10041019

10051020
Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
1006-
ArrayRef<StringRef> ObjectFilenames, StringRef ProfileFilename,
1007-
vfs::FileSystem &FS, ArrayRef<StringRef> Arches, StringRef CompilationDir,
1021+
ArrayRef<StringRef> ObjectFilenames,
1022+
std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
1023+
ArrayRef<StringRef> Arches, StringRef CompilationDir,
10081024
const object::BuildIDFetcher *BIDFetcher, bool CheckBinaryIDs) {
1009-
auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename, FS);
1010-
if (Error E = ProfileReaderOrErr.takeError())
1011-
return createFileError(ProfileFilename, std::move(E));
1012-
auto ProfileReader = std::move(ProfileReaderOrErr.get());
1025+
std::unique_ptr<IndexedInstrProfReader> ProfileReader;
1026+
if (ProfileFilename) {
1027+
auto ProfileReaderOrErr =
1028+
IndexedInstrProfReader::create(ProfileFilename.value(), FS);
1029+
if (Error E = ProfileReaderOrErr.takeError())
1030+
return createFileError(ProfileFilename.value(), std::move(E));
1031+
ProfileReader = std::move(ProfileReaderOrErr.get());
1032+
}
1033+
auto ProfileReaderRef =
1034+
ProfileReader
1035+
? std::optional<std::reference_wrapper<IndexedInstrProfReader>>(
1036+
*ProfileReader)
1037+
: std::nullopt;
10131038
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
10141039
bool DataFound = false;
10151040

@@ -1023,16 +1048,17 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10231048

10241049
SmallVector<object::BuildID> FoundBinaryIDs;
10251050
for (const auto &File : llvm::enumerate(ObjectFilenames)) {
1026-
if (Error E =
1027-
loadFromFile(File.value(), GetArch(File.index()), CompilationDir,
1028-
*ProfileReader, *Coverage, DataFound, &FoundBinaryIDs))
1051+
if (Error E = loadFromFile(File.value(), GetArch(File.index()),
1052+
CompilationDir, ProfileReaderRef, *Coverage,
1053+
DataFound, &FoundBinaryIDs))
10291054
return std::move(E);
10301055
}
10311056

10321057
if (BIDFetcher) {
10331058
std::vector<object::BuildID> ProfileBinaryIDs;
1034-
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
1035-
return createFileError(ProfileFilename, std::move(E));
1059+
if (ProfileReader)
1060+
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
1061+
return createFileError(ProfileFilename.value(), std::move(E));
10361062

10371063
SmallVector<object::BuildIDRef> BinaryIDsToFetch;
10381064
if (!ProfileBinaryIDs.empty()) {
@@ -1052,12 +1078,12 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10521078
if (PathOpt) {
10531079
std::string Path = std::move(*PathOpt);
10541080
StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef();
1055-
if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader,
1056-
*Coverage, DataFound))
1081+
if (Error E = loadFromFile(Path, Arch, CompilationDir, ProfileReaderRef,
1082+
*Coverage, DataFound))
10571083
return std::move(E);
10581084
} else if (CheckBinaryIDs) {
10591085
return createFileError(
1060-
ProfileFilename,
1086+
ProfileFilename.value(),
10611087
createStringError(errc::no_such_file_or_directory,
10621088
"Missing binary ID: " +
10631089
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 %s | FileCheck -check-prefixes=FULL %s
30+
31+
// RUN: llvm-cov export -format=lcov -summary-only %S/Inputs/lineExecutionCounts.covmapping %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)