[Coverage] Use gap regions to select better line exec counts

After clang started emitting deferred regions (r312818), llvm-cov has
had a hard time picking reasonable line execuction counts. There have
been one or two generic improvements in this area (e.g r310012), but
line counts can still report coverage for whitespace instead of code
(llvm.org/PR34612).

To fix the problem:

 * Introduce a new region kind so that frontends can explicitly label
   gap areas.

   This is done by changing the encoding of the columnEnd field of
   MappingRegion. This doesn't substantially increase binary size, and
   makes it easy to maintain backwards-compatibility.

 * Don't set the line count to a count from a gap area, unless the count
   comes from a wrapped segment.

 * Don't highlight gap areas as uncovered.

Fixes llvm.org/PR34612.

llvm-svn: 313597
This commit is contained in:
Vedant Kumar 2017-09-18 23:37:28 +00:00
parent 27d1b14ab0
commit ad8f637bd8
11 changed files with 80 additions and 48 deletions

View File

@ -258,7 +258,7 @@ The coverage mapping variable generated by Clang has 3 fields:
i32 2, ; The number of function records
i32 20, ; The length of the string that contains the encoded translation unit filenames
i32 20, ; The length of the string that contains the encoded coverage mapping data
i32 1, ; Coverage mapping format version
i32 2, ; Coverage mapping format version
},
[2 x { i64, i32, i64 }] [ ; Function records
{ i64, i32, i64 } {
@ -274,6 +274,8 @@ The coverage mapping variable generated by Clang has 3 fields:
[40 x i8] c"..." ; Encoded data (dissected later)
}, section "__llvm_covmap", align 8
The current version of the format is version 3. The only difference from version 2 is that a special encoding for column end locations was introduced to indicate gap regions.
The function record layout has evolved since version 1. In version 1, the function record for *foo* is defined as follows:
.. code-block:: llvm
@ -296,7 +298,7 @@ The coverage mapping header has the following fields:
* The length of the string in the third field of *__llvm_coverage_mapping* that contains the encoded coverage mapping data.
* The format version. The current version is 2 (encoded as a 1).
* The format version. The current version is 3 (encoded as a 2).
.. _function records:
@ -602,4 +604,6 @@ The source range record contains the following fields:
* *numLines*: The difference between the ending line and the starting line
of the current mapping region.
* *columnEnd*: The ending column of the mapping region.
* *columnEnd*: The ending column of the mapping region. If the high bit is set,
the current mapping region is a gap area. A count for a gap area is only used
as the line execution count if there are no other regions on a line.

View File

@ -213,7 +213,11 @@ struct CounterMappingRegion {
/// A SkippedRegion represents a source range with code that was skipped
/// by a preprocessor or similar means.
SkippedRegion
SkippedRegion,
/// A GapRegion is like a CodeRegion, but its count is only set as the
/// line execution count when its the only region in the line.
GapRegion
};
Counter Count;
@ -250,6 +254,13 @@ struct CounterMappingRegion {
LineEnd, ColumnEnd, SkippedRegion);
}
static CounterMappingRegion
makeGapRegion(Counter Count, unsigned FileID, unsigned LineStart,
unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd) {
return CounterMappingRegion(Count, FileID, 0, LineStart, ColumnStart,
LineEnd, (1U << 31) | ColumnEnd, GapRegion);
}
inline LineColPair startLoc() const {
return LineColPair(LineStart, ColumnStart);
}
@ -377,19 +388,23 @@ struct CoverageSegment {
bool HasCount;
/// Whether this enters a new region or returns to a previous count.
bool IsRegionEntry;
/// Whether this enters a gap region.
bool IsGapRegion;
CoverageSegment(unsigned Line, unsigned Col, bool IsRegionEntry)
: Line(Line), Col(Col), Count(0), HasCount(false),
IsRegionEntry(IsRegionEntry) {}
IsRegionEntry(IsRegionEntry), IsGapRegion(false) {}
CoverageSegment(unsigned Line, unsigned Col, uint64_t Count,
bool IsRegionEntry)
bool IsRegionEntry, bool IsGapRegion = false)
: Line(Line), Col(Col), Count(Count), HasCount(true),
IsRegionEntry(IsRegionEntry) {}
IsRegionEntry(IsRegionEntry), IsGapRegion(IsGapRegion) {}
friend bool operator==(const CoverageSegment &L, const CoverageSegment &R) {
return std::tie(L.Line, L.Col, L.Count, L.HasCount, L.IsRegionEntry) ==
std::tie(R.Line, R.Col, R.Count, R.HasCount, R.IsRegionEntry);
return std::tie(L.Line, L.Col, L.Count, L.HasCount, L.IsRegionEntry,
L.IsGapRegion) == std::tie(R.Line, R.Col, R.Count,
R.HasCount, R.IsRegionEntry,
R.IsGapRegion);
}
};
@ -660,7 +675,10 @@ enum CovMapVersion {
// name string pointer to MD5 to support name section compression. Name
// section is also compressed.
Version2 = 1,
// The current version is Version2
// A new interpretation of the columnEnd field is added in order to mark
// regions as gap areas.
Version3 = 2,
// The current version is Version3
CurrentVersion = INSTR_PROF_COVMAP_VERSION
};

View File

@ -630,7 +630,7 @@ serializeValueProfDataFrom(ValueProfRecordClosure *Closure,
/* Indexed profile format version (start from 1). */
#define INSTR_PROF_INDEX_VERSION 4
/* Coverage mapping format vresion (start from 0). */
#define INSTR_PROF_COVMAP_VERSION 1
#define INSTR_PROF_COVMAP_VERSION 2
/* Profile version is always of type uint64_t. Reserve the upper 8 bits in the
* version for other variants of profile. We set the lowest bit of the upper 8

View File

@ -320,7 +320,7 @@ class SegmentBuilder {
/// Emit a segment with the count from \p Region starting at \p StartLoc.
//
/// \p IsRegionEntry: The segment is at the start of a new region.
/// \p IsRegionEntry: The segment is at the start of a new non-gap region.
/// \p EmitSkippedRegion: The segment must be emitted as a skipped region.
void startSegment(const CountedRegion &Region, LineColPair StartLoc,
bool IsRegionEntry, bool EmitSkippedRegion = false) {
@ -337,7 +337,8 @@ class SegmentBuilder {
if (HasCount)
Segments.emplace_back(StartLoc.first, StartLoc.second,
Region.ExecutionCount, IsRegionEntry);
Region.ExecutionCount, IsRegionEntry,
Region.Kind == CounterMappingRegion::GapRegion);
else
Segments.emplace_back(StartLoc.first, StartLoc.second, IsRegionEntry);
@ -346,7 +347,8 @@ class SegmentBuilder {
dbgs() << "Segment at " << Last.Line << ":" << Last.Col
<< " (count = " << Last.Count << ")"
<< (Last.IsRegionEntry ? ", RegionEntry" : "")
<< (!Last.HasCount ? ", Skipped" : "") << "\n";
<< (!Last.HasCount ? ", Skipped" : "")
<< (Last.IsGapRegion ? ", Gap" : "") << "\n";
});
}
@ -419,20 +421,22 @@ class SegmentBuilder {
completeRegionsUntil(CurStartLoc, FirstCompletedRegion);
}
bool GapRegion = CR.value().Kind == CounterMappingRegion::GapRegion;
// Try to emit a segment for the current region.
if (CurStartLoc == CR.value().endLoc()) {
// Avoid making zero-length regions active. If it's the last region,
// emit a skipped segment. Otherwise use its predecessor's count.
const bool Skipped = (CR.index() + 1) == Regions.size();
startSegment(ActiveRegions.empty() ? CR.value() : *ActiveRegions.back(),
CurStartLoc, true, Skipped);
CurStartLoc, !GapRegion, Skipped);
continue;
}
if (CR.index() + 1 == Regions.size() ||
CurStartLoc != Regions[CR.index() + 1].startLoc()) {
// Emit a segment if the next region doesn't start at the same location
// as this one.
startSegment(CR.value(), CurStartLoc, true);
startSegment(CR.value(), CurStartLoc, !GapRegion);
}
// This region is active (i.e not completed).

View File

@ -216,6 +216,13 @@ Error RawCoverageMappingReader::readMappingRegionsSubArray(
if (auto Err = readIntMax(ColumnEnd, std::numeric_limits<unsigned>::max()))
return Err;
LineStart += LineStartDelta;
// If the high bit of ColumnEnd is set, this is a gap region.
if (ColumnEnd & (1U << 31)) {
Kind = CounterMappingRegion::GapRegion;
ColumnEnd &= ~(1U << 31);
}
// Adjust the column locations for the empty regions that are supposed to
// cover whole lines. Those regions should be encoded with the
// column range (1 -> std::numeric_limits<unsigned>::max()), but because
@ -534,11 +541,16 @@ Expected<std::unique_ptr<CovMapFuncRecordReader>> CovMapFuncRecordReader::get(
return llvm::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version1, IntPtrT, Endian>>(P, R, F);
case CovMapVersion::Version2:
case CovMapVersion::Version3:
// Decompress the name data.
if (Error E = P.create(P.getNameData()))
return std::move(E);
if (Version == CovMapVersion::Version2)
return llvm::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version2, IntPtrT, Endian>>(P, R, F);
else
return llvm::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version3, IntPtrT, Endian>>(P, R, F);
}
llvm_unreachable("Unsupported version");
}

View File

@ -171,6 +171,7 @@ void CoverageMappingWriter::write(raw_ostream &OS) {
Counter Count = Minimizer.adjust(I->Count);
switch (I->Kind) {
case CounterMappingRegion::CodeRegion:
case CounterMappingRegion::GapRegion:
writeCounter(MinExpressions, Count, OS);
break;
case CounterMappingRegion::ExpansionRegion: {

View File

@ -1,7 +1,7 @@
// RUN: llvm-cov show %S/Inputs/deferred-regions.covmapping -instr-profile %S/Inputs/deferred-regions.profdata -show-line-counts-or-regions -dump -path-equivalence=/Users/vk/src/llvm.org-coverage-braces/llvm/test/tools,%S/.. %s 2>%t.markers > %t.out && FileCheck %s -input-file %t.out && FileCheck %s -input-file %t.markers -check-prefix=MARKER
void foo(int x) {
if (x == 0) {
if (x == 0) { // CHECK: [[@LINE]]|{{ +}}2|
return; // CHECK: [[@LINE]]|{{ +}}1|
}
@ -17,10 +17,10 @@ void for_loop() {
return; // CHECK: [[@LINE]]|{{ +}}0|
for (int i = 0; i < 10; ++i) { // CHECK: [[@LINE]]|{{ +}}2|
if (i % 2 == 0)
if (i % 2 == 0) // CHECK: [[@LINE]]|{{ +}}2|
continue; // CHECK: [[@LINE]]|{{ +}}1|
if (i % 5 == 0)
if (i % 5 == 0) // CHECK: [[@LINE]]|{{ +}}1|
break; // CHECK: [[@LINE]]|{{ +}}0|
int x = i;
@ -37,19 +37,19 @@ void while_loop() {
int x = 0;
while (++x < 10) { // CHECK: [[@LINE]]|{{ +}}3|
if (x == 1)
if (x == 1) // CHECK: [[@LINE]]|{{ +}}2|
continue; // CHECK: [[@LINE]]|{{ +}}1|
while (++x < 4) { // CHECK: [[@LINE]]|{{ +}}1|
if (x == 3)
if (x == 3) // CHECK: [[@LINE]]|{{ +}}1|
break; // CHECK: [[@LINE]]|{{ +}}1|
// CHECK: [[@LINE]]|{{ +}}0|
while (++x < 5) {} // CHECK: [[@LINE]]|{{ +}}0|
} // CHECK: [[@LINE]]|{{ +}}1|
if (x == 0)
if (x == 0) // CHECK: [[@LINE]]|{{ +}}1|
throw Error(); // CHECK: [[@LINE]]|{{ +}}0|
// CHECK: [[@LINE]]|{{ +}}1|
while (++x < 9) { // CHECK: [[@LINE]]|{{ +}}6|
if (x == 0) // CHECK: [[@LINE]]|{{ +}}5|
break; // CHECK: [[@LINE]]|{{ +}}0|
@ -59,12 +59,12 @@ void while_loop() {
}
void gotos() {
if (false)
if (false) // CHECK: [[@LINE]]|{{ +}}1|
goto out; // CHECK: [[@LINE]]|{{ +}}0|
// CHECK: [[@LINE]]|{{ +}}1|
return; // CHECK: [[@LINE]]|{{ +}}1|
return;
out: // CHECK: [[@LINE]]|{{ +}}1|
out: // CHECK: [[@LINE]]|{{ +}}0|
return;
}
@ -80,23 +80,16 @@ int main() {
// MARKER: Marker at 4:7 = 2
// MARKER-NEXT: Highlighted line 17, 5 -> 11
// MARKER-NEXT: Marker at 17:5 = 0
// MARKER-NEXT: Marker at 19:3 = 1
// MARKER-NEXT: Marker at 19:19 = 2
// MARKER-NEXT: Marker at 19:27 = 1
// MARKER-NEXT: Marker at 21:7 = 1
// MARKER-NEXT: Marker at 23:5 = 1
// MARKER-NEXT: Marker at 23:9 = 1
// MARKER-NEXT: Highlighted line 24, 7 -> 12
// MARKER-NEXT: Marker at 24:7 = 0
// MARKER-NEXT: Highlighted line 36, 5 -> 11
// MARKER-NEXT: Marker at 36:5 = 0
// MARKER-NEXT: Marker at 39:10 = 3
// MARKER-NEXT: Marker at 41:7 = 1
// MARKER-NEXT: Marker at 43:5 = 1
// MARKER-NEXT: Marker at 43:12 = 1
// MARKER-NEXT: Highlighted line 45, 14 -> ?
// MARKER-NEXT: Marker at 45:9 = 1
// MARKER-NEXT: Highlighted line 46, 1 -> ?
// MARKER-NEXT: Highlighted line 47, 1 -> 7
// MARKER-NEXT: Highlighted line 47, 7 -> 14
@ -107,13 +100,10 @@ int main() {
// MARKER-NEXT: Marker at 47:14 = 0
// MARKER-NEXT: Marker at 47:23 = 0
// MARKER-NEXT: Highlighted line 51, 7 -> 20
// MARKER-NEXT: Marker at 51:7 = 0
// MARKER-NEXT: Marker at 53:5 = 1
// MARKER-NEXT: Marker at 53:12 = 6
// MARKER-NEXT: Highlighted line 55, 9 -> 14
// MARKER-NEXT: Highlighted line 63, 5 -> 13
// MARKER-NEXT: Marker at 63:5 = 0
// MARKER-NEXT: Highlighted line 67, 1 -> ?
// MARKER-NEXT: Highlighted line 68, 1 -> 8
// MARKER-NEXT: Highlighted line 68, 8 -> ?
// MARKER-NEXT: Highlighted line 69, 1 -> 2

View File

@ -89,7 +89,7 @@ LineCoverageStats::LineCoverageStats(
// Find the minimum number of regions which start in this line.
unsigned MinRegionCount = 0;
auto isStartOfRegion = [](const coverage::CoverageSegment *S) {
return S->HasCount && S->IsRegionEntry;
return !S->IsGapRegion && S->HasCount && S->IsRegionEntry;
};
for (unsigned I = 0; I < LineSegments.size() && MinRegionCount < 2; ++I)
if (isStartOfRegion(LineSegments[I]))
@ -112,16 +112,19 @@ LineCoverageStats::LineCoverageStats(
// avoid erroneously using the wrapped count, and to avoid picking region
// counts which come from deferred regions.
if (LineSegments.size() > 1) {
for (unsigned I = 0; I < LineSegments.size() - 1; ++I)
for (unsigned I = 0; I < LineSegments.size() - 1; ++I) {
if (!LineSegments[I]->IsGapRegion)
ExecutionCount = std::max(ExecutionCount, LineSegments[I]->Count);
}
return;
}
// Just pick the maximum count.
if (WrappedSegment && WrappedSegment->HasCount)
// If a non-gap region starts here, use its count. Otherwise use the wrapped
// count.
if (MinRegionCount == 1)
ExecutionCount = LineSegments[0]->Count;
else
ExecutionCount = WrappedSegment->Count;
if (!LineSegments.empty())
ExecutionCount = std::max(ExecutionCount, LineSegments[0]->Count);
}
unsigned SourceCoverageView::getFirstUncoveredLineNo() {

View File

@ -527,7 +527,7 @@ void SourceCoverageViewHTML::renderLine(
const auto *CurSeg = Segments[I];
if (CurSeg->Col == ExpansionCol)
Color = "cyan";
else if (CheckIfUncovered(CurSeg))
else if (!CurSeg->IsGapRegion && CheckIfUncovered(CurSeg))
Color = "red";
else
Color = None;

View File

@ -120,7 +120,7 @@ void SourceCoverageViewText::renderLine(
Col = End;
if (Col == ExpansionCol)
Highlight = raw_ostream::CYAN;
else if (S->HasCount && S->Count == 0)
else if (!S->IsGapRegion && S->HasCount && S->Count == 0)
Highlight = raw_ostream::RED;
else
Highlight = None;