From d168e0cb85eb150caa7ab241f136c5a23f79ba38 Mon Sep 17 00:00:00 2001 From: NAKAMURA Takumi Date: Mon, 5 Feb 2024 00:33:40 +0900 Subject: [PATCH] Implement MCDCTVIdxBuilder and MCDCTestVectorBuilder (LLVM side) 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 --- .../ProfileData/Coverage/CoverageMapping.h | 24 ++ .../ProfileData/Coverage/CoverageMapping.cpp | 226 +++++++++++++----- llvm/test/tools/llvm-cov/mcdc-const.test | 28 +-- llvm/test/tools/llvm-cov/mcdc-general.test | 16 +- 4 files changed, 214 insertions(+), 80 deletions(-) diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h index 88ec60c7aa33c..62867275a8524 100644 --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -32,6 +32,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include #include #include #include @@ -557,6 +558,29 @@ struct MCDCRecord { } }; +class MCDCTVIdxBuilder { +public: + struct MCDCNode { + int InCount = 0; + unsigned Width; + struct { + int ID; + int Idx; + } Conds[2]; + }; + + SmallVector Nodes; + unsigned NumTestVectors; + +public: + using NodeIDs = std::tuple; + + MCDCTVIdxBuilder(std::function Fetcher); +}; + /// A Counter mapping context is used to connect the counters, expressions /// and the obtained counter values. class CounterMappingContext { diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp index 39e43f86eab5e..d3a60c664b9e9 100644 --- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -223,6 +223,171 @@ Expected CounterMappingContext::evaluate(const Counter &C) const { return LastPoppedValue; } +MCDCTVIdxBuilder::MCDCTVIdxBuilder( + std::function 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> + Decisions; + + // Traverse Nodes to assign Idx + SmallVector 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 TVIDs; +#endif + + class BranchProvider { + ArrayRef Branches; + unsigned BranchIdx = 0; + + public: + BranchProvider(ArrayRef Branches) + : Branches(Branches) {} + + std::function 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 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 @@ -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; @@ -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. @@ -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. diff --git a/llvm/test/tools/llvm-cov/mcdc-const.test b/llvm/test/tools/llvm-cov/mcdc-const.test index 0b2c9c98d5355..5424625cf6a6b 100644 --- a/llvm/test/tools/llvm-cov/mcdc-const.test +++ b/llvm/test/tools/llvm-cov/mcdc-const.test @@ -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% @@ -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 @@ -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 @@ -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 diff --git a/llvm/test/tools/llvm-cov/mcdc-general.test b/llvm/test/tools/llvm-cov/mcdc-general.test index 753036bedaf17..4b59ce59d638e 100644 --- a/llvm/test/tools/llvm-cov/mcdc-general.test +++ b/llvm/test/tools/llvm-cov/mcdc-general.test @@ -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: ------------------