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) {