From f1c5f8681a1227487c3850a9531672e515a13c13 Mon Sep 17 00:00:00 2001 From: Vedant Kumar Date: Wed, 18 Oct 2017 23:58:28 +0000 Subject: [PATCH] [llvm-cov] Move LineCoverageIterator to libCoverage. NFC. LineCoverageIterator makes it easy for clients of coverage data to determine line execution counts for a file or function. The coverage iteration logic is tricky enough that it really pays not to have multiple copies of it. Hopefully having just one implementation in LLVM will make the iteration logic easier to test, reuse, and update. This commit is NFC but I've added a unit test to go along with it just because it's easy to do now. llvm-svn: 316141 --- .../ProfileData/Coverage/CoverageMapping.h | 83 +++++++++++++++++ lib/ProfileData/Coverage/CoverageMapping.cpp | 53 +++++++++++ tools/llvm-cov/CoverageSummaryInfo.cpp | 53 ----------- tools/llvm-cov/CoverageSummaryInfo.h | 89 ------------------- unittests/ProfileData/CoverageMappingTest.cpp | 57 ++++++++++++ 5 files changed, 193 insertions(+), 142 deletions(-) diff --git a/include/llvm/ProfileData/Coverage/CoverageMapping.h b/include/llvm/ProfileData/Coverage/CoverageMapping.h index 22286f6d468..5a4098cf666 100644 --- a/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -594,6 +594,89 @@ public: getInstantiationGroups(StringRef Filename) const; }; +/// Coverage statistics for a single line. +class LineCoverageStats { + uint64_t ExecutionCount; + bool HasMultipleRegions; + bool Mapped; + unsigned Line; + ArrayRef LineSegments; + const CoverageSegment *WrappedSegment; + + friend class LineCoverageIterator; + LineCoverageStats() = default; + +public: + LineCoverageStats(ArrayRef LineSegments, + const CoverageSegment *WrappedSegment, unsigned Line); + + uint64_t getExecutionCount() const { return ExecutionCount; } + + bool hasMultipleRegions() const { return HasMultipleRegions; } + + bool isMapped() const { return Mapped; } + + unsigned getLine() const { return Line; } + + ArrayRef getLineSegments() const { + return LineSegments; + } + + const CoverageSegment *getWrappedSegment() const { return WrappedSegment; } +}; + +/// An iterator over the \c LineCoverageStats objects for lines described by +/// a \c CoverageData instance. +class LineCoverageIterator + : public iterator_facade_base< + LineCoverageIterator, std::forward_iterator_tag, LineCoverageStats> { +public: + LineCoverageIterator(const CoverageData &CD) + : LineCoverageIterator(CD, CD.begin()->Line) {} + + LineCoverageIterator(const CoverageData &CD, unsigned Line) + : CD(CD), WrappedSegment(nullptr), Next(CD.begin()), Ended(false), + Line(Line), Segments(), Stats() { + this->operator++(); + } + + LineCoverageIterator &operator=(const LineCoverageIterator &R) = default; + + bool operator==(const LineCoverageIterator &R) const { + return &CD == &R.CD && Next == R.Next && Ended == R.Ended; + } + + const LineCoverageStats &operator*() const { return Stats; } + + LineCoverageStats &operator*() { return Stats; } + + LineCoverageIterator &operator++(); + + LineCoverageIterator getEnd() const { + auto EndIt = *this; + EndIt.Next = CD.end(); + EndIt.Ended = true; + return EndIt; + } + +private: + const CoverageData &CD; + const CoverageSegment *WrappedSegment; + std::vector::const_iterator Next; + bool Ended; + unsigned Line; + SmallVector Segments; + LineCoverageStats Stats; +}; + +/// Get a \c LineCoverageIterator range for the lines described by \p CD. +static inline iterator_range +getLineCoverageStats(const coverage::CoverageData &CD) { + auto Begin = LineCoverageIterator(CD); + auto End = Begin.getEnd(); + return make_range(Begin, End); +} + // Profile coverage map has the following layout: // [CoverageMapFileHeader] // [ArrayStart] diff --git a/lib/ProfileData/Coverage/CoverageMapping.cpp b/lib/ProfileData/Coverage/CoverageMapping.cpp index 52f9447aa3e..bda61768e7b 100644 --- a/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -671,6 +671,59 @@ CoverageData CoverageMapping::getCoverageForExpansion( return ExpansionCoverage; } +LineCoverageStats::LineCoverageStats( + ArrayRef LineSegments, + const coverage::CoverageSegment *WrappedSegment, unsigned Line) + : ExecutionCount(0), HasMultipleRegions(false), Mapped(false), Line(Line), + LineSegments(LineSegments), WrappedSegment(WrappedSegment) { + // Find the minimum number of regions which start in this line. + unsigned MinRegionCount = 0; + auto isStartOfRegion = [](const coverage::CoverageSegment *S) { + return !S->IsGapRegion && S->HasCount && S->IsRegionEntry; + }; + for (unsigned I = 0; I < LineSegments.size() && MinRegionCount < 2; ++I) + if (isStartOfRegion(LineSegments[I])) + ++MinRegionCount; + + bool StartOfSkippedRegion = !LineSegments.empty() && + !LineSegments.front()->HasCount && + LineSegments.front()->IsRegionEntry; + + HasMultipleRegions = MinRegionCount > 1; + Mapped = + !StartOfSkippedRegion && + ((WrappedSegment && WrappedSegment->HasCount) || (MinRegionCount > 0)); + + if (!Mapped) + return; + + // Pick the max count from the non-gap, region entry segments. If there + // aren't any, use the wrapped count. + if (!MinRegionCount) { + ExecutionCount = WrappedSegment->Count; + return; + } + for (const auto *LS : LineSegments) + if (isStartOfRegion(LS)) + ExecutionCount = std::max(ExecutionCount, LS->Count); +} + +LineCoverageIterator &LineCoverageIterator::operator++() { + if (Next == CD.end()) { + Stats = LineCoverageStats(); + Ended = true; + return *this; + } + if (Segments.size()) + WrappedSegment = Segments.back(); + Segments.clear(); + while (Next != CD.end() && Next->Line == Line) + Segments.push_back(&*Next++); + Stats = LineCoverageStats(Segments, WrappedSegment, Line); + ++Line; + return *this; +} + static std::string getCoverageMapErrString(coveragemap_error Err) { switch (Err) { case coveragemap_error::success: diff --git a/tools/llvm-cov/CoverageSummaryInfo.cpp b/tools/llvm-cov/CoverageSummaryInfo.cpp index d1fcef4299d..7847a2abf48 100644 --- a/tools/llvm-cov/CoverageSummaryInfo.cpp +++ b/tools/llvm-cov/CoverageSummaryInfo.cpp @@ -17,59 +17,6 @@ using namespace llvm; using namespace coverage; -LineCoverageStats::LineCoverageStats( - ArrayRef LineSegments, - const coverage::CoverageSegment *WrappedSegment, unsigned Line) - : ExecutionCount(0), HasMultipleRegions(false), Mapped(false), Line(Line), - LineSegments(LineSegments), WrappedSegment(WrappedSegment) { - // Find the minimum number of regions which start in this line. - unsigned MinRegionCount = 0; - auto isStartOfRegion = [](const coverage::CoverageSegment *S) { - return !S->IsGapRegion && S->HasCount && S->IsRegionEntry; - }; - for (unsigned I = 0; I < LineSegments.size() && MinRegionCount < 2; ++I) - if (isStartOfRegion(LineSegments[I])) - ++MinRegionCount; - - bool StartOfSkippedRegion = !LineSegments.empty() && - !LineSegments.front()->HasCount && - LineSegments.front()->IsRegionEntry; - - HasMultipleRegions = MinRegionCount > 1; - Mapped = - !StartOfSkippedRegion && - ((WrappedSegment && WrappedSegment->HasCount) || (MinRegionCount > 0)); - - if (!Mapped) - return; - - // Pick the max count from the non-gap, region entry segments. If there - // aren't any, use the wrapped count. - if (!MinRegionCount) { - ExecutionCount = WrappedSegment->Count; - return; - } - for (const auto *LS : LineSegments) - if (isStartOfRegion(LS)) - ExecutionCount = std::max(ExecutionCount, LS->Count); -} - -LineCoverageIterator &LineCoverageIterator::operator++() { - if (Next == CD.end()) { - Stats = LineCoverageStats(); - Ended = true; - return *this; - } - if (Segments.size()) - WrappedSegment = Segments.back(); - Segments.clear(); - while (Next != CD.end() && Next->Line == Line) - Segments.push_back(&*Next++); - Stats = LineCoverageStats(Segments, WrappedSegment, Line); - ++Line; - return *this; -} - FunctionCoverageSummary FunctionCoverageSummary::get(const CoverageMapping &CM, const coverage::FunctionRecord &Function) { diff --git a/tools/llvm-cov/CoverageSummaryInfo.h b/tools/llvm-cov/CoverageSummaryInfo.h index d3f43d19104..8eae0b7fec9 100644 --- a/tools/llvm-cov/CoverageSummaryInfo.h +++ b/tools/llvm-cov/CoverageSummaryInfo.h @@ -15,8 +15,6 @@ #ifndef LLVM_COV_COVERAGESUMMARYINFO_H #define LLVM_COV_COVERAGESUMMARYINFO_H -#include "llvm/ADT/iterator.h" -#include "llvm/ADT/iterator_range.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/Support/raw_ostream.h" @@ -138,93 +136,6 @@ public: } }; -/// \brief Coverage statistics for a single line. -class LineCoverageStats { - uint64_t ExecutionCount; - bool HasMultipleRegions; - bool Mapped; - unsigned Line; - ArrayRef LineSegments; - const coverage::CoverageSegment *WrappedSegment; - - friend class LineCoverageIterator; - LineCoverageStats() = default; - -public: - LineCoverageStats(ArrayRef LineSegments, - const coverage::CoverageSegment *WrappedSegment, - unsigned Line); - - uint64_t getExecutionCount() const { return ExecutionCount; } - - bool hasMultipleRegions() const { return HasMultipleRegions; } - - bool isMapped() const { return Mapped; } - - unsigned getLine() const { return Line; } - - ArrayRef getLineSegments() const { - return LineSegments; - } - - const coverage::CoverageSegment *getWrappedSegment() const { - return WrappedSegment; - } -}; - -/// Iterates over LineCoverageStats for each line described by a CoverageData -/// object. -class LineCoverageIterator - : public iterator_facade_base< - LineCoverageIterator, std::forward_iterator_tag, LineCoverageStats> { -public: - LineCoverageIterator(const coverage::CoverageData &CD) - : LineCoverageIterator(CD, CD.begin()->Line) {} - - LineCoverageIterator(const coverage::CoverageData &CD, unsigned Line) - : CD(CD), WrappedSegment(nullptr), Next(CD.begin()), Ended(false), - Line(Line), Segments(), Stats() { - this->operator++(); - } - - LineCoverageIterator &operator=(const LineCoverageIterator &R) = default; - - bool operator==(const LineCoverageIterator &R) const { - return &CD == &R.CD && Next == R.Next && Ended == R.Ended; - } - - const LineCoverageStats &operator*() const { return Stats; } - - LineCoverageStats &operator*() { return Stats; } - - LineCoverageIterator &operator++(); - - LineCoverageIterator getEnd() const { - auto EndIt = *this; - EndIt.Next = CD.end(); - EndIt.Ended = true; - return EndIt; - } - -private: - const coverage::CoverageData &CD; - const coverage::CoverageSegment *WrappedSegment; - std::vector::const_iterator Next; - bool Ended; - unsigned Line; - SmallVector Segments; - LineCoverageStats Stats; -}; - -/// Get a range of LineCoverageStats for each line described by a CoverageData -/// object. -static inline iterator_range -getLineCoverageStats(const coverage::CoverageData &CD) { - auto Begin = LineCoverageIterator(CD); - auto End = Begin.getEnd(); - return make_range(Begin, End); -} - /// \brief A summary of function's code coverage. struct FunctionCoverageSummary { std::string Name; diff --git a/unittests/ProfileData/CoverageMappingTest.cpp b/unittests/ProfileData/CoverageMappingTest.cpp index 8c9c8c48234..ad0a0cfb873 100644 --- a/unittests/ProfileData/CoverageMappingTest.cpp +++ b/unittests/ProfileData/CoverageMappingTest.cpp @@ -635,6 +635,63 @@ TEST_P(CoverageMappingTest, basic_coverage_iteration) { ASSERT_EQ(CoverageSegment(11, 11, false), Segments[6]); } +TEST_P(CoverageMappingTest, test_line_coverage_iterator) { + ProfileWriter.addRecord({"func", 0x1234, {30, 20, 10, 0}}, Err); + + startFunction("func", 0x1234); + addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9); + addCMR(Counter::getCounter(1), "file1", 1, 1, 4, 7); + addCMR(Counter::getCounter(2), "file1", 5, 8, 9, 1); + addCMR(Counter::getCounter(3), "file1", 10, 10, 11, 11); + EXPECT_THAT_ERROR(loadCoverageMapping(), Succeeded()); + + CoverageData Data = LoadedCoverage->getCoverageForFile("file1"); + + unsigned NumLineStats = 0; + for (const auto &LCS : getLineCoverageStats(Data)) { + ++NumLineStats; + (void)LCS; + } + ASSERT_EQ(11U, NumLineStats); + + LineCoverageIterator LCI{Data}; + + ASSERT_EQ(1U, LCI->getLine()); + ASSERT_EQ(20ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(2U, LCI->getLine()); + ASSERT_EQ(20ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(3U, LCI->getLine()); + ASSERT_EQ(20ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(4U, LCI->getLine()); + ASSERT_EQ(20ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(5U, LCI->getLine()); + ASSERT_EQ(10ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(6U, LCI->getLine()); + ASSERT_EQ(10ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(7U, LCI->getLine()); + ASSERT_EQ(10ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(8U, LCI->getLine()); + ASSERT_EQ(10ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(9U, LCI->getLine()); + ASSERT_EQ(10ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(10U, LCI->getLine()); + ASSERT_EQ(0ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(11U, LCI->getLine()); + ASSERT_EQ(0ULL, LCI->getExecutionCount()); + ++LCI; + ASSERT_EQ(LCI, LCI.getEnd()); +} + TEST_P(CoverageMappingTest, uncovered_function) { startFunction("func", 0x1234); addCMR(Counter::getZero(), "file1", 1, 2, 3, 4);