diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h index c5c9740f25c2c..b39f262c3d976 100644 --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -550,6 +550,55 @@ struct MCDCRecord { } }; +namespace mcdc { +/// Compute TestVector Indices "TVIdx" from the Conds graph. +/// +/// Clang CodeGen handles the bitmap index based on TVIdx. +/// llvm-cov reconstructs conditions from TVIdx. +/// +/// For each leaf "The final decision", +/// - TVIdx should be unique. +/// - TVIdx has the Width. +/// - The width represents the number of possible paths. +/// - The minimum width is 1 "deterministic". +/// - The order of leaves are sorted by Width DESC. It expects +/// latter TVIdx(s) (with Width=1) could be pruned and altered to +/// other simple branch conditions. +/// +class TVIdxBuilder { +public: + struct MCDCNode { + int InCount = 0; /// Reference count; temporary use + int Width; /// Number of accumulated paths (>= 1) + ConditionIDs NextIDs; + }; + +#ifndef NDEBUG + /// This is no longer needed after the assignment. + /// It may be used in assert() for reconfirmation. + SmallVector SavedNodes; +#endif + + /// Output: Index for TestVectors bitmap (These are not CondIDs) + SmallVector> Indices; + + /// Output: The number of test vectors. + /// Error with HardMaxTVs if the number has exploded. + int NumTestVectors; + + /// Hard limit of test vectors + static constexpr auto HardMaxTVs = + std::numeric_limits::max(); + +public: + /// Calculate and assign Indices + /// \param NextIDs The list of {FalseID, TrueID} indexed by ID + /// The first element [0] should be the root node. + /// \param Offset Offset of index to final decisions. + TVIdxBuilder(const SmallVectorImpl &NextIDs, int Offset = 0); +}; +} // namespace mcdc + /// 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 8f9d1eadc2daf..334f5dce879e5 100644 --- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -223,9 +223,130 @@ Expected CounterMappingContext::evaluate(const Counter &C) const { return LastPoppedValue; } +mcdc::TVIdxBuilder::TVIdxBuilder(const SmallVectorImpl &NextIDs, + int Offset) + : Indices(NextIDs.size()) { + // Construct Nodes and set up each InCount + auto N = NextIDs.size(); + SmallVector Nodes(N); + for (unsigned ID = 0; ID < N; ++ID) { + for (unsigned C = 0; C < 2; ++C) { +#ifndef NDEBUG + Indices[ID][C] = INT_MIN; +#endif + auto NextID = NextIDs[ID][C]; + Nodes[ID].NextIDs[C] = NextID; + if (NextID >= 0) + ++Nodes[NextID].InCount; + } + } + + // 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()) { + auto IID = Q.begin(); + int ID = *IID; + Q.erase(IID); + auto &Node = Nodes[ID]; + assert(Node.Width > 0); + + for (unsigned I = 0; I < 2; ++I) { + auto NextID = Node.NextIDs[I]; + assert(NextID != 0 && "NextID should not point to the top"); + if (NextID < 0) { + // Decision + Decisions.emplace_back(-Node.Width, Ord++, ID, I); + assert(Ord == Decisions.size()); + continue; + } + + // Inter Node + auto &NextNode = Nodes[NextID]; + assert(NextNode.InCount > 0); + + // Assign Idx + assert(Indices[ID][I] == INT_MIN); + Indices[ID][I] = NextNode.Width; + auto NextWidth = int64_t(NextNode.Width) + Node.Width; + if (NextWidth > HardMaxTVs) { + NumTestVectors = HardMaxTVs; // Overflow + return; + } + NextNode.Width = NextWidth; + + // Ready if all incomings are processed. + // Or NextNode.Width hasn't been confirmed yet. + if (--NextNode.InCount == 0) + Q.push_back(NextID); + } + } + + llvm::sort(Decisions); + + // Assign TestVector Indices in Decision Nodes + int64_t CurIdx = 0; + for (auto [NegWidth, Ord, ID, C] : Decisions) { + int Width = -NegWidth; + assert(Nodes[ID].Width == Width); + assert(Nodes[ID].NextIDs[C] < 0); + assert(Indices[ID][C] == INT_MIN); + Indices[ID][C] = Offset + CurIdx; + CurIdx += Width; + if (CurIdx > HardMaxTVs) { + NumTestVectors = HardMaxTVs; // Overflow + return; + } + } + + assert(CurIdx < HardMaxTVs); + NumTestVectors = CurIdx; + +#ifndef NDEBUG + for (const auto &Idxs : Indices) + for (auto Idx : Idxs) + assert(Idx != INT_MIN); + SavedNodes = std::move(Nodes); +#endif +} + namespace { -class MCDCRecordProcessor { +/// Construct this->NextIDs with Branches for TVIdxBuilder to use it +/// before MCDCRecordProcessor(). +class NextIDsBuilder { +protected: + SmallVector NextIDs; + +public: + NextIDsBuilder(const ArrayRef Branches) + : NextIDs(Branches.size()) { +#ifndef NDEBUG + DenseSet SeenIDs; +#endif + for (const auto *Branch : Branches) { + const auto &BranchParams = Branch->getBranchParams(); + assert(BranchParams.ID >= 0 && "CondID isn't set"); + assert(SeenIDs.insert(BranchParams.ID).second && "Duplicate CondID"); + NextIDs[BranchParams.ID] = BranchParams.Conds; + } + assert(SeenIDs.size() == Branches.size()); + } +}; + +class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { /// A bitmap representing the executed test vectors for a boolean expression. /// Each index of the bitmap corresponds to a possible test vector. An index /// with a bit value of '1' indicates that the corresponding Test Vector @@ -243,9 +364,6 @@ class MCDCRecordProcessor { /// Total number of conditions in the boolean expression. unsigned NumConditions; - /// Mapping of a condition ID to its corresponding branch params. - llvm::DenseMap CondsMap; - /// Vector used to track whether a condition is constant folded. MCDCRecord::BoolVector Folded; @@ -256,13 +374,17 @@ class MCDCRecordProcessor { /// ExecutedTestVectorBitmap. MCDCRecord::TestVectors ExecVectors; +#ifndef NDEBUG + DenseSet TVIdxs; +#endif + public: MCDCRecordProcessor(const BitVector &Bitmap, const CounterMappingRegion &Region, ArrayRef Branches) - : Bitmap(Bitmap), Region(Region), - DecisionParams(Region.getDecisionParams()), Branches(Branches), - NumConditions(DecisionParams.NumConditions), + : NextIDsBuilder(Branches), TVIdxBuilder(this->NextIDs), Bitmap(Bitmap), + Region(Region), DecisionParams(Region.getDecisionParams()), + Branches(Branches), NumConditions(DecisionParams.NumConditions), Folded(NumConditions, false), IndependencePairs(NumConditions) {} private: @@ -270,7 +392,7 @@ class MCDCRecordProcessor { // each node. When a terminal node (ID == 0) is reached, fill in the value in // the truth table. void buildTestVector(MCDCRecord::TestVector &TV, mcdc::ConditionID ID, - unsigned Index) { + int TVIdx, unsigned Index) { assert((Index & (1 << ID)) == 0); for (auto MCDCCond : {MCDCRecord::MCDC_False, MCDCRecord::MCDC_True}) { @@ -278,12 +400,17 @@ class MCDCRecordProcessor { static_assert(MCDCRecord::MCDC_True == 1); Index |= MCDCCond << ID; TV[ID] = MCDCCond; - auto NextID = CondsMap[ID][MCDCCond]; + auto NextID = NextIDs[ID][MCDCCond]; + auto NextTVIdx = TVIdx + Indices[ID][MCDCCond]; + assert(NextID == SavedNodes[ID].NextIDs[MCDCCond]); if (NextID >= 0) { - buildTestVector(TV, NextID, Index); + buildTestVector(TV, NextID, NextTVIdx, Index); continue; } + assert(TVIdx < SavedNodes[ID].Width); + assert(TVIdxs.insert(NextTVIdx).second && "Duplicate TVIdx"); + if (!Bitmap[DecisionParams.BitmapIdx * CHAR_BIT + Index]) continue; @@ -304,9 +431,12 @@ class MCDCRecordProcessor { void findExecutedTestVectors() { // Walk the binary decision diagram to enumerate all possible test vectors. // We start at the root node (ID == 0) with all values being DontCare. + // `TVIdx` starts with 0 and is in the traversal. // `Index` encodes the bitmask of true values and is initially 0. MCDCRecord::TestVector TV(NumConditions, MCDCRecord::MCDC_DontCare); - buildTestVector(TV, 0, 0); + buildTestVector(TV, 0, 0, 0); + assert(TVIdxs.size() == unsigned(NumTestVectors) && + "TVIdxs wasn't fulfilled"); } // Find an independence pair for each condition: @@ -367,7 +497,6 @@ class MCDCRecordProcessor { // from being measured. for (const auto *B : Branches) { const auto &BranchParams = B->getBranchParams(); - CondsMap[BranchParams.ID] = BranchParams.Conds; PosToID[I] = BranchParams.ID; CondLoc[I] = B->startLoc(); Folded[I++] = (B->Count.isZero() && B->FalseCount.isZero()); diff --git a/llvm/unittests/ProfileData/CoverageMappingTest.cpp b/llvm/unittests/ProfileData/CoverageMappingTest.cpp index 425b3d10510af..f063a33205b30 100644 --- a/llvm/unittests/ProfileData/CoverageMappingTest.cpp +++ b/llvm/unittests/ProfileData/CoverageMappingTest.cpp @@ -16,6 +16,7 @@ #include "llvm/Testing/Support/SupportHelpers.h" #include "gtest/gtest.h" +#include #include #include @@ -1074,4 +1075,59 @@ TEST(CoverageMappingTest, filename_compilation_dir) { } } +TEST(CoverageMappingTest, TVIdxBuilder) { + // ((n0 && n3) || (n2 && n4) || (n1 && n5)) + static const std::array Branches = {{ + {2, 3}, + {-1, 5}, + {1, 4}, + {2, -1}, + {1, -1}, + {-1, -1}, + }}; + int Offset = 1000; + auto TheBuilder = mcdc::TVIdxBuilder( + SmallVector(ArrayRef(Branches)), Offset); + EXPECT_TRUE(TheBuilder.NumTestVectors < TheBuilder.HardMaxTVs); + EXPECT_EQ(TheBuilder.Indices.size(), 6u); + EXPECT_EQ(TheBuilder.NumTestVectors, 15); + + std::map Decisions; + for (unsigned I = 0; I < TheBuilder.Indices.size(); ++I) { + struct Rec { + int Width; + std::array Indices; + }; + static const std::array IndicesRefs = {{ + {1, {0, 0}}, + {4, {1000, 0}}, + {2, {0, 0}}, + {1, {1, 1014}}, + {2, {2, 1012}}, + {4, {1004, 1008}}, + }}; + EXPECT_EQ(TheBuilder.Indices[I], IndicesRefs[I].Indices); + +#ifndef NDEBUG + const auto &Node = TheBuilder.SavedNodes[I]; + EXPECT_EQ(Node.Width, IndicesRefs[I].Width); + for (int C = 0; C < 2; ++C) { + auto Index = TheBuilder.Indices[I][C]; + if (Node.NextIDs[C] < 0) + EXPECT_TRUE(Decisions.insert({Index, Node.Width}).second); + } +#endif + } + +#ifndef NDEBUG + int NextIdx = Offset; + for (const auto [Index, Width] : Decisions) { + EXPECT_EQ(Index, NextIdx); + NextIdx += Width; + } + // The sum of Width(s) is NumTVs. + EXPECT_EQ(NextIdx, Offset + TheBuilder.NumTestVectors); +#endif +} + } // end anonymous namespace