Skip to content

Commit fe2841b

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 ccc18ca commit fe2841b

File tree

6 files changed

+169
-78
lines changed

6 files changed

+169
-78
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
@@ -990,18 +990,23 @@ class CoverageMapping {
990990
// Load coverage records from readers.
991991
static Error loadFromReaders(
992992
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
993-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage);
993+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
994+
&ProfileReader,
995+
CoverageMapping &Coverage);
994996

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

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

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

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

llvm/lib/ProfileData/Coverage/CoverageMapping.cpp

Lines changed: 76 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

@@ -960,10 +970,15 @@ Error CoverageMapping::loadFunctionRecord(
960970
// of CoverageMappingReader instances.
961971
Error CoverageMapping::loadFromReaders(
962972
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
963-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage) {
964-
assert(!Coverage.SingleByteCoverage ||
965-
*Coverage.SingleByteCoverage == ProfileReader.hasSingleByteCoverage());
966-
Coverage.SingleByteCoverage = ProfileReader.hasSingleByteCoverage();
973+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
974+
&ProfileReader,
975+
CoverageMapping &Coverage) {
976+
assert(!Coverage.SingleByteCoverage || !ProfileReader ||
977+
*Coverage.SingleByteCoverage ==
978+
ProfileReader.value().get().hasSingleByteCoverage());
979+
Coverage.SingleByteCoverage =
980+
ProfileReader ? ProfileReader.value().get().hasSingleByteCoverage()
981+
: true;
967982
for (const auto &CoverageReader : CoverageReaders) {
968983
for (auto RecordOrErr : *CoverageReader) {
969984
if (Error E = RecordOrErr.takeError())
@@ -978,7 +993,8 @@ Error CoverageMapping::loadFromReaders(
978993

979994
Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
980995
ArrayRef<std::unique_ptr<CoverageMappingReader>> CoverageReaders,
981-
IndexedInstrProfReader &ProfileReader) {
996+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
997+
&ProfileReader) {
982998
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
983999
if (Error E = loadFromReaders(CoverageReaders, ProfileReader, *Coverage))
9841000
return std::move(E);
@@ -987,18 +1003,19 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
9871003

9881004
// If E is a no_data_found error, returns success. Otherwise returns E.
9891005
static Error handleMaybeNoDataFoundError(Error E) {
990-
return handleErrors(
991-
std::move(E), [](const CoverageMapError &CME) {
992-
if (CME.get() == coveragemap_error::no_data_found)
993-
return static_cast<Error>(Error::success());
994-
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
995-
});
1006+
return handleErrors(std::move(E), [](const CoverageMapError &CME) {
1007+
if (CME.get() == coveragemap_error::no_data_found)
1008+
return static_cast<Error>(Error::success());
1009+
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
1010+
});
9961011
}
9971012

9981013
Error CoverageMapping::loadFromFile(
9991014
StringRef Filename, StringRef Arch, StringRef CompilationDir,
1000-
IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage,
1001-
bool &DataFound, SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
1015+
std::optional<std::reference_wrapper<IndexedInstrProfReader>>
1016+
&ProfileReader,
1017+
CoverageMapping &Coverage, bool &DataFound,
1018+
SmallVectorImpl<object::BuildID> *FoundBinaryIDs) {
10021019
auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN(
10031020
Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false);
10041021
if (std::error_code EC = CovMappingBufOrErr.getError())
@@ -1034,13 +1051,23 @@ Error CoverageMapping::loadFromFile(
10341051
}
10351052

10361053
Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
1037-
ArrayRef<StringRef> ObjectFilenames, StringRef ProfileFilename,
1038-
vfs::FileSystem &FS, ArrayRef<StringRef> Arches, StringRef CompilationDir,
1054+
ArrayRef<StringRef> ObjectFilenames,
1055+
std::optional<StringRef> ProfileFilename, vfs::FileSystem &FS,
1056+
ArrayRef<StringRef> Arches, StringRef CompilationDir,
10391057
const object::BuildIDFetcher *BIDFetcher, bool CheckBinaryIDs) {
1040-
auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename, FS);
1041-
if (Error E = ProfileReaderOrErr.takeError())
1042-
return createFileError(ProfileFilename, std::move(E));
1043-
auto ProfileReader = std::move(ProfileReaderOrErr.get());
1058+
std::unique_ptr<IndexedInstrProfReader> ProfileReader;
1059+
if (ProfileFilename) {
1060+
auto ProfileReaderOrErr =
1061+
IndexedInstrProfReader::create(ProfileFilename.value(), FS);
1062+
if (Error E = ProfileReaderOrErr.takeError())
1063+
return createFileError(ProfileFilename.value(), std::move(E));
1064+
ProfileReader = std::move(ProfileReaderOrErr.get());
1065+
}
1066+
auto ProfileReaderRef =
1067+
ProfileReader
1068+
? std::optional<std::reference_wrapper<IndexedInstrProfReader>>(
1069+
*ProfileReader)
1070+
: std::nullopt;
10441071
auto Coverage = std::unique_ptr<CoverageMapping>(new CoverageMapping());
10451072
bool DataFound = false;
10461073

@@ -1054,16 +1081,17 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10541081

10551082
SmallVector<object::BuildID> FoundBinaryIDs;
10561083
for (const auto &File : llvm::enumerate(ObjectFilenames)) {
1057-
if (Error E =
1058-
loadFromFile(File.value(), GetArch(File.index()), CompilationDir,
1059-
*ProfileReader, *Coverage, DataFound, &FoundBinaryIDs))
1084+
if (Error E = loadFromFile(File.value(), GetArch(File.index()),
1085+
CompilationDir, ProfileReaderRef, *Coverage,
1086+
DataFound, &FoundBinaryIDs))
10601087
return std::move(E);
10611088
}
10621089

10631090
if (BIDFetcher) {
10641091
std::vector<object::BuildID> ProfileBinaryIDs;
1065-
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
1066-
return createFileError(ProfileFilename, std::move(E));
1092+
if (ProfileReader)
1093+
if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs))
1094+
return createFileError(ProfileFilename.value(), std::move(E));
10671095

10681096
SmallVector<object::BuildIDRef> BinaryIDsToFetch;
10691097
if (!ProfileBinaryIDs.empty()) {
@@ -1083,12 +1111,12 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10831111
if (PathOpt) {
10841112
std::string Path = std::move(*PathOpt);
10851113
StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef();
1086-
if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader,
1087-
*Coverage, DataFound))
1114+
if (Error E = loadFromFile(Path, Arch, CompilationDir, ProfileReaderRef,
1115+
*Coverage, DataFound))
10881116
return std::move(E);
10891117
} else if (CheckBinaryIDs) {
10901118
return createFileError(
1091-
ProfileFilename,
1119+
ProfileFilename.value(),
10921120
createStringError(errc::no_such_file_or_directory,
10931121
"Missing binary ID: " +
10941122
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)