-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[Coverage] Let Decision
take account of expansions
#78969
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
Changes from 9 commits
5d9de08
1564424
f74dec7
425185c
2fda236
133bd01
2b6edff
9e0f60f
83a0ff8
200a32f
0e4b05b
c889c04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -582,6 +583,177 @@ 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) { | ||
// 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 if it is MCDCDecisionRegion. | ||
/// \param Region to be inspected | ||
/// \returns true if recording started. | ||
bool registerDecision(const CounterMappingRegion &Region) { | ||
if (Region.Kind != CounterMappingRegion::MCDCDecisionRegion) | ||
return false; | ||
|
||
// Start recording Region to create DecisionRecord | ||
Decisions.emplace_back(Region); | ||
return true; | ||
} | ||
|
||
using DecisionAndBranches = | ||
std::pair<const CounterMappingRegion *, /// Decision | ||
SmallVector<const CounterMappingRegion *> /// Branches | ||
>; | ||
|
||
/// If Region is ExpansionRegion, record it. | ||
/// If Region is MCDCBranchRegion, add it to DecisionRecord. | ||
/// \param Region to be inspected | ||
/// \returns DecisionsAndBranches if DecisionRecord completed. | ||
/// Or returns nullopt. | ||
std::optional<DecisionAndBranches> | ||
processRegion(const CounterMappingRegion &Region) { | ||
|
||
// Record ExpansionRegion. | ||
if (Region.Kind == CounterMappingRegion::ExpansionRegion) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's clearer to move This class then doesn't need to check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to make |
||
for (auto &Decision : reverse(Decisions)) { | ||
if (Decision.recordExpansion(Region)) | ||
break; | ||
} | ||
return std::nullopt; // It doesn't complete. | ||
} | ||
|
||
// Do nothing unless MCDCBranchRegion. | ||
if (Region.Kind != CounterMappingRegion::MCDCBranchRegion) | ||
return std::nullopt; | ||
|
||
// Seek each Decision and apply Region to it. | ||
for (auto DecisionIter = Decisions.begin(), DecisionEnd = Decisions.end(); | ||
DecisionIter != DecisionEnd; ++DecisionIter) | ||
switch (DecisionIter->addBranch(Region)) { | ||
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) { | ||
|
@@ -638,20 +810,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. | ||
if (Region.Kind == CounterMappingRegion::MCDCDecisionRegion) { | ||
assert(NumConds == 0); | ||
MCDCDecision = &Region; | ||
NumConds = Region.MCDCParams.NumConditions; | ||
// MCDCDecisionRegion should be handled first since it overlaps with | ||
// others inside. | ||
if (MCDCDecisions.registerDecision(Region)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it reads better if the I.e. we check different types of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did because of symmetry to |
||
continue; | ||
} | ||
Expected<int64_t> ExecutionCount = Ctx.evaluate(Region.Count); | ||
if (auto E = ExecutionCount.takeError()) { | ||
consumeError(std::move(E)); | ||
|
@@ -664,43 +829,37 @@ 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(); | ||
} | ||
auto Result = MCDCDecisions.processRegion(Region); | ||
if (!Result) // Any Decision doesn't complete. | ||
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(); | ||
} | ||
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(); | ||
} | ||
|
||
// Save the MC/DC Record so that it can be visualized later. | ||
Function.pushMCDCRecord(*Record); | ||
MCDCBranches.clear(); | ||
} | ||
// 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. | ||
|
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
main | ||
# Func Hash: | ||
24 | ||
# Num Counters: | ||
1 | ||
# Counter Values: | ||
1 | ||
|
||
foo | ||
# 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 | ||
|
||
|
||
bar | ||
# Func Hash: | ||
24 | ||
# Num Counters: | ||
1 | ||
# Counter Values: | ||
3 | ||
|
Uh oh!
There was an error while loading. Please reload this page.