Skip to content
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

[Coverage] Let Decision take account of expansions #78969

Merged
merged 12 commits into from
Feb 2, 2024
240 changes: 197 additions & 43 deletions llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "llvm/ProfileData/Coverage/CoverageMapping.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
Expand Down Expand Up @@ -582,6 +583,160 @@ static unsigned getMaxBitmapSize(const CounterMappingContext &Ctx,
return MaxBitmapID + (SizeInBits / CHAR_BIT);
}

namespace {

/// Collect Decisions, Branchs, and Expansions and associate them.
class MCDCDecisionRecorder {
private:
/// This holds the DecisionRegion and MCDCBranches under it.
/// Also traverses Expansion(s).
/// The Decision has the number of MCDCBranches and will complete
/// when it is filled with unique ConditionID of MCDCBranches.
struct DecisionRecord {
const CounterMappingRegion *DecisionRegion;

/// They are reflected from DecisionRegion for convenience.
LineColPair DecisionStartLoc;
LineColPair DecisionEndLoc;

/// This is passed to `MCDCRecordProcessor`, so this should be compatible
/// to`ArrayRef<const CounterMappingRegion *>`.
SmallVector<const CounterMappingRegion *> MCDCBranches;

/// IDs that are stored in MCDCBranches
/// Complete when all IDs (1 to NumConditions) are met.
DenseSet<CounterMappingRegion::MCDCConditionID> ConditionIDs;

/// Set of IDs of Expansion(s) that are relevant to DecisionRegion
/// and its children (via expansions).
/// FileID pointed by ExpandedFileID is dedicated to the expansion, so
/// the location in the expansion doesn't matter.
DenseSet<unsigned> ExpandedFileIDs;

DecisionRecord(const CounterMappingRegion &Decision)
: DecisionRegion(&Decision), DecisionStartLoc(Decision.startLoc()),
DecisionEndLoc(Decision.endLoc()) {
assert(Decision.Kind == CounterMappingRegion::MCDCDecisionRegion);
}

/// Determine whether DecisionRecord dominates `R`.
bool dominates(const CounterMappingRegion &R) const {
// Determine whether `R` is included in `DecisionRegion`.
if (R.FileID == DecisionRegion->FileID &&
R.startLoc() >= DecisionStartLoc && R.endLoc() <= DecisionEndLoc)
return true;

// Determine whether `R` is pointed by any of Expansions.
return ExpandedFileIDs.contains(R.FileID);
}

enum Result {
NotProcessed = 0, /// Irrelevant to this Decision
Processed, /// Added to this Decision
Completed, /// Added and filled this Decision
};

/// Add Branch into the Decision
/// \param Branch expects MCDCBranchRegion
/// \returns NotProcessed/Processed/Completed
Result addBranch(const CounterMappingRegion &Branch) {
assert(Branch.Kind == CounterMappingRegion::MCDCBranchRegion);

auto ConditionID = Branch.MCDCParams.ID;
assert(ConditionID > 0 && "ConditionID should begin with 1");

if (ConditionIDs.contains(ConditionID) ||
ConditionID > DecisionRegion->MCDCParams.NumConditions)
return NotProcessed;

if (!this->dominates(Branch))
return NotProcessed;

assert(MCDCBranches.size() < DecisionRegion->MCDCParams.NumConditions);

// Put `ID=1` in front of `MCDCBranches` for convenience
// even if `MCDCBranches` is not topological.
if (ConditionID == 1)
MCDCBranches.insert(MCDCBranches.begin(), &Branch);
else
MCDCBranches.push_back(&Branch);

// Mark `ID` as `assigned`.
ConditionIDs.insert(ConditionID);

// `Completed` when `MCDCBranches` is full
return (MCDCBranches.size() == DecisionRegion->MCDCParams.NumConditions
? Completed
: Processed);
}

/// Record Expansion if it is relevant to this Decision.
/// Each `Expansion` may nest.
/// \returns true if recorded.
bool recordExpansion(const CounterMappingRegion &Expansion) {
if (!this->dominates(Expansion))
return false;

ExpandedFileIDs.insert(Expansion.ExpandedFileID);
return true;
}
};

private:
/// Decisions in progress
/// DecisionRecord is added for each MCDCDecisionRegion.
/// DecisionRecord is removed when Decision is completed.
SmallVector<DecisionRecord> Decisions;

public:
~MCDCDecisionRecorder() {
assert(Decisions.empty() && "All Decisions have not been resolved");
}

/// Register Region and start recording.
void registerDecision(const CounterMappingRegion &Decision) {
Decisions.emplace_back(Decision);
}

void recordExpansion(const CounterMappingRegion &Expansion) {
any_of(Decisions, [&Expansion](auto &Decision) {
return Decision.recordExpansion(Expansion);
});
}

using DecisionAndBranches =
std::pair<const CounterMappingRegion *, /// Decision
SmallVector<const CounterMappingRegion *> /// Branches
>;

/// Add MCDCBranchRegion to DecisionRecord.
/// \param Branch to be processed
/// \returns DecisionsAndBranches if DecisionRecord completed.
/// Or returns nullopt.
std::optional<DecisionAndBranches>
processBranch(const CounterMappingRegion &Branch) {
// Seek each Decision and apply Region to it.
for (auto DecisionIter = Decisions.begin(), DecisionEnd = Decisions.end();
DecisionIter != DecisionEnd; ++DecisionIter)
switch (DecisionIter->addBranch(Branch)) {
case DecisionRecord::NotProcessed:
continue;
case DecisionRecord::Processed:
return std::nullopt;
case DecisionRecord::Completed:
DecisionAndBranches Result =
std::make_pair(DecisionIter->DecisionRegion,
std::move(DecisionIter->MCDCBranches));
Decisions.erase(DecisionIter); // No longer used.
return Result;
}

llvm_unreachable("Branch not found in Decisions");
}
};

} // namespace

Error CoverageMapping::loadFunctionRecord(
const CoverageMappingRecord &Record,
IndexedInstrProfReader &ProfileReader) {
Expand Down Expand Up @@ -638,18 +793,13 @@ Error CoverageMapping::loadFunctionRecord(
Record.MappingRegions[0].Count.isZero() && Counts[0] > 0)
return Error::success();

unsigned NumConds = 0;
const CounterMappingRegion *MCDCDecision;
std::vector<const CounterMappingRegion *> MCDCBranches;

MCDCDecisionRecorder MCDCDecisions;
FunctionRecord Function(OrigFuncName, Record.Filenames);
for (const auto &Region : Record.MappingRegions) {
// If an MCDCDecisionRegion is seen, track the BranchRegions that follow
// it according to Region.NumConditions.
// MCDCDecisionRegion should be handled first since it overlaps with
// others inside.
if (Region.Kind == CounterMappingRegion::MCDCDecisionRegion) {
assert(NumConds == 0);
MCDCDecision = &Region;
NumConds = Region.MCDCParams.NumConditions;
MCDCDecisions.registerDecision(Region);
continue;
}
Expected<int64_t> ExecutionCount = Ctx.evaluate(Region.Count);
Expand All @@ -664,43 +814,47 @@ Error CoverageMapping::loadFunctionRecord(
}
Function.pushRegion(Region, *ExecutionCount, *AltExecutionCount);

// If a MCDCDecisionRegion was seen, store the BranchRegions that
// correspond to it in a vector, according to the number of conditions
// recorded for the region (tracked by NumConds).
if (NumConds > 0 && Region.Kind == CounterMappingRegion::MCDCBranchRegion) {
MCDCBranches.push_back(&Region);

// As we move through all of the MCDCBranchRegions that follow the
// MCDCDecisionRegion, decrement NumConds to make sure we account for
// them all before we calculate the bitmap of executed test vectors.
if (--NumConds == 0) {
// Evaluating the test vector bitmap for the decision region entails
// calculating precisely what bits are pertinent to this region alone.
// This is calculated based on the recorded offset into the global
// profile bitmap; the length is calculated based on the recorded
// number of conditions.
Expected<BitVector> ExecutedTestVectorBitmap =
Ctx.evaluateBitmap(MCDCDecision);
if (auto E = ExecutedTestVectorBitmap.takeError()) {
consumeError(std::move(E));
return Error::success();
}
// Record ExpansionRegion.
if (Region.Kind == CounterMappingRegion::ExpansionRegion) {
MCDCDecisions.recordExpansion(Region);
continue;
}

// Since the bitmap identifies the executed test vectors for an MC/DC
// DecisionRegion, all of the information is now available to process.
// This is where the bulk of the MC/DC progressing takes place.
Expected<MCDCRecord> Record = Ctx.evaluateMCDCRegion(
*MCDCDecision, *ExecutedTestVectorBitmap, MCDCBranches);
if (auto E = Record.takeError()) {
consumeError(std::move(E));
return Error::success();
}
// Do nothing unless MCDCBranchRegion.
if (Region.Kind != CounterMappingRegion::MCDCBranchRegion)
continue;

// Save the MC/DC Record so that it can be visualized later.
Function.pushMCDCRecord(*Record);
MCDCBranches.clear();
}
auto Result = MCDCDecisions.processBranch(Region);
if (!Result) // Any Decision doesn't complete.
continue;

auto MCDCDecision = Result->first;
auto &MCDCBranches = Result->second;

// Evaluating the test vector bitmap for the decision region entails
// calculating precisely what bits are pertinent to this region alone.
// This is calculated based on the recorded offset into the global
// profile bitmap; the length is calculated based on the recorded
// number of conditions.
Expected<BitVector> ExecutedTestVectorBitmap =
Ctx.evaluateBitmap(MCDCDecision);
if (auto E = ExecutedTestVectorBitmap.takeError()) {
consumeError(std::move(E));
return Error::success();
}

// Since the bitmap identifies the executed test vectors for an MC/DC
// DecisionRegion, all of the information is now available to process.
// This is where the bulk of the MC/DC progressing takes place.
Expected<MCDCRecord> Record = Ctx.evaluateMCDCRegion(
*MCDCDecision, *ExecutedTestVectorBitmap, MCDCBranches);
if (auto E = Record.takeError()) {
consumeError(std::move(E));
return Error::success();
}

// Save the MC/DC Record so that it can be visualized later.
Function.pushMCDCRecord(*Record);
}

// Don't create records for (filenames, function) pairs we've already seen.
Expand Down
20 changes: 20 additions & 0 deletions llvm/test/tools/llvm-cov/Inputs/mcdc-macro.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#define C c
#define D 1
#define E (C != a) && (C > a)
#define F E

void __attribute__((noinline)) func1(void) { return; }

void __attribute__((noinline)) func(int a, int b, int c) {
if (a && D && E || b)
func1();
if (b && D)
func1();
if (a && (b && C) || (D && F))
func1();
}

int main() {
func(2, 3, 3);
return 0;
}
Binary file added llvm/test/tools/llvm-cov/Inputs/mcdc-macro.o
Binary file not shown.
62 changes: 62 additions & 0 deletions llvm/test/tools/llvm-cov/Inputs/mcdc-macro.proftext
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
func
# Func Hash:
395201011017399473
# Num Counters:
22
# Counter Values:
1
1
0
0
1
1
1
1
1
1
1
1
1
1
0
1
1
1
0
0
0
0
# Num Bitmap Bytes:
$13
# Bitmap Byte Values:
0x0
0x0
0x0
0x20
0x8
0x0
0x20
0x0
0x0
0x0
0x0
0x0
0x0


func1
# Func Hash:
24
# Num Counters:
1
# Counter Values:
3

main
# Func Hash:
24
# Num Counters:
1
# Counter Values:
1

Loading
Loading