Skip to content

Commit

Permalink
Implement MCDCTVIdxBuilder and MCDCTestVectorBuilder (LLVM side)
Browse files Browse the repository at this point in the history
This accept current version of profdata. The output might be different.

See also
https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798
  • Loading branch information
chapuni committed Feb 5, 2024
1 parent f035c01 commit d168e0c
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 80 deletions.
24 changes: 24 additions & 0 deletions llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <sstream>
Expand Down Expand Up @@ -557,6 +558,29 @@ struct MCDCRecord {
}
};

class MCDCTVIdxBuilder {
public:
struct MCDCNode {
int InCount = 0;
unsigned Width;
struct {
int ID;
int Idx;
} Conds[2];
};

SmallVector<MCDCNode> Nodes;
unsigned NumTestVectors;

public:
using NodeIDs = std::tuple<unsigned, // ID1 (ends with 0)
unsigned, // ID1 for False
unsigned // ID1 for True
>;

MCDCTVIdxBuilder(std::function<NodeIDs(bool TellSize)> Fetcher);
};

/// A Counter mapping context is used to connect the counters, expressions
/// and the obtained counter values.
class CounterMappingContext {
Expand Down
226 changes: 168 additions & 58 deletions llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,171 @@ Expected<int64_t> CounterMappingContext::evaluate(const Counter &C) const {
return LastPoppedValue;
}

MCDCTVIdxBuilder::MCDCTVIdxBuilder(
std::function<NodeIDs(bool TellSize)> Fetcher) {
// Build Nodes and set up each InCount
int MaxID = -1;
Nodes.resize(std::get<0>(Fetcher(true)));
while (true) {
auto [ID1, FalseID1, TrueID1] = Fetcher(false);
if (ID1 == 0)
break;
if (Nodes.size() < ID1)
Nodes.resize(ID1);
int ID = ID1 - 1;
MaxID = std::max(MaxID, ID);
auto &Node = Nodes[ID];
Node.Conds[0].ID = FalseID1 - 1;
Node.Conds[1].ID = TrueID1 - 1;
for (unsigned I = 0; I < 2; ++I) {
int NextID = Node.Conds[I].ID;
if (NextID >= 0)
++Nodes[NextID].InCount;
}
}

if (MaxID < 0)
return;

// Sort key ordered by <-Width, Ord>
SmallVector<std::tuple<int, /// -Width
unsigned, /// Ord
int, /// ID
unsigned /// Cond (0 or 1)
>>
Decisions;

// Traverse Nodes to assign Idx
SmallVector<int> Q;
assert(Nodes[0].InCount == 0);
Nodes[0].Width = 1;
Q.push_back(0);

unsigned Ord = 0;
while (!Q.empty()) {
int ID = *Q.begin();
Q.erase(Q.begin());
auto &Node = Nodes[ID];
assert(Node.Width > 0);

for (unsigned I = 0; I < 2; ++I) {
int NextID = Node.Conds[I].ID;
assert(NextID != 0);
if (NextID < 0) {
Decisions.emplace_back(-Node.Width, Ord++, ID, I);
assert(Ord == Decisions.size());
continue;
}

auto &NextNode = Nodes[NextID];
assert(NextNode.InCount > 0);
Node.Conds[I].Idx = NextNode.Width; // ???
NextNode.Width += Node.Width;
if (--NextNode.InCount == 0)
Q.push_back(NextID);
}
}

std::sort(Decisions.begin(), Decisions.end());

// Assign TestVector Index
unsigned CurIdx = 0;
for (auto [NegWidth, Ord, ID, C] : Decisions) {
unsigned Width = -NegWidth;
auto &Node = Nodes[ID];
assert(Node.Width == Width);
assert(Node.Conds[C].Idx == 0);
assert(Node.Conds[C].ID < 0);
Node.Conds[C].Idx = CurIdx;
CurIdx += Width;
}
NumTestVectors = CurIdx;
}

namespace {

class MCDCTestVectorBuilder : public MCDCTVIdxBuilder {
MCDCRecord::TestVectors TestVectors;
const BitVector &Bitmap;
unsigned BitmapIdx;
#ifndef NDEBUG
DenseSet<unsigned> TVIDs;
#endif

class BranchProvider {
ArrayRef<const CounterMappingRegion *> Branches;
unsigned BranchIdx = 0;

public:
BranchProvider(ArrayRef<const CounterMappingRegion *> Branches)
: Branches(Branches) {}

std::function<NodeIDs(bool)> getFetcher() {
return [this](bool TellSize) {
if (TellSize)
return NodeIDs(Branches.size(), 0, 0);
if (BranchIdx >= Branches.size())
return NodeIDs(0, 0, 0);
const auto *B = Branches[BranchIdx++];
return NodeIDs(B->MCDCParams.ID, B->MCDCParams.FalseID,
B->MCDCParams.TrueID);
};
}
};

public:
MCDCTestVectorBuilder(ArrayRef<const CounterMappingRegion *> Branches,
const BitVector &Bitmap, unsigned BitmapIdx)
: MCDCTVIdxBuilder(BranchProvider(Branches).getFetcher()), Bitmap(Bitmap),
BitmapIdx(BitmapIdx) {}

protected:
MCDCRecord::TestVector TempTV;

void buildTestVector(int ID = 0, unsigned TVIdx = 0, unsigned Index = 0) {
const auto &Node = Nodes[ID];

for (unsigned I = 0; I < 2; ++I) {
auto MCDCCond = (I ? MCDCRecord::MCDC_True : MCDCRecord::MCDC_False);
const auto &Cond = Node.Conds[I];
auto NextID = Cond.ID;
Index |= I << ID;
TempTV[ID] = MCDCCond;
if (NextID >= 0) {
buildTestVector(NextID, TVIdx + Cond.Idx, Index);
continue;
}

auto FinalTVIdx = Cond.Idx + TVIdx;
assert(TVIdx < Node.Width);
#ifndef NDEBUG
assert(!TVIDs.contains(FinalTVIdx));
TVIDs.insert(FinalTVIdx);
#endif

assert(BitmapIdx + Index < Bitmap.size() && "Bitmap overrun");
if (!Bitmap[BitmapIdx + Index])
continue;

TestVectors.push_back(TempTV);
TestVectors.back().push_back(MCDCCond);
}

// Reset back to DontCare.
TempTV[ID] = MCDCRecord::MCDC_DontCare;
}

public:
MCDCRecord::TestVectors findExecutedTestVectors() {
TempTV.resize(Nodes.size(), MCDCRecord::MCDC_DontCare);
buildTestVector();
assert(TVIDs.size() == NumTestVectors);
return std::move(TestVectors);
}
};

} // namespace

class MCDCRecordProcessor {
/// A bitmap representing the executed test vectors for a boolean expression.
/// Each index of the bitmap corresponds to a possible test vector. An index
Expand Down Expand Up @@ -251,9 +416,6 @@ class MCDCRecordProcessor {
/// Mapping of calculated MC/DC Independence Pairs for each condition.
MCDCRecord::TVPairMap IndependencePairs;

/// Total number of possible Test Vectors for the boolean expression.
MCDCRecord::TestVectors TestVectors;

/// Actual executed Test Vectors for the boolean expression, based on
/// ExecutedTestVectorBitmap.
MCDCRecord::TestVectors ExecVectors;
Expand All @@ -265,56 +427,9 @@ class MCDCRecordProcessor {
: Bitmap(Bitmap), Region(Region), Branches(Branches),
NumConditions(Region.MCDCParams.NumConditions),
BitmapIdx(Region.MCDCParams.BitmapIdx * CHAR_BIT),
Folded(NumConditions, false), IndependencePairs(NumConditions),
TestVectors((size_t)1 << NumConditions) {}
Folded(NumConditions, false), IndependencePairs(NumConditions) {}

private:
void recordTestVector(MCDCRecord::TestVector &TV, unsigned Index,
MCDCRecord::CondState Result) {
// Copy the completed test vector to the vector of testvectors.
TestVectors[Index] = TV;

// The final value (T,F) is equal to the last non-dontcare state on the
// path (in a short-circuiting system).
TestVectors[Index].push_back(Result);
}

// Walk the binary decision diagram and try assigning both false and true to
// each node. When a terminal node (ID == 0) is reached, fill in the value in
// the truth table.
void buildTestVector(MCDCRecord::TestVector &TV, unsigned ID,
unsigned Index) {
const CounterMappingRegion *Branch = Map[ID];

TV[ID - 1] = MCDCRecord::MCDC_False;
if (Branch->MCDCParams.FalseID > 0)
buildTestVector(TV, Branch->MCDCParams.FalseID, Index);
else
recordTestVector(TV, Index, MCDCRecord::MCDC_False);

Index |= 1 << (ID - 1);
TV[ID - 1] = MCDCRecord::MCDC_True;
if (Branch->MCDCParams.TrueID > 0)
buildTestVector(TV, Branch->MCDCParams.TrueID, Index);
else
recordTestVector(TV, Index, MCDCRecord::MCDC_True);

// Reset back to DontCare.
TV[ID - 1] = MCDCRecord::MCDC_DontCare;
}

/// Walk the bits in the bitmap. A bit set to '1' indicates that the test
/// vector at the corresponding index was executed during a test run.
void findExecutedTestVectors() {
for (unsigned Idx = 0; Idx < (1u << NumConditions); ++Idx) {
assert(BitmapIdx + Idx < Bitmap.size() && "Bitmap overrun");
if (Bitmap[BitmapIdx + Idx] == 0)
continue;
assert(!TestVectors[Idx].empty() && "Test Vector doesn't exist.");
ExecVectors.push_back(TestVectors[Idx]);
}
}

// 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 @@ -378,14 +493,9 @@ class MCDCRecordProcessor {
Folded[I++] = (B->Count.isZero() && B->FalseCount.isZero());
}

// Walk the binary decision diagram to enumerate all possible test vectors.
// We start at the root node (ID == 1) with all values being DontCare.
// `Index` encodes the bitmask of true values and is initially 0.
MCDCRecord::TestVector TV(NumConditions, MCDCRecord::MCDC_DontCare);
buildTestVector(TV, 1, 0);

// Using Profile Bitmap from runtime, mark the executed test vectors.
findExecutedTestVectors();
ExecVectors = MCDCTestVectorBuilder(Branches, Bitmap, BitmapIdx)
.findExecutedTestVectors();

// Compare executed test vectors against each other to find an independence
// pairs for each condition. This processing takes the most time.
Expand Down
28 changes: 14 additions & 14 deletions llvm/test/tools/llvm-cov/mcdc-const.test
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
// CHECKFULLCASE: | C1-Pair: constant folded
// CHECKFULLCASE-NEXT: | C2-Pair: not covered
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
// CHECKFULLCASE: | 1 { T, C = T }
// CHECKFULLCASE-NEXT: | 2 { F, C = T }
// CHECKFULLCASE: | 1 { F, C = T }
// CHECKFULLCASE-NEXT: | 2 { T, C = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: constant folded
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
Expand Down Expand Up @@ -106,8 +106,8 @@
// CHECKFULLCASE-NEXT: | C2-Pair: not covered
// CHECKFULLCASE-NEXT: | C3-Pair: not covered
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
// CHECKFULLCASE: | 1 { T, C, - = T }
// CHECKFULLCASE-NEXT: | 2 { F, C, - = T }
// CHECKFULLCASE: | 1 { F, C, - = T }
// CHECKFULLCASE-NEXT: | 2 { T, C, - = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: constant folded
// CHECKFULLCASE-NEXT: | C3-Pair: not covered
Expand All @@ -118,8 +118,8 @@
// CHECKFULLCASE-NEXT: | C2-Pair: not covered
// CHECKFULLCASE-NEXT: | C3-Pair: not covered
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
// CHECKFULLCASE: | 1 { T, C, - = T }
// CHECKFULLCASE-NEXT: | 2 { F, C, T = T }
// CHECKFULLCASE: | 1 { F, C, T = T }
// CHECKFULLCASE-NEXT: | 2 { T, C, - = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: constant folded
// CHECKFULLCASE-NEXT: | C3-Pair: not covered
Expand Down Expand Up @@ -151,26 +151,26 @@
// CHECKFULLCASE-NEXT: | C2-Pair: constant folded
// CHECKFULLCASE-NEXT: | C3-Pair: covered: (2,3)
// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00%
// CHECKFULLCASE: | 1 { T, -, C = T }
// CHECKFULLCASE-NEXT: | 2 { F, T, C = T }
// CHECKFULLCASE: | 1 { F, T, C = T }
// CHECKFULLCASE-NEXT: | 2 { T, -, C = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: not covered
// CHECKFULLCASE-NEXT: | C3-Pair: constant folded
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
// CHECKFULLCASE: | 1 { T, C, - = T }
// CHECKFULLCASE-NEXT: | 2 { F, C, - = T }
// CHECKFULLCASE: | 1 { F, C, - = T }
// CHECKFULLCASE-NEXT: | 2 { T, C, - = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: constant folded
// CHECKFULLCASE-NEXT: | C3-Pair: not covered
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
// CHECKFULLCASE: | 1 { T, -, C = T }
// CHECKFULLCASE-NEXT: | 2 { F, T, C = T }
// CHECKFULLCASE: | 1 { F, T, C = T }
// CHECKFULLCASE-NEXT: | 2 { T, -, C = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: not covered
// CHECKFULLCASE-NEXT: | C3-Pair: constant folded
// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00%
// CHECKFULLCASE: | 1 { T, C, - = T }
// CHECKFULLCASE-NEXT: | 2 { F, C, T = T }
// CHECKFULLCASE: | 1 { F, C, T = T }
// CHECKFULLCASE-NEXT: | 2 { T, C, - = T }
// CHECKFULLCASE: | C1-Pair: not covered
// CHECKFULLCASE-NEXT: | C2-Pair: constant folded
// CHECKFULLCASE-NEXT: | C3-Pair: not covered
Expand Down
16 changes: 8 additions & 8 deletions llvm/test/tools/llvm-cov/mcdc-general.test
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@
// CHECK-NEXT: |
// CHECK-NEXT: | C1, C2, C3, C4 Result
// CHECK-NEXT: | 1 { F, -, F, - = F }
// CHECK-NEXT: | 2 { T, F, F, - = F }
// CHECK-NEXT: | 3 { F, -, T, F = F }
// CHECK-NEXT: | 2 { F, -, T, F = F }
// CHECK-NEXT: | 3 { T, F, F, - = F }
// CHECK-NEXT: | 4 { T, F, T, F = F }
// CHECK-NEXT: | 5 { T, T, -, - = T }
// CHECK-NEXT: | 6 { T, F, T, T = T }
// CHECK-NEXT: | 5 { T, F, T, T = T }
// CHECK-NEXT: | 6 { T, T, -, - = T }
// CHECK-NEXT: |
// CHECK-NEXT: | C1-Pair: covered: (1,5)
// CHECK-NEXT: | C2-Pair: covered: (2,5)
// CHECK-NEXT: | C3-Pair: covered: (2,6)
// CHECK-NEXT: | C4-Pair: covered: (4,6)
// CHECK-NEXT: | C1-Pair: covered: (1,6)
// CHECK-NEXT: | C2-Pair: covered: (3,6)
// CHECK-NEXT: | C3-Pair: covered: (3,5)
// CHECK-NEXT: | C4-Pair: covered: (4,5)
// CHECK-NEXT: | MC/DC Coverage for Decision: 100.00%
// CHECK-NEXT: |
// CHECK-NEXT: ------------------
Expand Down

0 comments on commit d168e0c

Please sign in to comment.