From 43ae7a91cb36dffe4701bbde5e2c132a386fa8e5 Mon Sep 17 00:00:00 2001 From: Alvise de Faveri Date: Wed, 15 Sep 2021 19:15:33 +0200 Subject: [PATCH] DLA: Add DeduplicateUnionFields Step Add a step that recognizes if two subtrees of a union node are topologically equivalent and merges them. This corresponds to removing duplicate fields in unions. This deduplication was prevously done while emitting layouts. A check is inserted into DLAMakeLayouts to assert that, after constructing unions, no union has only one child, which could be the case if we didn't deduplicate union fields in the graph. --- .../DataLayoutAnalysis/DLATypeSystem.h | 10 +- .../Backend/DLAMakeLayouts.cpp | 50 +- lib/DataLayoutAnalysis/CMakeLists.txt | 1 + lib/DataLayoutAnalysis/DLAPass.cpp | 3 +- lib/DataLayoutAnalysis/DLATypeSystem.cpp | 28 +- .../Middleend/DLACollapseSingleChild.cpp | 3 - .../DLAComputeNonInterferingComponents.cpp | 3 +- .../Middleend/DLAComputeUpperMemberAccess.cpp | 4 +- .../Middleend/DLADeduplicateUnionFields.cpp | 440 ++++++++++++++++++ .../Middleend/DLARemoveConflictingEdges.cpp | 4 +- lib/DataLayoutAnalysis/Middleend/DLAStep.cpp | 1 + lib/DataLayoutAnalysis/Middleend/DLAStep.h | 20 + tests/Unit/DLASteps.cpp | 225 ++++++++- 13 files changed, 759 insertions(+), 33 deletions(-) create mode 100644 lib/DataLayoutAnalysis/Middleend/DLADeduplicateUnionFields.cpp diff --git a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h index f732979fd..5a5862f98 100644 --- a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h +++ b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h @@ -258,10 +258,12 @@ public: dla::TypeLinkTag::instanceTag(std::forward(OE))); } - void dumpDotOnFile(const char *FName) const; + void dumpDotOnFile(const char *FName, + bool ShowCollapsed = false) const debug_function; - void dumpDotOnFile(const std::string &FName) const { - dumpDotOnFile(FName.c_str()); + void + dumpDotOnFile(const std::string &FName, bool ShowCollapsed = false) const { + dumpDotOnFile(FName.c_str(), ShowCollapsed); } auto getNumLayouts() const { return Layouts.size(); } @@ -308,6 +310,8 @@ public: bool verifyLeafs() const; // Checks that there are no equality edges. bool verifyNoEquality() const; + // Checks that no union node has only one child + bool verifyUnions() const; // Checks that no node conflicting edges. bool verifyConflicts() const; diff --git a/lib/DataLayoutAnalysis/Backend/DLAMakeLayouts.cpp b/lib/DataLayoutAnalysis/Backend/DLAMakeLayouts.cpp index 0f81e5e2d..d1f74fbb4 100644 --- a/lib/DataLayoutAnalysis/Backend/DLAMakeLayouts.cpp +++ b/lib/DataLayoutAnalysis/Backend/DLAMakeLayouts.cpp @@ -111,15 +111,22 @@ static Layout *makeLayout(const LayoutTypeSystem &TS, case AllChildrenAreNonInterfering: { + StructLayout::fields_container_t SFlds; + // Create BaseLayout for leaf nodes revng_assert(not isLeaf(N) or N->Size); if (isLeaf(N)) { Layout *AccessLayout = createLayout(Layouts, N->Size); + + // If the leaf has an inheritance parent, wrap the BaseLayout into a + // struct + if (llvm::any_of(N->Predecessors, isInheritanceEdge)) { + SFlds.push_back(AccessLayout); + AccessLayout = createLayout(Layouts, SFlds); + } return AccessLayout; } - StructLayout::fields_container_t SFlds; - struct OrderedChild { int64_t Offset; decltype(N->Size) Size; @@ -215,10 +222,10 @@ static Layout *makeLayout(const LayoutTypeSystem &TS, revng_assert(StartByte >= 0LL and Size > 0ULL); uint64_t Start = static_cast(StartByte); revng_assert(Start >= CurSize); - auto PadSize = Start - CurSize; // always >= 0; + auto PadSize = Start - CurSize; revng_assert(PadSize >= 0); - // If an unaccessed layout is known to exist, add it as padding + // If there is a "hole" between accesses, add it as padding if (PadSize) { Layout *Padding = createLayout(Layouts, PadSize); SFlds.push_back(Padding); @@ -253,6 +260,7 @@ static Layout *makeLayout(const LayoutTypeSystem &TS, // Look at all the instance-of edges and inheritance edges all together bool InheritsFromOther = false; + bool HasNullChild = false; for (auto &[Child, EdgeTag] : children_edges(N)) { revng_log(Log, "Child ID: " << Child->ID); @@ -263,8 +271,10 @@ static Layout *makeLayout(const LayoutTypeSystem &TS, // Ignore children for which we haven't created a layout, because they // only have children from which it was not possible to create valid // layouts. - if (not ChildType) + if (not ChildType) { + revng_log(Log, "No corresponding layout for " << Child->ID); return nullptr; + } switch (EdgeTag->getKind()) { @@ -291,8 +301,16 @@ static Layout *makeLayout(const LayoutTypeSystem &TS, // Bail out if we have not constructed a union field, because it means // that this is not a supported case yet. - if (nullptr != ChildType) - UFlds.insert(ChildType); + + if (nullptr != ChildType) { + bool New = UFlds.insert(ChildType).second; + + if (not New) + revng_log(Log, "Duplicate layout found, size: " << UFlds.size()); + } else { + HasNullChild = true; + revng_log(Log, "No type created for " << Child->ID); + } } // This layout has no useful access or outgoing edges that can build the @@ -301,10 +319,17 @@ static Layout *makeLayout(const LayoutTypeSystem &TS, if (UFlds.empty()) return nullptr; - Layout *CreatedLayout = (UFlds.size() > 1ULL) ? - createLayout(Layouts, UFlds) : - *UFlds.begin(); - return CreatedLayout; + // If a null layout was generated for one of the children, or the node has + // more than one parent, the union might contain only one field. In this + // case, there is no point in emitting a union, so a struct must be emitted. + if (UFlds.size() == 1) { + revng_assert(HasNullChild); + StructLayout::fields_container_t Fields; + Fields.push_back(*UFlds.begin()); + return createLayout(Layouts, Fields); + } + + return createLayout(Layouts, UFlds); } break; case Unknown: @@ -319,7 +344,8 @@ LayoutPtrVector makeLayouts(const LayoutTypeSystem &TS, LayoutVector &Layouts) { TS.dumpDotOnFile("final.dot"); if (VerifyLog.isEnabled()) - revng_assert(TS.verifyDAG() and TS.verifyInheritanceTree()); + revng_assert(TS.verifyDAG() and TS.verifyInheritanceTree() + and TS.verifyUnions()); // Prepare the vector of layouts that correspond to actual LayoutTypePtrs LayoutPtrVector OrderedLayouts; diff --git a/lib/DataLayoutAnalysis/CMakeLists.txt b/lib/DataLayoutAnalysis/CMakeLists.txt index b90574fde..5ec16e992 100644 --- a/lib/DataLayoutAnalysis/CMakeLists.txt +++ b/lib/DataLayoutAnalysis/CMakeLists.txt @@ -13,6 +13,7 @@ revng_add_analyses_library(DataLayoutAnalysis revngc Middleend/DLARemoveTransitiveInheritanceEdges.cpp Middleend/DLAMakeInheritanceTree.cpp Middleend/DLACollapseSingleChild.cpp + Middleend/DLADeduplicateUnionFields.cpp Middleend/DLARemoveConflictingEdges.cpp Middleend/DLAStep.cpp Backend/DLAMakeLayouts.cpp diff --git a/lib/DataLayoutAnalysis/DLAPass.cpp b/lib/DataLayoutAnalysis/DLAPass.cpp index 37fb07fd7..2ef6479bc 100644 --- a/lib/DataLayoutAnalysis/DLAPass.cpp +++ b/lib/DataLayoutAnalysis/DLAPass.cpp @@ -57,7 +57,8 @@ bool DLAPass::runOnModule(llvm::Module &M) { revng_check(SM.addStep()); revng_check(SM.addStep()); revng_check(SM.addStep()); - + revng_check(SM.addStep()); + revng_check(SM.addStep()); SM.run(TS); if (BuilderLog.isEnabled()) diff --git a/lib/DataLayoutAnalysis/DLATypeSystem.cpp b/lib/DataLayoutAnalysis/DLATypeSystem.cpp index 3888b3fc7..8c584597f 100644 --- a/lib/DataLayoutAnalysis/DLATypeSystem.cpp +++ b/lib/DataLayoutAnalysis/DLATypeSystem.cpp @@ -8,6 +8,7 @@ #include "llvm/ADT/SCCIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/Debug.h" #include "llvm/Support/FormattedStream.h" #include "llvm/Support/raw_ostream.h" @@ -24,6 +25,8 @@ using namespace llvm; using NodeAllocatorT = SpecificBumpPtrAllocator; +static Logger<> CollapsedNodePrinter("dla-print-collapsed-in-dot"); + void *operator new(size_t, NodeAllocatorT &NodeAllocator) { return NodeAllocator.Allocate(); } @@ -93,7 +96,8 @@ static_assert(sizeof(Instance) == (str_len(Instance) + 1)); static_assert(sizeof(Unexpected) == (str_len(Unexpected) + 1)); } // end unnamed namespace -void LayoutTypeSystem::dumpDotOnFile(const char *FName) const { +void debug_function LayoutTypeSystem::dumpDotOnFile(const char *FName, + bool ShowCollapsed) const { std::error_code EC; raw_fd_ostream DotFile(FName, EC); revng_check(not EC, "Could not open file for printing LayoutTypeSystem dot"); @@ -121,7 +125,9 @@ void LayoutTypeSystem::dumpDotOnFile(const char *FName) const { revng_unreachable(); } - DebugPrinter->printNodeContent(*this, L, DotFile); + if (CollapsedNodePrinter.isEnabled() or ShowCollapsed) + DebugPrinter->printNodeContent(*this, L, DotFile); + DotFile << "\"];\n"; } @@ -640,6 +646,20 @@ bool LayoutTypeSystem::verifyInheritanceTree() const { return true; } +bool LayoutTypeSystem::verifyUnions() const { + using GraphNodeT = const LayoutTypeSystemNode *; + for (GraphNodeT Node : llvm::nodes(this)) { + if (Node->InterferingInfo == AllChildrenAreInterfering + and Node->Successors.size() <= 1) { + if (VerifyDLALog.isEnabled()) + revng_check(false); + return false; + } + } + + return true; +} + bool LayoutTypeSystem::verifyConflicts() const { using GraphNodeT = const LayoutTypeSystemNode *; using LinkT = const LayoutTypeSystemNode::Link; @@ -647,12 +667,12 @@ bool LayoutTypeSystem::verifyConflicts() const { for (GraphNodeT Node : llvm::nodes(this)) { for (auto &Succ : Node->Successors) { - auto HasSameSucc = [&Succ](const LinkT &L2) { + auto HasSameSuccAtOffset0 = [&Succ](const LinkT &L2) { return isInstanceOff0Edge(L2) and (Succ.first == L2.first); }; if (isInheritanceEdge(Succ) - and llvm::any_of(Node->Successors, HasSameSucc)) { + and llvm::any_of(Node->Successors, HasSameSuccAtOffset0)) { if (VerifyDLALog.isEnabled()) revng_check(false); return false; diff --git a/lib/DataLayoutAnalysis/Middleend/DLACollapseSingleChild.cpp b/lib/DataLayoutAnalysis/Middleend/DLACollapseSingleChild.cpp index 444b6b4c4..4cffbf75d 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLACollapseSingleChild.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLACollapseSingleChild.cpp @@ -38,9 +38,6 @@ bool CollapseSingleChild::collapseSingle(LayoutTypeSystem &TS, return (Node->Predecessors.size() <= 1); }; - if (not HasAtMostOneParent(Node)) - return false; - // Get nodes that have a single instance or inheritance child if (HasSingleChild(Node) and ChildIsInstanceOrInheritance(Node)) { auto &ChildEdge = *(Node->Successors.begin()); diff --git a/lib/DataLayoutAnalysis/Middleend/DLAComputeNonInterferingComponents.cpp b/lib/DataLayoutAnalysis/Middleend/DLAComputeNonInterferingComponents.cpp index e3c1f744d..d37ad0202 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAComputeNonInterferingComponents.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLAComputeNonInterferingComponents.cpp @@ -257,7 +257,8 @@ bool ComputeNonInterferingComponents::runOnTypeSystem(LayoutTypeSystem &TS) { } if (VerifyLog.isEnabled()) - revng_assert(TS.verifyDAG() and TS.verifyInheritanceTree()); + revng_assert(TS.verifyDAG() and TS.verifyInheritanceTree() + and TS.verifyUnions()); return Changed; } diff --git a/lib/DataLayoutAnalysis/Middleend/DLAComputeUpperMemberAccess.cpp b/lib/DataLayoutAnalysis/Middleend/DLAComputeUpperMemberAccess.cpp index 8aec3661e..5187cb657 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAComputeUpperMemberAccess.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLAComputeUpperMemberAccess.cpp @@ -106,9 +106,11 @@ bool ComputeUpperMemberAccesses::runOnTypeSystem(LayoutTypeSystem &TS) { revng_unreachable("unexpected edge"); } } + if (FinalSize != N->Size) + Changed = true; + N->Size = FinalSize; revng_assert(FinalSize); - Changed = true; } } diff --git a/lib/DataLayoutAnalysis/Middleend/DLADeduplicateUnionFields.cpp b/lib/DataLayoutAnalysis/Middleend/DLADeduplicateUnionFields.cpp new file mode 100644 index 000000000..8b155ecad --- /dev/null +++ b/lib/DataLayoutAnalysis/Middleend/DLADeduplicateUnionFields.cpp @@ -0,0 +1,440 @@ +// +// Copyright (c) rev.ng Srls. See LICENSE.md for details. +// + +#include +#include +#include +#include + +#include "llvm/ADT/GraphTraits.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/Support/Debug.h" + +#include "revng/Support/Assert.h" +#include "revng/Support/Debug.h" + +#include "revng-c/DataLayoutAnalysis/DLATypeSystem.h" + +#include "../DLAHelpers.h" +#include "DLAStep.h" + +using LTSN = dla::LayoutTypeSystemNode; +using order = std::strong_ordering; +using Link = dla::LayoutTypeSystemNode::Link; +using EdgeList = std::vector; +using Tag = dla::TypeLinkTag; + +using namespace llvm; + +static Logger<> Log("dla-deduplicate-union-fields"); +static Logger<> CmpLog("dla-duf-comparisons"); + +namespace dla { + +///\brief Strong ordering for nodes: order by size, then by number of successors +static order cmpNodes(const LTSN *A, const LTSN *B) { + if (A == B) + return order::equal; + + if (not A) + return order::less; + + if (not B) + return order::greater; + + const auto SizeCmp = A->Size <=> B->Size; + if (SizeCmp != order::equal) { + revng_log(CmpLog, "Different sizes"); + return SizeCmp; + } + + size_t NChild1 = A->Successors.size(); + size_t NChild2 = B->Successors.size(); + const auto NChildCmp = NChild1 <=> NChild2; + if (NChildCmp != order::equal) { + revng_log(CmpLog, + "Different number of successors: node " + << A->ID << " has " << NChild1 << " successors, node " << B->ID + << " has" << NChild2 << " successors"); + return NChildCmp; + } + + // TODO: check pointer edges + return order::equal; +} + +///\brief Strong ordering for edges: order by kind, then by offset expression +/// +///\note Inheritance and instance at offset 0 can be considered equivalent when +/// comparing subtrees. +static order +cmpEdgeTags(const Tag *A, const Tag *B, bool IgnoreInheritance = true) { + if (A == B) + return order::equal; + revng_assert(A != nullptr and B != nullptr); + + // If A is an inheritance edge, consider it as an instance-offset-0 edge + auto KindA = A->getKind(); + OffsetExpression OffA; + if (KindA == TypeLinkTag::LK_Inheritance) { + if (IgnoreInheritance) + KindA = TypeLinkTag::LK_Instance; + OffA.Offset = 0; + } else { + OffA = A->getOffsetExpr(); + } + + // If B is an inheritance edge, consider it as an instance-offset-0 edge + auto KindB = B->getKind(); + OffsetExpression OffB; + if (KindB == TypeLinkTag::LK_Inheritance) { + if (IgnoreInheritance) + KindB = TypeLinkTag::LK_Instance; + OffB.Offset = 0; + } else { + OffB = B->getOffsetExpr(); + } + + const auto KindCmp = KindA <=> KindB; + if (KindCmp != order::equal) + return KindCmp; + + const auto OffsetCmp = OffA.Offset <=> OffB.Offset; + if (OffsetCmp != order::equal) + return OffsetCmp; + + const auto StrideSizeCmp = OffA.Strides.size() <=> OffB.Strides.size(); + if (StrideSizeCmp != order::equal) + return StrideSizeCmp; + for (const auto &[StrA, StrB] : llvm::zip(OffA.Strides, OffB.Strides)) { + const auto StrideCmp = StrA <=> StrB; + if (StrideCmp != order::equal) + return StrideCmp; + } + + const auto TCSizeCmp = OffA.TripCounts.size() <=> OffB.TripCounts.size(); + if (TCSizeCmp != order::equal) + return TCSizeCmp; + for (const auto &[TCA, TCB] : llvm::zip(OffA.TripCounts, OffB.TripCounts)) { + if (not TCA and not TCB) + continue; + if (TCA and not TCB) + return order::less; + if (not TCA and TCB) + return order::greater; + + if (TCA and TCB) { + const auto TCCmp = *TCA <=> *TCB; + if (TCCmp != order::equal) + return TCCmp; + } + } + + return order::equal; +} + +///\brief Compare two subtrees, saving the visited nodes onto two stacks +static std::tuple +exploreAndCompare(const Link &Child1, const Link &Child2); + +///\brief Recursively define an ordering between children of a node +static bool linkOrderLess(const Link &A, const Link &B) { + const order EdgeOrder = cmpEdgeTags(A.second, + B.second, + /*IgnoreInheritance=*/false); + if (EdgeOrder != order::equal) + return EdgeOrder < 0; + + const order NodeOrder = cmpNodes(A.first, B.first); + if (NodeOrder != order::equal) + return NodeOrder < 0; + + // In case the two nodes are equivalent, explore the whole subtree + // TODO: cache the result of this comparison? + const auto [SubtreeOrder, _, __] = exploreAndCompare(A, B); + revng_assert(SubtreeOrder != order::equal); + return SubtreeOrder == order::less; +} + +static std::tuple +exploreAndCompare(const Link &Child1, const Link &Child2) { + EdgeList VisitStack1{ Child1 }, VisitStack2{ Child2 }; + EdgeList NextToVisit1, NextToVisit2; + size_t CurIdx = 0; + do { + // Append the newly found nodes to the visit stack of each subtree + size_t NextSize = NextToVisit1.size(); + revng_assert(NextSize == NextToVisit2.size()); + + if (NextSize > 0) { + size_t PrevSize = VisitStack1.size(); + VisitStack1.reserve(PrevSize + NextSize); + VisitStack2.reserve(PrevSize + NextSize); + VisitStack1.insert(VisitStack1.end(), + std::make_move_iterator(NextToVisit1.begin()), + std::make_move_iterator(NextToVisit1.end())); + VisitStack2.insert(VisitStack2.end(), + std::make_move_iterator(NextToVisit2.begin()), + std::make_move_iterator(NextToVisit2.end())); + NextToVisit1.clear(); + NextToVisit2.clear(); + } + + // Perform bfs on new nodes + for (; CurIdx < VisitStack1.size(); CurIdx++) { + const auto &[Node1, Edge1] = VisitStack1[CurIdx]; + const auto &[Node2, Edge2] = VisitStack2[CurIdx]; + revng_log(CmpLog, "Comparing " << Node1->ID << " with " << Node2->ID); + + // TODO: handle pointer edges + const order EdgeOrder = cmpEdgeTags(Edge1, Edge2); + if (EdgeOrder != order::equal) + return { EdgeOrder, {}, {} }; + + const order NodeOrder = cmpNodes(Node1, Node2); + if (NodeOrder != order::equal) + return { NodeOrder, {}, {} }; + + // Enqueue the successors of the current nodes + revng_log(CmpLog, "Could not tell the difference, visiting successors"); + NextToVisit1.reserve(NextToVisit1.size() + Node1->Successors.size()); + NextToVisit2.reserve(NextToVisit2.size() + Node2->Successors.size()); + llvm::copy(Node1->Successors, std::back_inserter(NextToVisit1)); + llvm::copy(Node2->Successors, std::back_inserter(NextToVisit2)); + + // Sort the newly enqueued nodes + size_t NChildren = Node1->Successors.size(); + revng_assert(NChildren == Node2->Successors.size()); + + std::sort(NextToVisit1.end() - NChildren, + NextToVisit1.end(), + linkOrderLess); + std::sort(NextToVisit2.end() - NChildren, + NextToVisit2.end(), + linkOrderLess); + } + } while (NextToVisit1.size() > 0); + + return { order::equal, VisitStack1, VisitStack2 }; +} + +///\brief Check if two subtrees are equivalent, saving the visited nodes in the +/// order in which they were compared. +static inline std::tuple +areEquivSubtrees(Link &Child1, Link &Child2) { + auto [Result, Visited1, Visited2] = exploreAndCompare(Child1, Child2); + bool AreSubtreesEqual = Result == order::equal; + + return { AreSubtreesEqual, Visited1, Visited2 }; +} + +///\brief Visit the two subtrees of \a Child1 and \a Child2. If they are +/// equivalent, merge each node with the one it has been compared to. +/// +///\return true if the two nodes were merged, and merged subtree +///\param TS the graph in which the comparison should be performed +///\param Child1 the root of the first subtree +///\param Child2 the root of the second subtree, will be collapsed if +/// equivalent to the subtree of \a Child1 +static std::pair +mergeIfTopologicallyEq(LayoutTypeSystem &TS, Link &Child1, Link &Child2) { + if (Child1 == Child2) + return { true, {} }; + + auto [AreEquiv, Subtree1, Subtree2] = areEquivSubtrees(Child1, Child2); + if (AreEquiv) { + // Create a map between nodes to merge and the corresponding merge + // destination, in order to: + // 1. avoid duplicates in merging list + // 2. check that a node is never merged into two separate nodes + // 3. handle the case in which the merge destination has to be merged itself + std::map MergeMap; + for (const auto &[Link1, Link2] : llvm::zip(Subtree1, Subtree2)) { + auto *NodeToKeep = Link1.first; + auto *NodeToMerge = Link2.first; + + const auto &[_, Inserted] = MergeMap.insert({ NodeToMerge, NodeToKeep }); + if (not Inserted) + revng_assert(MergeMap.at(NodeToMerge) = NodeToKeep); + } + + // Redirect chains of nodes that have to be merged together + llvm::SmallPtrSet Subtree1MergedNodes; + for (auto &[NodeToMerge, NodeToKeep] : MergeMap) { + if (NodeToKeep == NodeToMerge + or Subtree1MergedNodes.contains(NodeToMerge)) + continue; + + auto MapEntry = MergeMap.find(NodeToKeep); + llvm::SmallPtrSet MergeChain; + + // Find chains of nodes to merge + while (MapEntry != MergeMap.end()) { + Subtree1MergedNodes.insert(MapEntry->first); + const auto &[_, Inserted] = MergeChain.insert(NodeToKeep); + // Avoid loops + if (not Inserted) + break; + + NodeToKeep = MapEntry->second; + + // Go to next node of the chain + MapEntry = MergeMap.find(NodeToKeep); + } + + // Update the merge destination of all the nodes of the chain + for (auto *N : MergeChain) + MergeMap.at(N) = NodeToKeep; + } + + // Execute merge + for (auto &[NodeToMerge, NodeToKeep] : MergeMap) { + if (NodeToKeep == NodeToMerge) + continue; + + // TODO: light merge + TS.mergeNodes({ NodeToKeep, NodeToMerge }); + } + + // Remove merged nodes from subtree1 + if (Subtree1MergedNodes.size() > 0) { + for (auto It = Subtree1.begin(); It != Subtree1.end();) { + if (Subtree1MergedNodes.contains(It->first)) + It = Subtree1.erase(It); + else + ++It; + } + } + + return { true, Subtree1 }; + } + + return { false, {} }; +} + +///\brief Remove conflicting edges and collapse single children after merging. +static bool +postProcessMerge(LayoutTypeSystem &TS, const EdgeList &MergedSubtree) { + bool Modified = false; + + // Merging nodes together might have created conflicting edges, i.e. + // instance-offset-0 edges that connect two nodes with an already + // existing inheritance edges: remove them. + for (auto &E : MergedSubtree) { + // Materialize predecessors to avoid iterator invalidation + llvm::SmallVector PredNodes; + for (auto &PredLink : E.first->Predecessors) + PredNodes.push_back(PredLink.first); + + // Remove conflicts from predecessors + for (auto &Pred : PredNodes) + Modified |= RemoveConflictingEdges::removeConflicts(TS, Pred); + + // Remove conflict from node + Modified |= RemoveConflictingEdges::removeConflicts(TS, E.first); + } + + // Merging nodes and removing conflicts might have created situations in + // which a node has a single child: collapse it into its parent. + LTSN *SubtreeRoot = MergedSubtree.begin()->first; + for (auto &N : post_order(SubtreeRoot)) + Modified |= CollapseSingleChild::collapseSingle(TS, N); + + return Modified; +} + +bool DeduplicateUnionFields::runOnTypeSystem(LayoutTypeSystem &TS) { + bool TypeSystemChanged = false; + if (VerifyLog.isEnabled()) + revng_assert(TS.verifyConsistency() and TS.verifyDAG() + and TS.verifyInheritanceTree()); + + if (Log.isEnabled()) + TS.dumpDotOnFile("before-deduplicate-union-fields.dot"); + + for (LTSN *Root : llvm::nodes(&TS)) { + revng_assert(Root != nullptr); + if (not isRoot(Root)) + continue; + + // Visit all Union nodes in post-order + for (LTSN *UnionNode : post_order(Root)) { + if (UnionNode->InterferingInfo != AllChildrenAreInterfering) + continue; + revng_log(Log, "****** Union Node found: " << UnionNode->ID); + + RemoveConflictingEdges::removeConflicts(TS, UnionNode); + + llvm::SmallSetVector ToCompare; + llvm::SmallSetVector Visited; + + for (Link Succ : UnionNode->Successors) + ToCompare.insert(Succ); + + bool UnionNodeChanged = false; + while (ToCompare.size() > 0) { + Link CurLink = ToCompare.back(); + LTSN *CurChild = CurLink.first; + ToCompare.pop_back(); + + // Compare each pair of children of the union node + bool Merged = false; + for (Link VisitedLink : Visited) { + LTSN *VisitedChild = VisitedLink.first; + + revng_log(Log, "Is " << CurChild->ID << " == " << VisitedChild->ID); + revng_assert(VisitedChild != CurChild + or cmpEdgeTags(VisitedLink.second, CurLink.second) != 0); + + auto [IsMerged, MergedSubtree] = mergeIfTopologicallyEq(TS, + VisitedLink, + CurLink); + + if (IsMerged) { + TypeSystemChanged = true; + UnionNodeChanged = true; + Merged = true; + revng_log(Log, "Merged!"); + + bool SubtreeChanged = postProcessMerge(TS, MergedSubtree); + if (SubtreeChanged) { + // If the subtree was modified, re-enqueue the node + Visited.remove(VisitedLink); + ToCompare.insert(VisitedLink); + } + + // If the node was merged, stop comparing it with other children + break; + } + } + + if (not Merged) { + Visited.insert(CurLink); + revng_log(Log, "Child " << CurChild->ID << " not merged"); + } + } + + // Collapse the union node if we are left with only one member + if (UnionNodeChanged) { + CollapseSingleChild::collapseSingle(TS, UnionNode); + RemoveConflictingEdges::removeConflicts(TS, UnionNode); + } + } + } + + if (Log.isEnabled()) + TS.dumpDotOnFile("after-deduplicate-union-fields.dot"); + if (VerifyLog.isEnabled()) { + revng_assert(TS.verifyConsistency()); + revng_assert(TS.verifyInheritanceDAG()); + revng_assert(TS.verifyInheritanceTree()); + revng_assert(TS.verifyConflicts()); + } + + return TypeSystemChanged; +} + +} // end namespace dla diff --git a/lib/DataLayoutAnalysis/Middleend/DLARemoveConflictingEdges.cpp b/lib/DataLayoutAnalysis/Middleend/DLARemoveConflictingEdges.cpp index 711e66e7a..ee1f065d9 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLARemoveConflictingEdges.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLARemoveConflictingEdges.cpp @@ -24,10 +24,12 @@ static Logger<> Log("dla-remove-conflicting-edges"); namespace dla { +/// \brief Drop instance-at-offset-0 edges when they connect two nodes that +/// are also connected by an inheritance edge. bool RemoveConflictingEdges::removeConflicts(LayoutTypeSystem &TS, LayoutTypeSystemNode *Node) { bool Changed = false; - std::set InhNodes; + llvm::SmallPtrSet InhNodes; for (auto &L : Node->Successors) if (isInheritanceEdge(L)) InhNodes.insert(L.first); diff --git a/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp b/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp index c2a8b66b0..1287caf0e 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp @@ -18,6 +18,7 @@ const char CollapseCompatibleArrays::ID = 0; const char PropagateInheritanceToAccessors::ID = 0; const char ComputeNonInterferingComponents::ID = 0; const char CollapseSingleChild::ID = 0; +const char DeduplicateUnionFields::ID = 0; const char RemoveConflictingEdges::ID = 0; static Logger<> DLAStepManagerLog("dla-step-manager"); diff --git a/lib/DataLayoutAnalysis/Middleend/DLAStep.h b/lib/DataLayoutAnalysis/Middleend/DLAStep.h index c8651b4ff..f0cc9c2fc 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAStep.h +++ b/lib/DataLayoutAnalysis/Middleend/DLAStep.h @@ -246,6 +246,26 @@ public: virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; }; +/// dla::Step that merges structurally identical subtrees of an interfering +/// node. +class DeduplicateUnionFields : public Step { + static const char ID; + +public: + static const constexpr void *getID() { return &ID; } + + DeduplicateUnionFields() : + Step(ID, + // Dependencies + { ComputeNonInterferingComponents::getID() }, + // Invalidated + { ComputeNonInterferingComponents::getID() }) {} + + virtual ~DeduplicateUnionFields() override = default; + + virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; +}; + template bool intersect(IterT I1, IterT E1, IterT I2, IterT E2) { while ((I1 != E1) and (I2 != E2)) { diff --git a/tests/Unit/DLASteps.cpp b/tests/Unit/DLASteps.cpp index 23f59433e..f0af86ea7 100644 --- a/tests/Unit/DLASteps.cpp +++ b/tests/Unit/DLASteps.cpp @@ -425,20 +425,21 @@ BOOST_AUTO_TEST_CASE(CollapseSingleChild_multiParent) { /*offset=*/8U, /*size=*/0U); TS.addInheritanceLink(Parent2, Child1); - LTSN *Child2 = addInstanceAtOffset(TS, - Child1, - /*offset=*/0U, - /*size=*/8U); + /*LTSN *Child2 =*/addInstanceAtOffset(TS, + Child1, + /*offset=*/0U, + /*size=*/8U); // Run step + TS.dumpDotOnFile("bef.dot", true); runStep(TS); + TS.dumpDotOnFile("after.dot", true); // Check graph - revng_check(TS.getNumLayouts() == 4); + revng_check(TS.getNumLayouts() == 3); checkNode(TS, Parent1, 0, InterferingChildrenInfo::Unknown, { 0 }); checkNode(TS, Parent2, 0, InterferingChildrenInfo::Unknown, { 1 }); - checkNode(TS, Child1, 0, InterferingChildrenInfo::Unknown, { 2 }); - checkNode(TS, Child2, 8, InterferingChildrenInfo::Unknown, { 3 }); + checkNode(TS, Child1, 8, InterferingChildrenInfo::Unknown, { 2, 3 }); } ///\brief Test the case in which there are multiple levels of single-childs to @@ -657,6 +658,216 @@ BOOST_AUTO_TEST_CASE(PropagateToAccessors) { checkNode(TS, NodeF, 8, InterferingChildrenInfo::Unknown, { 5, 6 }); } +// ----------------- Deduplicate Union Fields -------------- + +BOOST_AUTO_TEST_CASE(DeduplicateUnionFields_basic) { + dla::LayoutTypeSystem TS; + + // Build TS + LTSN *NodeA = createRoot(TS); + LTSN *NodeB = addInheritance(TS, NodeA); + LTSN *NodeD = addInstanceAtOffset(TS, NodeB, /*offset=*/0, /*size=*/8); + LTSN *NodeE = addInstanceAtOffset(TS, NodeB, /*offset=*/8, /*size=*/8); + + LTSN *NodeC = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/16); + /*LTSN *NodeF*/ addInstanceAtOffset(TS, NodeC, /*offset=*/0, /*size=*/8); + /*LTSN *NodeG*/ addInstanceAtOffset(TS, NodeC, /*offset=*/8, /*size=*/8); + + LTSN *Node1 = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/12); + LTSN *Node2 = addInstanceAtOffset(TS, Node1, /*offset=*/4, /*size=*/8); + LTSN *Node3 = addInstanceAtOffset(TS, Node2, /*offset=*/0, /*size=*/4); + LTSN *Node4 = addInstanceAtOffset(TS, Node2, /*offset=*/4, /*size=*/4); + + // Run steps + VerifyLog.enable(); + dla::StepManager SM; + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + + SM.run(TS); + + // Compress the equivalence classes + dla::VectEqClasses &Eq = TS.getEqClasses(); + Eq.compress(); + + // Check TS + revng_check(TS.getNumLayouts() == 8); + checkNode(TS, NodeA, 16, AllChildrenAreInterfering, { 0 }); + checkNode(TS, NodeB, 16, AllChildrenAreNonInterfering, { 1, 4 }); + checkNode(TS, NodeD, 8, AllChildrenAreNonInterfering, { 2, 5 }); + checkNode(TS, NodeE, 8, AllChildrenAreNonInterfering, { 3, 6 }); + checkNode(TS, Node1, 12, AllChildrenAreNonInterfering, { 7 }); + checkNode(TS, Node2, 8, AllChildrenAreNonInterfering, { 8 }); + checkNode(TS, Node3, 4, AllChildrenAreNonInterfering, { 9 }); + checkNode(TS, Node4, 4, AllChildrenAreNonInterfering, { 10 }); +} + +BOOST_AUTO_TEST_CASE(DeduplicateUnionFields_diamond) { + dla::LayoutTypeSystem TS; + + // Build TS + LTSN *NodeA = createRoot(TS); + LTSN *NodeB = addInheritance(TS, NodeA); + NodeB->Size = 8; + /*LTSN *NodeC =*/addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/8); + LTSN *NodeD = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + LTSN *NodeE = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + LTSN *NodeF = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + LTSN *NodeG = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + + LTSN *NodeH = addInstanceAtOffset(TS, NodeD, /*offset=*/0, /*size=*/8); + OffsetExpression OE{}; + OE.Offset = 0; + TS.addInstanceLink(NodeE, NodeH, std::move(OE)); + + LTSN *NodeI = addInstanceAtOffset(TS, NodeF, /*offset=*/0, /*size=*/8); + OffsetExpression OE2{}; + OE2.Offset = 0; + TS.addInstanceLink(NodeG, NodeI, std::move(OE2)); + + // Run steps + VerifyLog.enable(); + dla::StepManager SM; + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + + SM.run(TS); + + // Compress the equivalence classes + dla::VectEqClasses &Eq = TS.getEqClasses(); + Eq.compress(); + + // Check TS + revng_check(TS.getNumLayouts() == 1); + checkNode(TS, + NodeA, + 8, + AllChildrenAreNonInterfering, + { 0, 1, 2, 3, 4, 5, 6, 7, 8 }); +} + +BOOST_AUTO_TEST_CASE(DeduplicateUnionFields_commonNodeSymmetric) { + dla::LayoutTypeSystem TS; + + // Build TS + LTSN *NodeUnion = createRoot(TS); + LTSN *NodeA = addInstanceAtOffset(TS, NodeUnion, /*offset=*/0, /*size=*/0); + LTSN *NodeB = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/8); + LTSN *NodeC = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + LTSN *NodeD = addInstanceAtOffset(TS, NodeC, /*offset=*/8, /*size=*/8); + + LTSN *NodeA1 = addInstanceAtOffset(TS, NodeUnion, /*offset=*/0, /*size=*/0); + LTSN *NodeC1 = addInstanceAtOffset(TS, NodeA1, /*offset=*/0, /*size=*/0); + /*LTSN *NodeD1 =*/addInstanceAtOffset(TS, NodeC1, /*offset=*/8, /*size=*/8); + OffsetExpression OE{}; + OE.Offset = 0; + TS.addInstanceLink(NodeA1, NodeB, std::move(OE)); + + // Run steps + VerifyLog.enable(); + dla::StepManager SM; + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + SM.run(TS); + + // Compress the equivalence classes + dla::VectEqClasses &Eq = TS.getEqClasses(); + Eq.compress(); + + // Check TS + revng_check(TS.getNumLayouts() == 4); + checkNode(TS, NodeUnion, 16, AllChildrenAreInterfering, { 0, 1, 5 }); + checkNode(TS, NodeB, 8, AllChildrenAreNonInterfering, { 2 }); + checkNode(TS, NodeC, 16, AllChildrenAreNonInterfering, { 3, 6 }); + checkNode(TS, NodeD, 8, AllChildrenAreNonInterfering, { 4, 7 }); +} + +BOOST_AUTO_TEST_CASE(DeduplicateUnionFields_commonNodeAsymmetric) { + dla::LayoutTypeSystem TS; + + // Build TS + LTSN *NodeUnion = createRoot(TS); + LTSN *NodeA = addInstanceAtOffset(TS, NodeUnion, /*offset=*/0, /*size=*/0); + /*LTSN *NodeB =*/addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/8); + LTSN *NodeC = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + LTSN *NodeD = addInstanceAtOffset(TS, NodeC, /*offset=*/4, /*size=*/8); + + LTSN *NodeA1 = addInstanceAtOffset(TS, NodeUnion, /*offset=*/0, /*size=*/0); + LTSN *NodeC1 = addInstanceAtOffset(TS, NodeA1, /*offset=*/0, /*size=*/0); + /*LTSN *NodeD1 =*/addInstanceAtOffset(TS, NodeC1, /*offset=*/4, /*size=*/8); + OffsetExpression OE{}; + OE.Offset = 0; + TS.addInstanceLink(NodeA1, NodeD, std::move(OE)); + + // Run steps + VerifyLog.enable(); + dla::StepManager SM; + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + + SM.run(TS); + + // Compress the equivalence classes + dla::VectEqClasses &Eq = TS.getEqClasses(); + Eq.compress(); + + // Check TS + revng_check(TS.getNumLayouts() == 3); + checkNode(TS, NodeUnion, 12, AllChildrenAreInterfering, { 0, 1, 5 }); + checkNode(TS, NodeC, 12, AllChildrenAreNonInterfering, { 3, 6 }); + checkNode(TS, NodeD, 8, AllChildrenAreNonInterfering, { 2, 4, 7 }); +} + +BOOST_AUTO_TEST_CASE(DeduplicateUnionFields_commonNodeAsymmetricCollapse) { + dla::LayoutTypeSystem TS; + + // Build TS + LTSN *NodeUnion = createRoot(TS); + LTSN *NodeA = addInstanceAtOffset(TS, NodeUnion, /*offset=*/0, /*size=*/0); + /*LTSN *NodeB =*/addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/8); + LTSN *NodeC = addInstanceAtOffset(TS, NodeA, /*offset=*/0, /*size=*/0); + LTSN *NodeD = addInstanceAtOffset(TS, NodeC, /*offset=*/0, /*size=*/8); + + LTSN *NodeA1 = addInstanceAtOffset(TS, NodeUnion, /*offset=*/0, /*size=*/0); + LTSN *NodeC1 = addInstanceAtOffset(TS, NodeA1, /*offset=*/0, /*size=*/0); + /*LTSN *NodeD1 =*/addInstanceAtOffset(TS, NodeC1, /*offset=*/0, /*size=*/8); + OffsetExpression OE{}; + OE.Offset = 0; + TS.addInstanceLink(NodeA1, NodeD, std::move(OE)); + + // Run steps + VerifyLog.enable(); + dla::StepManager SM; + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + + SM.run(TS); + + // Compress the equivalence classes + dla::VectEqClasses &Eq = TS.getEqClasses(); + Eq.compress(); + + // Check TS + revng_check(TS.getNumLayouts() == 3); + checkNode(TS, NodeUnion, 8, AllChildrenAreInterfering, { 0, 1, 5 }); + checkNode(TS, NodeC, 8, AllChildrenAreNonInterfering, { 3, 6 }); + checkNode(TS, NodeD, 8, AllChildrenAreNonInterfering, { 2, 4, 7 }); +} + // ----------------- Remove Conflicting edges -------------- BOOST_AUTO_TEST_CASE(RemoveConflictingEdges_basic) {