Skip to content

Commit c087beb

Browse files
authored
Introduce mcdc::TVIdxBuilder (LLVM side, NFC) (#80676)
This is a preparation of incoming Clang changes (#82448) and just checks `TVIdx` is calculated correctly. NFC. `TVIdxBuilder` calculates deterministic Indices for each Condition Node. It is used for `clang` to emit `TestVector` indices (aka ID) and for `llvm-cov` to reconstruct `TestVectors`. This includes the unittest `CoverageMappingTest.TVIdxBuilder`. See also https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798
1 parent 8be39b3 commit c087beb

File tree

3 files changed

+246
-12
lines changed

3 files changed

+246
-12
lines changed

llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,55 @@ struct MCDCRecord {
550550
}
551551
};
552552

553+
namespace mcdc {
554+
/// Compute TestVector Indices "TVIdx" from the Conds graph.
555+
///
556+
/// Clang CodeGen handles the bitmap index based on TVIdx.
557+
/// llvm-cov reconstructs conditions from TVIdx.
558+
///
559+
/// For each leaf "The final decision",
560+
/// - TVIdx should be unique.
561+
/// - TVIdx has the Width.
562+
/// - The width represents the number of possible paths.
563+
/// - The minimum width is 1 "deterministic".
564+
/// - The order of leaves are sorted by Width DESC. It expects
565+
/// latter TVIdx(s) (with Width=1) could be pruned and altered to
566+
/// other simple branch conditions.
567+
///
568+
class TVIdxBuilder {
569+
public:
570+
struct MCDCNode {
571+
int InCount = 0; /// Reference count; temporary use
572+
int Width; /// Number of accumulated paths (>= 1)
573+
ConditionIDs NextIDs;
574+
};
575+
576+
#ifndef NDEBUG
577+
/// This is no longer needed after the assignment.
578+
/// It may be used in assert() for reconfirmation.
579+
SmallVector<MCDCNode> SavedNodes;
580+
#endif
581+
582+
/// Output: Index for TestVectors bitmap (These are not CondIDs)
583+
SmallVector<std::array<int, 2>> Indices;
584+
585+
/// Output: The number of test vectors.
586+
/// Error with HardMaxTVs if the number has exploded.
587+
int NumTestVectors;
588+
589+
/// Hard limit of test vectors
590+
static constexpr auto HardMaxTVs =
591+
std::numeric_limits<decltype(NumTestVectors)>::max();
592+
593+
public:
594+
/// Calculate and assign Indices
595+
/// \param NextIDs The list of {FalseID, TrueID} indexed by ID
596+
/// The first element [0] should be the root node.
597+
/// \param Offset Offset of index to final decisions.
598+
TVIdxBuilder(const SmallVectorImpl<ConditionIDs> &NextIDs, int Offset = 0);
599+
};
600+
} // namespace mcdc
601+
553602
/// A Counter mapping context is used to connect the counters, expressions
554603
/// and the obtained counter values.
555604
class CounterMappingContext {

llvm/lib/ProfileData/Coverage/CoverageMapping.cpp

Lines changed: 141 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,130 @@ Expected<int64_t> CounterMappingContext::evaluate(const Counter &C) const {
223223
return LastPoppedValue;
224224
}
225225

226+
mcdc::TVIdxBuilder::TVIdxBuilder(const SmallVectorImpl<ConditionIDs> &NextIDs,
227+
int Offset)
228+
: Indices(NextIDs.size()) {
229+
// Construct Nodes and set up each InCount
230+
auto N = NextIDs.size();
231+
SmallVector<MCDCNode> Nodes(N);
232+
for (unsigned ID = 0; ID < N; ++ID) {
233+
for (unsigned C = 0; C < 2; ++C) {
234+
#ifndef NDEBUG
235+
Indices[ID][C] = INT_MIN;
236+
#endif
237+
auto NextID = NextIDs[ID][C];
238+
Nodes[ID].NextIDs[C] = NextID;
239+
if (NextID >= 0)
240+
++Nodes[NextID].InCount;
241+
}
242+
}
243+
244+
// Sort key ordered by <-Width, Ord>
245+
SmallVector<std::tuple<int, /// -Width
246+
unsigned, /// Ord
247+
int, /// ID
248+
unsigned /// Cond (0 or 1)
249+
>>
250+
Decisions;
251+
252+
// Traverse Nodes to assign Idx
253+
SmallVector<int> Q;
254+
assert(Nodes[0].InCount == 0);
255+
Nodes[0].Width = 1;
256+
Q.push_back(0);
257+
258+
unsigned Ord = 0;
259+
while (!Q.empty()) {
260+
auto IID = Q.begin();
261+
int ID = *IID;
262+
Q.erase(IID);
263+
auto &Node = Nodes[ID];
264+
assert(Node.Width > 0);
265+
266+
for (unsigned I = 0; I < 2; ++I) {
267+
auto NextID = Node.NextIDs[I];
268+
assert(NextID != 0 && "NextID should not point to the top");
269+
if (NextID < 0) {
270+
// Decision
271+
Decisions.emplace_back(-Node.Width, Ord++, ID, I);
272+
assert(Ord == Decisions.size());
273+
continue;
274+
}
275+
276+
// Inter Node
277+
auto &NextNode = Nodes[NextID];
278+
assert(NextNode.InCount > 0);
279+
280+
// Assign Idx
281+
assert(Indices[ID][I] == INT_MIN);
282+
Indices[ID][I] = NextNode.Width;
283+
auto NextWidth = int64_t(NextNode.Width) + Node.Width;
284+
if (NextWidth > HardMaxTVs) {
285+
NumTestVectors = HardMaxTVs; // Overflow
286+
return;
287+
}
288+
NextNode.Width = NextWidth;
289+
290+
// Ready if all incomings are processed.
291+
// Or NextNode.Width hasn't been confirmed yet.
292+
if (--NextNode.InCount == 0)
293+
Q.push_back(NextID);
294+
}
295+
}
296+
297+
llvm::sort(Decisions);
298+
299+
// Assign TestVector Indices in Decision Nodes
300+
int64_t CurIdx = 0;
301+
for (auto [NegWidth, Ord, ID, C] : Decisions) {
302+
int Width = -NegWidth;
303+
assert(Nodes[ID].Width == Width);
304+
assert(Nodes[ID].NextIDs[C] < 0);
305+
assert(Indices[ID][C] == INT_MIN);
306+
Indices[ID][C] = Offset + CurIdx;
307+
CurIdx += Width;
308+
if (CurIdx > HardMaxTVs) {
309+
NumTestVectors = HardMaxTVs; // Overflow
310+
return;
311+
}
312+
}
313+
314+
assert(CurIdx < HardMaxTVs);
315+
NumTestVectors = CurIdx;
316+
317+
#ifndef NDEBUG
318+
for (const auto &Idxs : Indices)
319+
for (auto Idx : Idxs)
320+
assert(Idx != INT_MIN);
321+
SavedNodes = std::move(Nodes);
322+
#endif
323+
}
324+
226325
namespace {
227326

228-
class MCDCRecordProcessor {
327+
/// Construct this->NextIDs with Branches for TVIdxBuilder to use it
328+
/// before MCDCRecordProcessor().
329+
class NextIDsBuilder {
330+
protected:
331+
SmallVector<mcdc::ConditionIDs> NextIDs;
332+
333+
public:
334+
NextIDsBuilder(const ArrayRef<const CounterMappingRegion *> Branches)
335+
: NextIDs(Branches.size()) {
336+
#ifndef NDEBUG
337+
DenseSet<mcdc::ConditionID> SeenIDs;
338+
#endif
339+
for (const auto *Branch : Branches) {
340+
const auto &BranchParams = Branch->getBranchParams();
341+
assert(BranchParams.ID >= 0 && "CondID isn't set");
342+
assert(SeenIDs.insert(BranchParams.ID).second && "Duplicate CondID");
343+
NextIDs[BranchParams.ID] = BranchParams.Conds;
344+
}
345+
assert(SeenIDs.size() == Branches.size());
346+
}
347+
};
348+
349+
class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder {
229350
/// A bitmap representing the executed test vectors for a boolean expression.
230351
/// Each index of the bitmap corresponds to a possible test vector. An index
231352
/// with a bit value of '1' indicates that the corresponding Test Vector
@@ -243,9 +364,6 @@ class MCDCRecordProcessor {
243364
/// Total number of conditions in the boolean expression.
244365
unsigned NumConditions;
245366

246-
/// Mapping of a condition ID to its corresponding branch params.
247-
llvm::DenseMap<unsigned, mcdc::ConditionIDs> CondsMap;
248-
249367
/// Vector used to track whether a condition is constant folded.
250368
MCDCRecord::BoolVector Folded;
251369

@@ -256,34 +374,43 @@ class MCDCRecordProcessor {
256374
/// ExecutedTestVectorBitmap.
257375
MCDCRecord::TestVectors ExecVectors;
258376

377+
#ifndef NDEBUG
378+
DenseSet<unsigned> TVIdxs;
379+
#endif
380+
259381
public:
260382
MCDCRecordProcessor(const BitVector &Bitmap,
261383
const CounterMappingRegion &Region,
262384
ArrayRef<const CounterMappingRegion *> Branches)
263-
: Bitmap(Bitmap), Region(Region),
264-
DecisionParams(Region.getDecisionParams()), Branches(Branches),
265-
NumConditions(DecisionParams.NumConditions),
385+
: NextIDsBuilder(Branches), TVIdxBuilder(this->NextIDs), Bitmap(Bitmap),
386+
Region(Region), DecisionParams(Region.getDecisionParams()),
387+
Branches(Branches), NumConditions(DecisionParams.NumConditions),
266388
Folded(NumConditions, false), IndependencePairs(NumConditions) {}
267389

268390
private:
269391
// Walk the binary decision diagram and try assigning both false and true to
270392
// each node. When a terminal node (ID == 0) is reached, fill in the value in
271393
// the truth table.
272394
void buildTestVector(MCDCRecord::TestVector &TV, mcdc::ConditionID ID,
273-
unsigned Index) {
395+
int TVIdx, unsigned Index) {
274396
assert((Index & (1 << ID)) == 0);
275397

276398
for (auto MCDCCond : {MCDCRecord::MCDC_False, MCDCRecord::MCDC_True}) {
277399
static_assert(MCDCRecord::MCDC_False == 0);
278400
static_assert(MCDCRecord::MCDC_True == 1);
279401
Index |= MCDCCond << ID;
280402
TV[ID] = MCDCCond;
281-
auto NextID = CondsMap[ID][MCDCCond];
403+
auto NextID = NextIDs[ID][MCDCCond];
404+
auto NextTVIdx = TVIdx + Indices[ID][MCDCCond];
405+
assert(NextID == SavedNodes[ID].NextIDs[MCDCCond]);
282406
if (NextID >= 0) {
283-
buildTestVector(TV, NextID, Index);
407+
buildTestVector(TV, NextID, NextTVIdx, Index);
284408
continue;
285409
}
286410

411+
assert(TVIdx < SavedNodes[ID].Width);
412+
assert(TVIdxs.insert(NextTVIdx).second && "Duplicate TVIdx");
413+
287414
if (!Bitmap[DecisionParams.BitmapIdx * CHAR_BIT + Index])
288415
continue;
289416

@@ -304,9 +431,12 @@ class MCDCRecordProcessor {
304431
void findExecutedTestVectors() {
305432
// Walk the binary decision diagram to enumerate all possible test vectors.
306433
// We start at the root node (ID == 0) with all values being DontCare.
434+
// `TVIdx` starts with 0 and is in the traversal.
307435
// `Index` encodes the bitmask of true values and is initially 0.
308436
MCDCRecord::TestVector TV(NumConditions, MCDCRecord::MCDC_DontCare);
309-
buildTestVector(TV, 0, 0);
437+
buildTestVector(TV, 0, 0, 0);
438+
assert(TVIdxs.size() == unsigned(NumTestVectors) &&
439+
"TVIdxs wasn't fulfilled");
310440
}
311441

312442
// Find an independence pair for each condition:
@@ -367,7 +497,6 @@ class MCDCRecordProcessor {
367497
// from being measured.
368498
for (const auto *B : Branches) {
369499
const auto &BranchParams = B->getBranchParams();
370-
CondsMap[BranchParams.ID] = BranchParams.Conds;
371500
PosToID[I] = BranchParams.ID;
372501
CondLoc[I] = B->startLoc();
373502
Folded[I++] = (B->Count.isZero() && B->FalseCount.isZero());

llvm/unittests/ProfileData/CoverageMappingTest.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "llvm/Testing/Support/SupportHelpers.h"
1717
#include "gtest/gtest.h"
1818

19+
#include <map>
1920
#include <ostream>
2021
#include <utility>
2122

@@ -1074,4 +1075,59 @@ TEST(CoverageMappingTest, filename_compilation_dir) {
10741075
}
10751076
}
10761077

1078+
TEST(CoverageMappingTest, TVIdxBuilder) {
1079+
// ((n0 && n3) || (n2 && n4) || (n1 && n5))
1080+
static const std::array<mcdc::ConditionIDs, 6> Branches = {{
1081+
{2, 3},
1082+
{-1, 5},
1083+
{1, 4},
1084+
{2, -1},
1085+
{1, -1},
1086+
{-1, -1},
1087+
}};
1088+
int Offset = 1000;
1089+
auto TheBuilder = mcdc::TVIdxBuilder(
1090+
SmallVector<mcdc::ConditionIDs>(ArrayRef(Branches)), Offset);
1091+
EXPECT_TRUE(TheBuilder.NumTestVectors < TheBuilder.HardMaxTVs);
1092+
EXPECT_EQ(TheBuilder.Indices.size(), 6u);
1093+
EXPECT_EQ(TheBuilder.NumTestVectors, 15);
1094+
1095+
std::map<int, int> Decisions;
1096+
for (unsigned I = 0; I < TheBuilder.Indices.size(); ++I) {
1097+
struct Rec {
1098+
int Width;
1099+
std::array<int, 2> Indices;
1100+
};
1101+
static const std::array<Rec, 6> IndicesRefs = {{
1102+
{1, {0, 0}},
1103+
{4, {1000, 0}},
1104+
{2, {0, 0}},
1105+
{1, {1, 1014}},
1106+
{2, {2, 1012}},
1107+
{4, {1004, 1008}},
1108+
}};
1109+
EXPECT_EQ(TheBuilder.Indices[I], IndicesRefs[I].Indices);
1110+
1111+
#ifndef NDEBUG
1112+
const auto &Node = TheBuilder.SavedNodes[I];
1113+
EXPECT_EQ(Node.Width, IndicesRefs[I].Width);
1114+
for (int C = 0; C < 2; ++C) {
1115+
auto Index = TheBuilder.Indices[I][C];
1116+
if (Node.NextIDs[C] < 0)
1117+
EXPECT_TRUE(Decisions.insert({Index, Node.Width}).second);
1118+
}
1119+
#endif
1120+
}
1121+
1122+
#ifndef NDEBUG
1123+
int NextIdx = Offset;
1124+
for (const auto [Index, Width] : Decisions) {
1125+
EXPECT_EQ(Index, NextIdx);
1126+
NextIdx += Width;
1127+
}
1128+
// The sum of Width(s) is NumTVs.
1129+
EXPECT_EQ(NextIdx, Offset + TheBuilder.NumTestVectors);
1130+
#endif
1131+
}
1132+
10771133
} // end anonymous namespace

0 commit comments

Comments
 (0)