diff --git a/llvm/include/llvm/CodeGenData/OutlinedHashTree.h b/llvm/include/llvm/CodeGenData/OutlinedHashTree.h new file mode 100644 index 0000000000000..2c8a9288f8a8c --- /dev/null +++ b/llvm/include/llvm/CodeGenData/OutlinedHashTree.h @@ -0,0 +1,99 @@ +//===- OutlinedHashTree.h --------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines the OutlinedHashTree class. It contains sequences of stable +// hash values of instructions that have been outlined. This OutlinedHashTree +// can be used to track the outlined instruction sequences across modules. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CODEGENDATA_OUTLINEDHASHTREE_H +#define LLVM_CODEGENDATA_OUTLINEDHASHTREE_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StableHashing.h" +#include "llvm/ObjectYAML/YAML.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include + +namespace llvm { + +/// A HashNode is an entry in an OutlinedHashTree, holding a hash value +/// and a collection of Successors (other HashNodes). If a HashNode has +/// a positive terminal value (Terminals > 0), it signifies the end of +/// a hash sequence with that occurrence count. +struct HashNode { + /// The hash value of the node. + stable_hash Hash = 0; + /// The number of terminals in the sequence ending at this node. + std::optional Terminals; + /// The successors of this node. + /// We don't use DenseMap as a stable_hash value can be tombstone. + std::unordered_map> Successors; +}; + +class OutlinedHashTree { + + using EdgeCallbackFn = + std::function; + using NodeCallbackFn = std::function; + + using HashSequence = SmallVector; + using HashSequencePair = std::pair; + +public: + /// Walks every edge and node in the OutlinedHashTree and calls CallbackEdge + /// for the edges and CallbackNode for the nodes with the stable_hash for + /// the source and the stable_hash of the sink for an edge. These generic + /// callbacks can be used to traverse a OutlinedHashTree for the purpose of + /// print debugging or serializing it. + void walkGraph(NodeCallbackFn CallbackNode, + EdgeCallbackFn CallbackEdge = nullptr, + bool SortedWalk = false) const; + + /// Release all hash nodes except the root hash node. + void clear() { + assert(getRoot()->Hash == 0 && !getRoot()->Terminals); + getRoot()->Successors.clear(); + } + + /// \returns true if the hash tree has only the root node. + bool empty() { return size() == 1; } + + /// \returns the size of a OutlinedHashTree by traversing it. If + /// \p GetTerminalCountOnly is true, it only counts the terminal nodes + /// (meaning it returns the the number of hash sequences in the + /// OutlinedHashTree). + size_t size(bool GetTerminalCountOnly = false) const; + + /// \returns the depth of a OutlinedHashTree by traversing it. + size_t depth() const; + + /// \returns the root hash node of a OutlinedHashTree. + const HashNode *getRoot() const { return &Root; } + HashNode *getRoot() { return &Root; } + + /// Inserts a \p Sequence into the this tree. The last node in the sequence + /// will increase Terminals. + void insert(const HashSequencePair &SequencePair); + + /// Merge a \p OtherTree into this Tree. + void merge(const OutlinedHashTree *OtherTree); + + /// \returns the matching count if \p Sequence exists in the OutlinedHashTree. + std::optional find(const HashSequence &Sequence) const; + +private: + HashNode Root; +}; + +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/CodeGenData/OutlinedHashTreeRecord.h b/llvm/include/llvm/CodeGenData/OutlinedHashTreeRecord.h new file mode 100644 index 0000000000000..de397c9ca5e70 --- /dev/null +++ b/llvm/include/llvm/CodeGenData/OutlinedHashTreeRecord.h @@ -0,0 +1,75 @@ +//===- OutlinedHashTreeRecord.h --------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines the OutlinedHashTreeRecord class. This class holds the outlined +// hash tree for both serialization and deserialization processes. It utilizes +// two data formats for serialization: raw binary data and YAML. +// These two formats can be used interchangeably. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CODEGENDATA_OUTLINEDHASHTREERECORD_H +#define LLVM_CODEGENDATA_OUTLINEDHASHTREERECORD_H + +#include "llvm/CodeGenData/OutlinedHashTree.h" + +namespace llvm { + +/// HashNodeStable is the serialized, stable, and compact representation +/// of a HashNode. +struct HashNodeStable { + llvm::yaml::Hex64 Hash; + unsigned Terminals; + std::vector SuccessorIds; +}; + +using IdHashNodeStableMapTy = std::map; +using IdHashNodeMapTy = DenseMap; +using HashNodeIdMapTy = DenseMap; + +struct OutlinedHashTreeRecord { + std::unique_ptr HashTree; + + OutlinedHashTreeRecord() { HashTree = std::make_unique(); } + OutlinedHashTreeRecord(std::unique_ptr HashTree) + : HashTree(std::move(HashTree)) {}; + + /// Serialize the outlined hash tree to a raw_ostream. + void serialize(raw_ostream &OS) const; + /// Deserialize the outlined hash tree from a raw_ostream. + void deserialize(const unsigned char *&Ptr); + /// Serialize the outlined hash tree to a YAML stream. + void serializeYAML(yaml::Output &YOS) const; + /// Deserialize the outlined hash tree from a YAML stream. + void deserializeYAML(yaml::Input &YIS); + + /// Merge the other outlined hash tree into this one. + void merge(const OutlinedHashTreeRecord &Other) { + HashTree->merge(Other.HashTree.get()); + } + + /// \returns true if the outlined hash tree is empty. + bool empty() const { return HashTree->empty(); } + + /// Print the outlined hash tree in a YAML format. + void print(raw_ostream &OS = llvm::errs()) const { + yaml::Output YOS(OS); + serializeYAML(YOS); + } + +private: + /// Convert the outlined hash tree to stable data. + void convertToStableData(IdHashNodeStableMapTy &IdNodeStableMap) const; + + /// Convert the stable data back to the outlined hash tree. + void convertFromStableData(const IdHashNodeStableMapTy &IdNodeStableMap); +}; + +} // end namespace llvm + +#endif // LLVM_CODEGENDATA_OUTLINEDHASHTREERECORD_H diff --git a/llvm/lib/CMakeLists.txt b/llvm/lib/CMakeLists.txt index 08f198679551d..638c3bd6f90f5 100644 --- a/llvm/lib/CMakeLists.txt +++ b/llvm/lib/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(InterfaceStub) add_subdirectory(IRPrinter) add_subdirectory(IRReader) add_subdirectory(CodeGen) +add_subdirectory(CodeGenData) add_subdirectory(CodeGenTypes) add_subdirectory(BinaryFormat) add_subdirectory(Bitcode) diff --git a/llvm/lib/CodeGenData/CMakeLists.txt b/llvm/lib/CodeGenData/CMakeLists.txt new file mode 100644 index 0000000000000..3ba90f96cc86d --- /dev/null +++ b/llvm/lib/CodeGenData/CMakeLists.txt @@ -0,0 +1,14 @@ +add_llvm_component_library(LLVMCodeGenData + OutlinedHashTree.cpp + OutlinedHashTreeRecord.cpp + + ADDITIONAL_HEADER_DIRS + ${LLVM_MAIN_INCLUDE_DIR}/llvm/CodeGenData + + DEPENDS + intrinsics_gen + + LINK_COMPONENTS + Core + Support + ) diff --git a/llvm/lib/CodeGenData/OutlinedHashTree.cpp b/llvm/lib/CodeGenData/OutlinedHashTree.cpp new file mode 100644 index 0000000000000..d64098098de62 --- /dev/null +++ b/llvm/lib/CodeGenData/OutlinedHashTree.cpp @@ -0,0 +1,129 @@ +//===-- OutlinedHashTree.cpp ----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// An OutlinedHashTree is a Trie that contains sequences of stable hash values +// of instructions that have been outlined. This OutlinedHashTree can be used +// to understand the outlined instruction sequences collected across modules. +// +//===----------------------------------------------------------------------===// + +#include "llvm/CodeGenData/OutlinedHashTree.h" + +#define DEBUG_TYPE "outlined-hash-tree" + +using namespace llvm; + +void OutlinedHashTree::walkGraph(NodeCallbackFn CallbackNode, + EdgeCallbackFn CallbackEdge, + bool SortedWalk) const { + SmallVector Stack; + Stack.emplace_back(getRoot()); + + while (!Stack.empty()) { + const auto *Current = Stack.pop_back_val(); + if (CallbackNode) + CallbackNode(Current); + + auto HandleNext = [&](const HashNode *Next) { + if (CallbackEdge) + CallbackEdge(Current, Next); + Stack.emplace_back(Next); + }; + if (SortedWalk) { + SmallVector> SortedSuccessors; + for (const auto &[Hash, Successor] : Current->Successors) + SortedSuccessors.emplace_back(Hash, Successor.get()); + llvm::sort(SortedSuccessors); + for (const auto &P : SortedSuccessors) + HandleNext(P.second); + } else { + for (const auto &P : Current->Successors) + HandleNext(P.second.get()); + } + } +} + +size_t OutlinedHashTree::size(bool GetTerminalCountOnly) const { + size_t Size = 0; + walkGraph([&Size, GetTerminalCountOnly](const HashNode *N) { + Size += (N && (!GetTerminalCountOnly || N->Terminals)); + }); + return Size; +} + +size_t OutlinedHashTree::depth() const { + size_t Size = 0; + DenseMap DepthMap; + walkGraph([&Size, &DepthMap]( + const HashNode *N) { Size = std::max(Size, DepthMap[N]); }, + [&DepthMap](const HashNode *Src, const HashNode *Dst) { + size_t Depth = DepthMap[Src]; + DepthMap[Dst] = Depth + 1; + }); + return Size; +} + +void OutlinedHashTree::insert(const HashSequencePair &SequencePair) { + auto &[Sequence, Count] = SequencePair; + HashNode *Current = getRoot(); + + for (stable_hash StableHash : Sequence) { + auto I = Current->Successors.find(StableHash); + if (I == Current->Successors.end()) { + std::unique_ptr Next = std::make_unique(); + HashNode *NextPtr = Next.get(); + NextPtr->Hash = StableHash; + Current->Successors.emplace(StableHash, std::move(Next)); + Current = NextPtr; + } else + Current = I->second.get(); + } + if (Count) + Current->Terminals = (Current->Terminals ? *Current->Terminals : 0) + Count; +} + +void OutlinedHashTree::merge(const OutlinedHashTree *Tree) { + HashNode *Dst = getRoot(); + const HashNode *Src = Tree->getRoot(); + SmallVector> Stack; + Stack.emplace_back(Dst, Src); + + while (!Stack.empty()) { + auto [DstNode, SrcNode] = Stack.pop_back_val(); + if (!SrcNode) + continue; + if (SrcNode->Terminals) + DstNode->Terminals = + (DstNode->Terminals ? *DstNode->Terminals : 0) + *SrcNode->Terminals; + for (auto &[Hash, NextSrcNode] : SrcNode->Successors) { + HashNode *NextDstNode; + auto I = DstNode->Successors.find(Hash); + if (I == DstNode->Successors.end()) { + auto NextDst = std::make_unique(); + NextDstNode = NextDst.get(); + NextDstNode->Hash = Hash; + DstNode->Successors.emplace(Hash, std::move(NextDst)); + } else + NextDstNode = I->second.get(); + + Stack.emplace_back(NextDstNode, NextSrcNode.get()); + } + } +} + +std::optional +OutlinedHashTree::find(const HashSequence &Sequence) const { + const HashNode *Current = getRoot(); + for (stable_hash StableHash : Sequence) { + const auto I = Current->Successors.find(StableHash); + if (I == Current->Successors.end()) + return 0; + Current = I->second.get(); + } + return Current->Terminals; +} diff --git a/llvm/lib/CodeGenData/OutlinedHashTreeRecord.cpp b/llvm/lib/CodeGenData/OutlinedHashTreeRecord.cpp new file mode 100644 index 0000000000000..996a57fd5e713 --- /dev/null +++ b/llvm/lib/CodeGenData/OutlinedHashTreeRecord.cpp @@ -0,0 +1,168 @@ +//===-- OutlinedHashTreeRecord.cpp ----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This defines the OutlinedHashTreeRecord class. This class holds the outlined +// hash tree for both serialization and deserialization processes. It utilizes +// two data formats for serialization: raw binary data and YAML. +// These two formats can be used interchangeably. +// +//===----------------------------------------------------------------------===// + +#include "llvm/CodeGenData/OutlinedHashTreeRecord.h" +#include "llvm/ObjectYAML/YAML.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/EndianStream.h" + +#define DEBUG_TYPE "outlined-hash-tree" + +using namespace llvm; +using namespace llvm::support; + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &io, HashNodeStable &res) { + io.mapRequired("Hash", res.Hash); + io.mapRequired("Terminals", res.Terminals); + io.mapRequired("SuccessorIds", res.SuccessorIds); + } +}; + +template <> struct CustomMappingTraits { + static void inputOne(IO &io, StringRef Key, IdHashNodeStableMapTy &V) { + HashNodeStable NodeStable; + io.mapRequired(Key.str().c_str(), NodeStable); + unsigned Id; + if (Key.getAsInteger(0, Id)) { + io.setError("Id not an integer"); + return; + } + V.insert({Id, NodeStable}); + } + + static void output(IO &io, IdHashNodeStableMapTy &V) { + for (auto Iter = V.begin(); Iter != V.end(); ++Iter) + io.mapRequired(utostr(Iter->first).c_str(), Iter->second); + } +}; + +} // namespace yaml +} // namespace llvm + +void OutlinedHashTreeRecord::serialize(raw_ostream &OS) const { + IdHashNodeStableMapTy IdNodeStableMap; + convertToStableData(IdNodeStableMap); + support::endian::Writer Writer(OS, endianness::little); + Writer.write(IdNodeStableMap.size()); + + for (const auto &[Id, NodeStable] : IdNodeStableMap) { + Writer.write(Id); + Writer.write(NodeStable.Hash); + Writer.write(NodeStable.Terminals); + Writer.write(NodeStable.SuccessorIds.size()); + for (auto SuccessorId : NodeStable.SuccessorIds) + Writer.write(SuccessorId); + } +} + +void OutlinedHashTreeRecord::deserialize(const unsigned char *&Ptr) { + IdHashNodeStableMapTy IdNodeStableMap; + auto NumIdNodeStableMap = + endian::readNext(Ptr); + + for (unsigned I = 0; I < NumIdNodeStableMap; ++I) { + auto Id = endian::readNext(Ptr); + HashNodeStable NodeStable; + NodeStable.Hash = + endian::readNext(Ptr); + NodeStable.Terminals = + endian::readNext(Ptr); + auto NumSuccessorIds = + endian::readNext(Ptr); + for (unsigned J = 0; J < NumSuccessorIds; ++J) + NodeStable.SuccessorIds.push_back( + endian::readNext(Ptr)); + + IdNodeStableMap[Id] = std::move(NodeStable); + } + + convertFromStableData(IdNodeStableMap); +} + +void OutlinedHashTreeRecord::serializeYAML(yaml::Output &YOS) const { + IdHashNodeStableMapTy IdNodeStableMap; + convertToStableData(IdNodeStableMap); + + YOS << IdNodeStableMap; +} + +void OutlinedHashTreeRecord::deserializeYAML(yaml::Input &YIS) { + IdHashNodeStableMapTy IdNodeStableMap; + + YIS >> IdNodeStableMap; + YIS.nextDocument(); + + convertFromStableData(IdNodeStableMap); +} + +void OutlinedHashTreeRecord::convertToStableData( + IdHashNodeStableMapTy &IdNodeStableMap) const { + // Build NodeIdMap + HashNodeIdMapTy NodeIdMap; + HashTree->walkGraph( + [&NodeIdMap](const HashNode *Current) { + size_t Index = NodeIdMap.size(); + NodeIdMap[Current] = Index; + assert(Index = NodeIdMap.size() + 1 && + "Expected size of NodeMap to increment by 1"); + }, + /*EdgeCallbackFn=*/nullptr, /*SortedWork=*/true); + + // Convert NodeIdMap to NodeStableMap + for (auto &P : NodeIdMap) { + auto *Node = P.first; + auto Id = P.second; + HashNodeStable NodeStable; + NodeStable.Hash = Node->Hash; + NodeStable.Terminals = Node->Terminals ? *Node->Terminals : 0; + for (auto &P : Node->Successors) + NodeStable.SuccessorIds.push_back(NodeIdMap[P.second.get()]); + IdNodeStableMap[Id] = NodeStable; + } + + // Sort the Successors so that they come out in the same order as in the map. + for (auto &P : IdNodeStableMap) + llvm::sort(P.second.SuccessorIds); +} + +void OutlinedHashTreeRecord::convertFromStableData( + const IdHashNodeStableMapTy &IdNodeStableMap) { + IdHashNodeMapTy IdNodeMap; + // Initialize the root node at 0. + IdNodeMap[0] = HashTree->getRoot(); + assert(IdNodeMap[0]->Successors.empty()); + + for (auto &P : IdNodeStableMap) { + auto Id = P.first; + const HashNodeStable &NodeStable = P.second; + assert(IdNodeMap.count(Id)); + HashNode *Curr = IdNodeMap[Id]; + Curr->Hash = NodeStable.Hash; + if (NodeStable.Terminals) + Curr->Terminals = NodeStable.Terminals; + auto &Successors = Curr->Successors; + assert(Successors.empty()); + for (auto SuccessorId : NodeStable.SuccessorIds) { + auto Sucessor = std::make_unique(); + IdNodeMap[SuccessorId] = Sucessor.get(); + auto Hash = IdNodeStableMap.at(SuccessorId).Hash; + Successors[Hash] = std::move(Sucessor); + } + } +} diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt index 696926110df7a..49ed6c8fb6c42 100644 --- a/llvm/unittests/CMakeLists.txt +++ b/llvm/unittests/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(BinaryFormat) add_subdirectory(Bitcode) add_subdirectory(Bitstream) add_subdirectory(CodeGen) +add_subdirectory(CodeGenData) add_subdirectory(DebugInfo) add_subdirectory(Debuginfod) add_subdirectory(Demangle) diff --git a/llvm/unittests/CodeGenData/CMakeLists.txt b/llvm/unittests/CodeGenData/CMakeLists.txt new file mode 100644 index 0000000000000..3d821b87e29d8 --- /dev/null +++ b/llvm/unittests/CodeGenData/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + CodeGen + CodeGenData + Core + Support + ) + +add_llvm_unittest(CodeGenDataTests + OutlinedHashTreeRecordTest.cpp + OutlinedHashTreeTest.cpp + ) + +target_link_libraries(CodeGenDataTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/CodeGenData/OutlinedHashTreeRecordTest.cpp b/llvm/unittests/CodeGenData/OutlinedHashTreeRecordTest.cpp new file mode 100644 index 0000000000000..aa7ad4a33754f --- /dev/null +++ b/llvm/unittests/CodeGenData/OutlinedHashTreeRecordTest.cpp @@ -0,0 +1,118 @@ +//===- OutlinedHashTreeRecordTest.cpp -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/CodeGenData/OutlinedHashTreeRecord.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +TEST(OutlinedHashTreeRecordTest, Empty) { + OutlinedHashTreeRecord HashTreeRecord; + ASSERT_TRUE(HashTreeRecord.empty()); +} + +TEST(OutlinedHashTreeRecordTest, Print) { + OutlinedHashTreeRecord HashTreeRecord; + HashTreeRecord.HashTree->insert({{1, 2}, 3}); + + const char *ExpectedTreeStr = R"(--- +0: + Hash: 0x0 + Terminals: 0 + SuccessorIds: [ 1 ] +1: + Hash: 0x1 + Terminals: 0 + SuccessorIds: [ 2 ] +2: + Hash: 0x2 + Terminals: 3 + SuccessorIds: [ ] +... +)"; + std::string TreeDump; + raw_string_ostream OS(TreeDump); + HashTreeRecord.print(OS); + EXPECT_EQ(ExpectedTreeStr, TreeDump); +} + +TEST(OutlinedHashTreeRecordTest, Stable) { + OutlinedHashTreeRecord HashTreeRecord1; + HashTreeRecord1.HashTree->insert({{1, 2}, 4}); + HashTreeRecord1.HashTree->insert({{1, 3}, 5}); + + OutlinedHashTreeRecord HashTreeRecord2; + HashTreeRecord2.HashTree->insert({{1, 3}, 5}); + HashTreeRecord2.HashTree->insert({{1, 2}, 4}); + + // Output is stable regardless of insertion order. + std::string TreeDump1; + raw_string_ostream OS1(TreeDump1); + HashTreeRecord1.print(OS1); + std::string TreeDump2; + raw_string_ostream OS2(TreeDump2); + HashTreeRecord2.print(OS2); + + EXPECT_EQ(TreeDump1, TreeDump2); +} + +TEST(OutlinedHashTreeRecordTest, Serialize) { + OutlinedHashTreeRecord HashTreeRecord1; + HashTreeRecord1.HashTree->insert({{1, 2}, 4}); + HashTreeRecord1.HashTree->insert({{1, 3}, 5}); + + // Serialize and deserialize the tree. + SmallVector Out; + raw_svector_ostream OS(Out); + HashTreeRecord1.serialize(OS); + + OutlinedHashTreeRecord HashTreeRecord2; + const uint8_t *Data = reinterpret_cast(Out.data()); + HashTreeRecord2.deserialize(Data); + + // Two trees should be identical. + std::string TreeDump1; + raw_string_ostream OS1(TreeDump1); + HashTreeRecord1.print(OS1); + std::string TreeDump2; + raw_string_ostream OS2(TreeDump2); + HashTreeRecord2.print(OS2); + + EXPECT_EQ(TreeDump1, TreeDump2); +} + +TEST(OutlinedHashTreeRecordTest, SerializeYAML) { + OutlinedHashTreeRecord HashTreeRecord1; + HashTreeRecord1.HashTree->insert({{1, 2}, 4}); + HashTreeRecord1.HashTree->insert({{1, 3}, 5}); + + // Serialize and deserialize the tree in a YAML format. + std::string Out; + raw_string_ostream OS(Out); + yaml::Output YOS(OS); + HashTreeRecord1.serializeYAML(YOS); + + OutlinedHashTreeRecord HashTreeRecord2; + yaml::Input YIS(StringRef(Out.data(), Out.size())); + HashTreeRecord2.deserializeYAML(YIS); + + // Two trees should be identical. + std::string TreeDump1; + raw_string_ostream OS1(TreeDump1); + HashTreeRecord1.print(OS1); + std::string TreeDump2; + raw_string_ostream OS2(TreeDump2); + HashTreeRecord2.print(OS2); + + EXPECT_EQ(TreeDump1, TreeDump2); +} + +} // end namespace diff --git a/llvm/unittests/CodeGenData/OutlinedHashTreeTest.cpp b/llvm/unittests/CodeGenData/OutlinedHashTreeTest.cpp new file mode 100644 index 0000000000000..4ab5566cf817b --- /dev/null +++ b/llvm/unittests/CodeGenData/OutlinedHashTreeTest.cpp @@ -0,0 +1,82 @@ +//===- OutlinedHashTreeTest.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/CodeGenData/OutlinedHashTree.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +TEST(OutlinedHashTreeTest, Empty) { + OutlinedHashTree HashTree; + EXPECT_TRUE(HashTree.empty()); + // The header node is always present. + EXPECT_EQ(HashTree.size(), 1); + EXPECT_EQ(HashTree.depth(), 0); +} + +TEST(OutlinedHashTreeTest, Insert) { + OutlinedHashTree HashTree; + HashTree.insert({{1, 2, 3}, 1}); + // The node count is 4 (including the root node). + EXPECT_EQ(HashTree.size(), 4); + // The terminal count is 1. + EXPECT_EQ(HashTree.size(/*GetTerminalCountOnly=*/true), 1); + // The depth is 3. + EXPECT_EQ(HashTree.depth(), 3); + + HashTree.clear(); + EXPECT_TRUE(HashTree.empty()); + + HashTree.insert({{1, 2, 3}, 1}); + HashTree.insert({{1, 2, 4}, 2}); + // The nodes of 1 and 2 are shared with the same prefix. + // The nodes are root, 1, 2, 3 and 4, whose counts are 5. + EXPECT_EQ(HashTree.size(), 5); +} + +TEST(OutlinedHashTreeTest, Find) { + OutlinedHashTree HashTree; + HashTree.insert({{1, 2, 3}, 1}); + HashTree.insert({{1, 2, 3}, 2}); + + // The node count does not change as the same sequences are added. + EXPECT_EQ(HashTree.size(), 4); + // The terminal counts are accumulated from two same sequences. + EXPECT_TRUE(HashTree.find({1, 2, 3})); + EXPECT_EQ(HashTree.find({1, 2, 3}).value(), 3); + EXPECT_FALSE(HashTree.find({1, 2})); +} + +TEST(OutlinedHashTreeTest, Merge) { + // Build HashTree1 inserting 2 sequences. + OutlinedHashTree HashTree1; + + HashTree1.insert({{1, 2}, 20}); + HashTree1.insert({{1, 4}, 30}); + + // Build HashTree2 and HashTree3 for each + OutlinedHashTree HashTree2; + HashTree2.insert({{1, 2}, 20}); + OutlinedHashTree HashTree3; + HashTree3.insert({{1, 4}, 30}); + + // Merge HashTree3 into HashTree2. + HashTree2.merge(&HashTree3); + + // Compare HashTree1 and HashTree2. + EXPECT_EQ(HashTree1.size(), HashTree2.size()); + EXPECT_EQ(HashTree1.depth(), HashTree2.depth()); + EXPECT_EQ(HashTree1.find({1, 2}), HashTree2.find({1, 2})); + EXPECT_EQ(HashTree1.find({1, 4}), HashTree2.find({1, 4})); + EXPECT_EQ(HashTree1.find({1, 3}), HashTree2.find({1, 3})); +} + +} // end namespace