Skip to content

llvm-cov: Introduce --merge-instantiations=<MergeStrategy> #121194

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: users/chapuni/cov/merge/forfile
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 78 additions & 4 deletions llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ namespace coverage {
class CoverageMappingReader;
struct CoverageMappingRecord;

enum class MergeStrategy {
Merge,
Any,
All,
};

enum class coveragemap_error {
success = 0,
eof,
Expand Down Expand Up @@ -384,6 +390,32 @@ struct CountedRegion : public CounterMappingRegion {
: CounterMappingRegion(R), ExecutionCount(ExecutionCount),
FalseExecutionCount(FalseExecutionCount), TrueFolded(false),
FalseFolded(false) {}

LineColPair viewLoc() const { return startLoc(); }

bool isMergeable(const CountedRegion &RHS) const {
return (this->viewLoc() == RHS.viewLoc());
}

void merge(const CountedRegion &RHS, MergeStrategy Strategy);

/// Returns comparable rank value in selecting a better Record for merging.
auto getMergeRank(MergeStrategy Strategy) const {
assert(isBranch() && "Dedicated to Branch");
assert(Strategy == MergeStrategy::Any && "Dedicated to Any");
unsigned m = 0;
// Prefer both Counts have values.
m = (m << 1) | (ExecutionCount != 0 && FalseExecutionCount != 0);
// Prefer both are unfolded.
m = (m << 1) | (!TrueFolded && !FalseFolded);
// Prefer either Count has value.
m = (m << 1) | (ExecutionCount != 0 || FalseExecutionCount != 0);
// Prefer either is unfolded.
m = (m << 1) | (!TrueFolded || !FalseFolded);
return std::make_pair(m, ExecutionCount + FalseExecutionCount);
}

void commit() const {}
};

/// MCDC Record grouping all information together.
Expand Down Expand Up @@ -471,6 +503,19 @@ struct MCDCRecord {
findIndependencePairs();
}

inline LineColPair viewLoc() const { return Region.endLoc(); }

bool isMergeable(const MCDCRecord &RHS) const {
return (this->viewLoc() == RHS.viewLoc() && this->PosToID == RHS.PosToID &&
this->CondLoc == RHS.CondLoc);
}

// This may invalidate IndependencePairs
// MCDCRecord &operator+=(const MCDCRecord &RHS);
void merge(MCDCRecord &&RHS, MergeStrategy Strategy);

void commit() { findIndependencePairs(); }

// Compare executed test vectors against each other to find an independence
// pairs for each condition. This processing takes the most time.
void findIndependencePairs();
Expand Down Expand Up @@ -523,15 +568,42 @@ struct MCDCRecord {
return (*IndependencePairs)[PosToID[Condition]];
}

float getPercentCovered() const {
unsigned Folded = 0;
std::pair<unsigned, unsigned> getCoveredCount() const {
unsigned Covered = 0;
unsigned Folded = 0;
for (unsigned C = 0; C < getNumConditions(); C++) {
if (isCondFolded(C))
Folded++;
else if (isConditionIndependencePairCovered(C))
Covered++;
}
return {Covered, Folded};
}

/// Returns comparable rank value in selecting a better Record for merging.
std::tuple<unsigned, unsigned, unsigned>
getMergeRank(MergeStrategy Strategy) const {
auto [Covered, Folded] = getCoveredCount();
auto NumTVs = getNumTestVectors();
switch (Strategy) {
case MergeStrategy::Merge:
case MergeStrategy::Any:
return {
Covered, // The largest covered number
~Folded, // Less folded is better
NumTVs, // Show more test vectors
};
case MergeStrategy::All:
return {
~Covered, // The smallest covered number
~Folded, // Less folded is better
NumTVs, // Show more test vectors
};
}
}

float getPercentCovered() const {
auto [Covered, Folded] = getCoveredCount();

unsigned Total = getNumConditions() - Folded;
if (Total == 0)
Expand Down Expand Up @@ -1024,11 +1096,13 @@ class CoverageMapping {
/// information. That is, only names returned from getUniqueSourceFiles will
/// yield a result.
CoverageData getCoverageForFile(
StringRef Filename,
StringRef Filename, MergeStrategy Strategy = MergeStrategy::Merge,
const DenseSet<const FunctionRecord *> &FilteredOutFunctions = {}) const;

/// Get the coverage for a particular function.
CoverageData getCoverageForFunction(const FunctionRecord &Function) const;
CoverageData
getCoverageForFunction(const FunctionRecord &Function,
MergeStrategy Strategy = MergeStrategy::Merge) const;

/// Get the coverage for an expansion within a coverage set.
CoverageData getCoverageForExpansion(const ExpansionRecord &Expansion) const;
Expand Down
171 changes: 156 additions & 15 deletions llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <iterator>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <stack>
#include <string>
Expand Down Expand Up @@ -246,6 +247,58 @@ Expected<int64_t> CounterMappingContext::evaluate(const Counter &C) const {
return LastPoppedValue;
}

void CountedRegion::merge(const CountedRegion &RHS, MergeStrategy Strategy) {
assert(this->isBranch() && RHS.isBranch());
auto MergeCounts = [Strategy](uint64_t &LHSCount, bool &LHSFolded,
uint64_t RHSCount, bool RHSFolded) {
switch (Strategy) {
default:
llvm_unreachable("Don't perform by-parameter merging");
case MergeStrategy::Merge:
LHSCount += RHSCount;
LHSFolded = (LHSFolded && !LHSCount && RHSFolded);
break;
case MergeStrategy::All:
LHSCount = (LHSFolded ? RHSCount
: RHSFolded ? LHSCount
: std::min(LHSCount, RHSCount));
LHSFolded = (LHSFolded && RHSFolded);
break;
}
};

switch (Strategy) {
case MergeStrategy::Any:
// Take either better (more satisfied) hand side.
// FIXME: Really needed? Better just to merge?
if (this->getMergeRank(Strategy) < RHS.getMergeRank(Strategy))
*this = RHS;
break;
case MergeStrategy::Merge:
case MergeStrategy::All:
// Able to merge by parameter.
MergeCounts(this->ExecutionCount, this->TrueFolded, RHS.ExecutionCount,
RHS.TrueFolded);
MergeCounts(this->FalseExecutionCount, this->FalseFolded,
RHS.FalseExecutionCount, RHS.FalseFolded);
break;
}
}

void MCDCRecord::merge(MCDCRecord &&RHS, MergeStrategy Strategy) {
assert(this->PosToID == RHS.PosToID);
assert(this->CondLoc == RHS.CondLoc);

switch (Strategy) {
case MergeStrategy::Merge:
case MergeStrategy::Any:
case MergeStrategy::All:
if (this->getMergeRank(Strategy) < RHS.getMergeRank(Strategy))
*this = std::move(RHS);
return;
}
}

// Find an independence pair for each condition:
// - The condition is true in one test and false in the other.
// - The decision outcome is true one test and false in the other.
Expand Down Expand Up @@ -1298,7 +1351,8 @@ class SegmentBuilder {

/// Combine counts of regions which cover the same area.
static ArrayRef<CountedRegion>
combineRegions(MutableArrayRef<CountedRegion> Regions) {
combineRegions(MutableArrayRef<CountedRegion> Regions,
MergeStrategy Strategy) {
if (Regions.empty())
return Regions;
auto Active = Regions.begin();
Expand All @@ -1324,21 +1378,36 @@ class SegmentBuilder {
// value for that area.
// We add counts of the regions of the same kind as the active region
// to handle the both situations.
if (I->Kind == Active->Kind)
if (I->Kind != Active->Kind)
continue;

switch (Strategy) {
case MergeStrategy::Merge:
Active->ExecutionCount += I->ExecutionCount;
break;
case MergeStrategy::Any:
Active->ExecutionCount =
std::max(Active->ExecutionCount, I->ExecutionCount);
break;
case MergeStrategy::All:
Active->ExecutionCount =
std::min(Active->ExecutionCount, I->ExecutionCount);
break;
}
}
return Regions.drop_back(std::distance(++Active, End));
}

public:
/// Build a sorted list of CoverageSegments from a list of Regions.
static std::vector<CoverageSegment>
buildSegments(MutableArrayRef<CountedRegion> Regions) {
buildSegments(MutableArrayRef<CountedRegion> Regions,
MergeStrategy Strategy) {
std::vector<CoverageSegment> Segments;
SegmentBuilder Builder(Segments);

sortNestedRegions(Regions);
ArrayRef<CountedRegion> CombinedRegions = combineRegions(Regions);
ArrayRef<CountedRegion> CombinedRegions = combineRegions(Regions, Strategy);

LLVM_DEBUG({
dbgs() << "Combined regions:\n";
Expand Down Expand Up @@ -1368,11 +1437,78 @@ class SegmentBuilder {
}
};

template <class RecTy>
static void mergeRecords(std::vector<RecTy> &Records, MergeStrategy Strategy) {
if (Records.empty())
return;

std::vector<RecTy> NewRecords;

assert(Records.size() <= std::numeric_limits<unsigned>::max());

// Build up sorted indices (rather than pointers) of Records.
SmallVector<unsigned, 1> BIdxs(Records.size());
std::iota(BIdxs.begin(), BIdxs.end(), 0);
llvm::stable_sort(BIdxs, [&](auto A, auto B) {
auto StartA = Records[A].viewLoc();
auto StartB = Records[B].viewLoc();
return (std::tie(StartA, A) < std::tie(StartB, B));
});

// 1st element should be stored into SubView.
auto I = BIdxs.begin(), E = BIdxs.end();
SmallVector<RecTy, 1> ViewRecords{Records[*I++]};

auto findMergeableInViewRecords = [&](const RecTy &Branch) {
auto I = ViewRecords.rbegin(), E = ViewRecords.rend();
for (; I != E; ++I)
if (I->isMergeable(Branch))
return I;

// Not mergeable.
return E;
};

auto addRecordToSubView = [&] {
assert(!ViewRecords.empty() && "Should have the back");
for (auto &Acc : ViewRecords) {
Acc.commit();
NewRecords.push_back(std::move(Acc));
}
};

for (; I != E; ++I) {
assert(!ViewRecords.empty() && "Should have the back in the loop");
auto &AccB = ViewRecords.back();
auto &Branch = Records[*I];

// Flush current and create the next SubView at the different line.
if (AccB.viewLoc().first != Branch.viewLoc().first) {
addRecordToSubView();
ViewRecords = {Branch};
} else if (auto AccI = findMergeableInViewRecords(Branch);
AccI != ViewRecords.rend()) {
// Merge the current Branch into the back of SubView.
AccI->merge(std::move(Branch), Strategy);
} else {
// Not mergeable.
ViewRecords.push_back(Branch);
}
}

// Flush the last SubView.
addRecordToSubView();

// Replace
Records = std::move(NewRecords);
}

struct MergeableCoverageData : public CoverageData {
std::vector<CountedRegion> CodeRegions;
MergeStrategy Strategy;

MergeableCoverageData(bool Single, StringRef Filename)
: CoverageData(Single, Filename) {}
MergeableCoverageData(MergeStrategy Strategy, bool Single, StringRef Filename)
: CoverageData(Single, Filename), Strategy(Strategy) {}

void addFunctionRegions(
const FunctionRecord &Function,
Expand All @@ -1394,8 +1530,11 @@ struct MergeableCoverageData : public CoverageData {
MCDCRecords.push_back(MR);
}

CoverageData buildSegments() {
Segments = SegmentBuilder::buildSegments(CodeRegions);
CoverageData merge(MergeStrategy Strategy) {
mergeRecords(BranchRegions, Strategy);
mergeRecords(MCDCRecords, Strategy);

Segments = SegmentBuilder::buildSegments(CodeRegions, Strategy);
return CoverageData(std::move(*this));
}
};
Expand Down Expand Up @@ -1449,10 +1588,10 @@ static bool isExpansion(const CountedRegion &R, unsigned FileID) {
}

CoverageData CoverageMapping::getCoverageForFile(
StringRef Filename,
StringRef Filename, MergeStrategy Strategy,
const DenseSet<const FunctionRecord *> &FilteredOutFunctions) const {
assert(SingleByteCoverage);
MergeableCoverageData FileCoverage(*SingleByteCoverage, Filename);
MergeableCoverageData FileCoverage(Strategy, *SingleByteCoverage, Filename);

// Look up the function records in the given file. Due to hash collisions on
// the filename, we may get back some records that are not in the file.
Expand All @@ -1471,7 +1610,7 @@ CoverageData CoverageMapping::getCoverageForFile(

LLVM_DEBUG(dbgs() << "Emitting segments for file: " << Filename << "\n");

return FileCoverage.buildSegments();
return FileCoverage.merge(Strategy);
}

std::vector<InstantiationGroup>
Expand Down Expand Up @@ -1500,13 +1639,14 @@ CoverageMapping::getInstantiationGroups(StringRef Filename) const {
}

CoverageData
CoverageMapping::getCoverageForFunction(const FunctionRecord &Function) const {
CoverageMapping::getCoverageForFunction(const FunctionRecord &Function,
MergeStrategy Strategy) const {
auto MainFileID = findMainViewFileID(Function);
if (!MainFileID)
return CoverageData();

assert(SingleByteCoverage);
MergeableCoverageData FunctionCoverage(*SingleByteCoverage,
MergeableCoverageData FunctionCoverage(Strategy, *SingleByteCoverage,
Function.Filenames[*MainFileID]);
FunctionCoverage.addFunctionRegions(
Function, [&](auto &CR) { return (CR.FileID == *MainFileID); },
Expand All @@ -1515,7 +1655,7 @@ CoverageMapping::getCoverageForFunction(const FunctionRecord &Function) const {
LLVM_DEBUG(dbgs() << "Emitting segments for function: " << Function.Name
<< "\n");

return FunctionCoverage.buildSegments();
return FunctionCoverage.merge(Strategy);
}

CoverageData CoverageMapping::getCoverageForExpansion(
Expand All @@ -1537,7 +1677,8 @@ CoverageData CoverageMapping::getCoverageForExpansion(

LLVM_DEBUG(dbgs() << "Emitting segments for expansion of file "
<< Expansion.FileID << "\n");
ExpansionCoverage.Segments = SegmentBuilder::buildSegments(Regions);
ExpansionCoverage.Segments =
SegmentBuilder::buildSegments(Regions, MergeStrategy::Merge);

return ExpansionCoverage;
}
Expand Down
Loading
Loading