From f5ef671ecceaeb82c30f57a615cb3d1c15f15b2c Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Mon, 5 Sep 2022 17:36:40 +0200 Subject: [PATCH 1/9] Enforce revng check-conventions --- include/revng-c/TypeNames/ModelTypeNames.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/include/revng-c/TypeNames/ModelTypeNames.h b/include/revng-c/TypeNames/ModelTypeNames.h index 44b1794cf..7fc51c16c 100644 --- a/include/revng-c/TypeNames/ModelTypeNames.h +++ b/include/revng-c/TypeNames/ModelTypeNames.h @@ -24,13 +24,14 @@ constexpr const char *const ArrayWrapperFieldName = "the_array"; /// Print a string containing the C Type name of \a QT and a /// (possibly empty) \a InstanceName . extern tokenTypes::TypeString getNamedCInstance(const model::QualifiedType &QT, - llvm::StringRef InstanceName, - bool TypeDefinition); + llvm::StringRef InstanceName, + bool TypeDefinition); /// Return an escaped name for the type /// \note If T is a function type, the appropriate function typename will be /// returned -extern tokenTypes::TypeString getTypeName(const model::Type &T, bool TypeDefinition); +extern tokenTypes::TypeString +getTypeName(const model::Type &T, bool TypeDefinition); inline tokenTypes::TypeString getTypeName(const model::QualifiedType &QT, bool TypeDefinition) { @@ -44,19 +45,22 @@ extern tokenTypes::TypeString getArrayWrapper(const model::QualifiedType &QT); /// Return the name of the type returned by \a F /// \note If F returns more than one value, the name of the wrapping struct /// will be returned. -extern tokenTypes::TypeString getReturnTypeName(const model::RawFunctionType &F); +extern tokenTypes::TypeString +getReturnTypeName(const model::RawFunctionType &F); /// Return the name of the array wrapper that wraps \a QT (QT must be /// an array). /// \note If F returns an array, the name of the wrapping struct will be /// returned. -extern tokenTypes::TypeString getReturnTypeName(const model::CABIFunctionType &F); +extern tokenTypes::TypeString +getReturnTypeName(const model::CABIFunctionType &F); /// Return the name of the \a Index -th field of the struct returned /// by \a F. /// \note F must be returning more than one value, otherwise /// there is no wrapping struct. -extern tokenTypes::TypeString getReturnField(const model::RawFunctionType &F, size_t Index); +extern tokenTypes::TypeString +getReturnField(const model::RawFunctionType &F, size_t Index); /// Print the function prototype (without any trailing ';') of \a FT /// using \a FunctionName as the function's name. If the return value From fb652dedb8bd54020101f61ea5aa97a3a4f9e3dc Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Wed, 27 Jul 2022 12:19:32 +0200 Subject: [PATCH 2/9] Add dla::OffsetExpression::verify() --- .../DataLayoutAnalysis/DLATypeSystem.h | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h index 6795d681b..a93679763 100644 --- a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h +++ b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h @@ -41,6 +41,33 @@ struct OffsetExpression { operator<=>(const OffsetExpression &Other) const = default; void print(llvm::raw_ostream &OS) const; + + bool verify() const debug_function { + if (Offset < 0) + return false; + + if (Strides.size() != TripCounts.size()) + return false; + + int64_t PrevStride = std::numeric_limits::max(); + for (const auto &[Stride, MaybeTC] : llvm::zip_first(Strides, TripCounts)) { + + // Strides should go from larger to smaller + if (PrevStride < Stride) + return false; + + // Arrays with unknown length are considered as if they had one element + auto TripCount = MaybeTC.value_or(1); + // If the current stride times the current trip count is larger than the + // previous stride, it would trip over the element of the outer array. + if (Stride * TripCount > PrevStride) + return false; + + PrevStride = Stride; + } + + return true; + } }; // end class OffsetExpression class TypeLinkTag { From 3ec19a4a93f060b8040fab117b8bd975728b1fa6 Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Wed, 27 Jul 2022 12:19:59 +0200 Subject: [PATCH 3/9] Add dla::OffsetExpression::append() static method --- include/revng-c/DataLayoutAnalysis/DLATypeSystem.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h index a93679763..83f673db9 100644 --- a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h +++ b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h @@ -68,6 +68,18 @@ struct OffsetExpression { return true; } + + static OffsetExpression + append(OffsetExpression LHS, const OffsetExpression &RHS) { + revng_assert(LHS.verify()); + revng_assert(RHS.verify()); + LHS.Offset += RHS.Offset; + LHS.Strides.append(RHS.Strides); + LHS.TripCounts.append(RHS.TripCounts); + revng_assert(LHS.verify()); + return LHS; + } + }; // end class OffsetExpression class TypeLinkTag { @@ -132,6 +144,7 @@ public: friend void writeToLog(Logger &L, const dla::TypeLinkTag &T, int /* Ignore */); + }; // end class TypeLinkTag class LayoutTypeSystem; From 38e46587629767890a3f2eedb3fa5d422dd98377 Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Thu, 11 Aug 2022 10:49:08 +0200 Subject: [PATCH 4/9] Add LayoutTypeSystem::NeighborIterator type --- include/revng-c/DataLayoutAnalysis/DLATypeSystem.h | 5 +++-- lib/DataLayoutAnalysis/DLATypeSystem.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h index 83f673db9..2efde3891 100644 --- a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h +++ b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h @@ -239,6 +239,7 @@ public: using Node = LayoutTypeSystemNode; using NodePtr = LayoutTypeSystemNode *; using NodeUniquePtr = std::unique_ptr; + using NeighborIterator = LayoutTypeSystemNode::NeighborIterator; static dla::LayoutTypeSystem::NodePtr getNodePtr(const dla::LayoutTypeSystem::NodeUniquePtr &P) { @@ -333,12 +334,12 @@ public: void moveEdgeTarget(LayoutTypeSystemNode *OldTgt, LayoutTypeSystemNode *NewTgt, - LayoutTypeSystemNode::NeighborIterator InverseEdgeIt, + NeighborIterator InverseEdgeIt, int64_t OffsetToSum); void moveEdgeSource(LayoutTypeSystemNode *OldSrc, LayoutTypeSystemNode *NewSrc, - LayoutTypeSystemNode::NeighborIterator EdgeIt, + NeighborIterator EdgeIt, int64_t OffsetToSum); private: diff --git a/lib/DataLayoutAnalysis/DLATypeSystem.cpp b/lib/DataLayoutAnalysis/DLATypeSystem.cpp index e86becc58..9dfbaef9b 100644 --- a/lib/DataLayoutAnalysis/DLATypeSystem.cpp +++ b/lib/DataLayoutAnalysis/DLATypeSystem.cpp @@ -330,7 +330,7 @@ void LayoutTypeSystem::removeNode(LayoutTypeSystemNode *ToRemove) { NodeAllocator.Deallocate(ToRemove); } -using NeighborIterator = LayoutTypeSystemNode::NeighborIterator; +using NeighborIterator = LayoutTypeSystem::NeighborIterator; static void moveEdgeTargetWithoutSumming(LayoutTypeSystemNode *OldTgt, LayoutTypeSystemNode *NewTgt, From 695c1529dbd09970b2d96a506b7f618d6e58507f Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Mon, 12 Sep 2022 17:52:06 +0200 Subject: [PATCH 5/9] dla::LayoutTypeSystem add eraseEdge method --- include/revng-c/DataLayoutAnalysis/DLATypeSystem.h | 3 +++ lib/DataLayoutAnalysis/DLATypeSystem.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h index 2efde3891..38e793738 100644 --- a/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h +++ b/include/revng-c/DataLayoutAnalysis/DLATypeSystem.h @@ -342,6 +342,9 @@ public: NeighborIterator EdgeIt, int64_t OffsetToSum); + NeighborIterator + eraseEdge(LayoutTypeSystemNode *Src, NeighborIterator EdgeIt); + private: uint64_t NID = 0ULL; diff --git a/lib/DataLayoutAnalysis/DLATypeSystem.cpp b/lib/DataLayoutAnalysis/DLATypeSystem.cpp index 9dfbaef9b..8f3cf8188 100644 --- a/lib/DataLayoutAnalysis/DLATypeSystem.cpp +++ b/lib/DataLayoutAnalysis/DLATypeSystem.cpp @@ -443,6 +443,18 @@ void LayoutTypeSystem::moveEdgeSource(LayoutTypeSystemNode *OldSrc, } } +NeighborIterator LayoutTypeSystem::eraseEdge(LayoutTypeSystemNode *Src, + NeighborIterator EdgeIt) { + LayoutTypeSystemNode *Tgt = EdgeIt->first; + + // Erase the inverse edge from Tgt to Src + bool Erased = Tgt->Predecessors.erase({ Src, EdgeIt->second }); + revng_assert(Erased); + + // Erase the actual forward edge from Src to Tgt + return Src->Successors.erase(EdgeIt); +} + static Logger<> VerifyDLALog("dla-verify-strict"); bool LayoutTypeSystem::verifyConsistency() const { From 47c06350ca72ee8b1fba489a447b70c98e514c2f Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Mon, 5 Sep 2022 17:12:58 +0200 Subject: [PATCH 6/9] FieldSizeComputation: add #include --- lib/DataLayoutAnalysis/Middleend/FieldSizeComputation.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/DataLayoutAnalysis/Middleend/FieldSizeComputation.h b/lib/DataLayoutAnalysis/Middleend/FieldSizeComputation.h index b9e31fa01..eb745fac2 100644 --- a/lib/DataLayoutAnalysis/Middleend/FieldSizeComputation.h +++ b/lib/DataLayoutAnalysis/Middleend/FieldSizeComputation.h @@ -4,6 +4,8 @@ // Copyright (c) rev.ng Labs Srl. See LICENSE.md for details. // +#include + namespace dla { struct LayoutTypeSystemNode; From 8ff8a795e4eb228ece82e7bcad98702be43fe412 Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Wed, 10 Aug 2022 12:32:40 +0200 Subject: [PATCH 7/9] DLA: Add DecomposeStridedEdges dla::Step --- lib/DataLayoutAnalysis/CMakeLists.txt | 1 + lib/DataLayoutAnalysis/Middleend/DLAStep.cpp | 1 + lib/DataLayoutAnalysis/Middleend/DLAStep.h | 25 ++++- .../Middleend/DecomposeStridedEdges.cpp | 95 +++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 lib/DataLayoutAnalysis/Middleend/DecomposeStridedEdges.cpp diff --git a/lib/DataLayoutAnalysis/CMakeLists.txt b/lib/DataLayoutAnalysis/CMakeLists.txt index 7d9262205..7b9f94f30 100644 --- a/lib/DataLayoutAnalysis/CMakeLists.txt +++ b/lib/DataLayoutAnalysis/CMakeLists.txt @@ -13,6 +13,7 @@ revng_add_analyses_library( Middleend/DLAComputeUpperMemberAccess.cpp Middleend/DLAPruneLayoutNodesWithoutLayout.cpp Middleend/DLACollapseSingleChild.cpp + Middleend/DecomposeStridedEdges.cpp Middleend/DeduplicateFields.cpp Middleend/DLAStep.cpp Middleend/FieldSizeComputation.cpp diff --git a/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp b/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp index b34064d83..1f0fe3523 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp @@ -14,6 +14,7 @@ const char CollapseInstanceAtOffset0SCC::ID = 0; const char CollapseSingleChild::ID = 0; const char ComputeNonInterferingComponents::ID = 0; const char ComputeUpperMemberAccesses::ID = 0; +const char DecomposeStridedEdges::ID = 0; const char DeduplicateFields::ID = 0; const char MergePointerNodes::ID = 0; const char PruneLayoutNodesWithoutLayout::ID = 0; diff --git a/lib/DataLayoutAnalysis/Middleend/DLAStep.h b/lib/DataLayoutAnalysis/Middleend/DLAStep.h index 24e811991..f0082e5fb 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAStep.h +++ b/lib/DataLayoutAnalysis/Middleend/DLAStep.h @@ -139,6 +139,21 @@ public: virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; }; +/// dla::Step that takes all strided edges and decompose in edges with only one +/// stride layer +class DecomposeStridedEdges : public Step { + static const char ID; + +public: + static const constexpr void *getID() { return &ID; } + + inline DecomposeStridedEdges(); + + virtual ~DecomposeStridedEdges() override = default; + + virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; +}; + /// dla::Step that computes and propagates informations on accesses and type /// sizes. class ComputeUpperMemberAccesses : public Step { @@ -252,13 +267,21 @@ public: // Dependencies {}, // Invalidated - {}) {} + { DecomposeStridedEdges::getID() }) {} virtual ~DeduplicateFields() override = default; virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; }; +inline DecomposeStridedEdges::DecomposeStridedEdges() : + Step(ID, + // Dependencies + { ComputeUpperMemberAccesses::getID() }, + // Invalidated + { DeduplicateFields::getID() }) { +} + template bool intersect(IterT I1, IterT E1, IterT I2, IterT E2) { while ((I1 != E1) and (I2 != E2)) { diff --git a/lib/DataLayoutAnalysis/Middleend/DecomposeStridedEdges.cpp b/lib/DataLayoutAnalysis/Middleend/DecomposeStridedEdges.cpp new file mode 100644 index 000000000..b94deacbe --- /dev/null +++ b/lib/DataLayoutAnalysis/Middleend/DecomposeStridedEdges.cpp @@ -0,0 +1,95 @@ +// +// Copyright (c) rev.ng Labs Srl. See LICENSE.md for details. +// + +#include "DLAStep.h" +#include "FieldSizeComputation.h" + +namespace dla { + +bool DecomposeStridedEdges::runOnTypeSystem(LayoutTypeSystem &TS) { + if (VerifyLog.isEnabled()) + revng_assert(TS.verifyDAG()); + + bool Changed = false; + + for (LayoutTypeSystemNode *Parent : llvm::nodes(&TS)) { + + using DLAGraph = llvm::GraphTraits; + auto EdgeIt = DLAGraph::child_edge_begin(Parent); + auto EdgeEnd = DLAGraph::child_edge_end(Parent); + auto EdgeNext = DLAGraph::child_edge_end(Parent); + + for (; EdgeIt != EdgeEnd; EdgeIt = EdgeNext) { + EdgeNext = std::next(EdgeIt); + + auto &Edge = *EdgeIt; + if (not isInstanceEdge(Edge)) + continue; + + const auto &[Child, Tag] = Edge; + const auto &OffsetExpr = Tag->getOffsetExpr(); + auto NLayers = OffsetExpr.Strides.size(); + if (NLayers < 2) + continue; + + Changed = true; + + // Setup the chain of nodes across which we will build the new chain of + // single-layered strided edges. + llvm::SmallVector NodeChain; + { + NodeChain.reserve(NLayers + 1); + // The first node of the chain is the original child. + NodeChain.push_back(Child); + // Then we have to insert NLayers - 1 new artificial node. + const auto &NewNodes = TS.createArtificialLayoutTypes(NLayers - 1); + for (auto *New : NewNodes) + NodeChain.push_back(New); + // The last node of the chain is the original parent. + NodeChain.push_back(Parent); + } + + // From outer to inner + const auto &StridesTripCounts = llvm::zip_first(OffsetExpr.Strides, + OffsetExpr.TripCounts); + // From inner to outer + const auto &InToOut = llvm::reverse(StridesTripCounts); + + // Actually link the nodes in the chain with the new single-layered + // strided edges + for (const auto &Group : llvm::enumerate(InToOut)) { + // Build the strided offset expression of the new edge + OffsetExpression OE; + // Take the Strides and TripCounts from the current layer. + const auto &[S, TC] = Group.value(); + OE.Strides.push_back(S); + OE.TripCounts.push_back(TC); + // At the last iteration, representing the outermost array, copy the + // offset as well (all the other iterations will have offset 0) + if (Group.index() == NLayers - 1) + OE.Offset = OffsetExpr.Offset; + + // Set up the predecessor and successor of the new single-layered + // strided edge. + auto Idx = Group.index(); + LayoutTypeSystemNode *Pred = NodeChain[Idx + 1]; + LayoutTypeSystemNode *Succ = NodeChain[Idx]; + // Link them + const auto [Tag, New] = TS.addInstanceLink(Pred, Succ, std::move(OE)); + if (Pred != Parent) + Pred->Size = getFieldSize(Succ, Tag); + } + + // Remove the old strided edge + TS.eraseEdge(Parent, EdgeIt); + } + } + + if (VerifyLog.isEnabled()) + revng_assert(TS.verifyDAG()); + + return Changed; +} + +} // end namespace dla From f0a1903201b27af0f6d4b1496556c6ea530ead13 Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Thu, 11 Aug 2022 10:49:42 +0200 Subject: [PATCH 8/9] DLA: add ArrangeAccessesHierarchically dla::Step --- lib/DataLayoutAnalysis/CMakeLists.txt | 1 + .../ArrangeAccessesHierarchically.cpp | 547 ++++++++++++++++++ lib/DataLayoutAnalysis/Middleend/DLAStep.cpp | 4 +- lib/DataLayoutAnalysis/Middleend/DLAStep.h | 20 + .../Middleend/DeduplicateFields.cpp | 6 +- tests/unit/llvm-lit-tests/CheckDLA.ll.yml | 4 - 6 files changed, 574 insertions(+), 8 deletions(-) create mode 100644 lib/DataLayoutAnalysis/Middleend/ArrangeAccessesHierarchically.cpp diff --git a/lib/DataLayoutAnalysis/CMakeLists.txt b/lib/DataLayoutAnalysis/CMakeLists.txt index 7b9f94f30..986f226fe 100644 --- a/lib/DataLayoutAnalysis/CMakeLists.txt +++ b/lib/DataLayoutAnalysis/CMakeLists.txt @@ -8,6 +8,7 @@ revng_add_analyses_library( Frontend/DLACreateIntraProceduralTypes.cpp Frontend/DLATypeSystemBuilder.cpp Frontend/SCEVBaseAddressExplorer.cpp + Middleend/ArrangeAccessesHierarchically.cpp Middleend/CollapseSCC.cpp Middleend/DLAComputeNonInterferingComponents.cpp Middleend/DLAComputeUpperMemberAccess.cpp diff --git a/lib/DataLayoutAnalysis/Middleend/ArrangeAccessesHierarchically.cpp b/lib/DataLayoutAnalysis/Middleend/ArrangeAccessesHierarchically.cpp new file mode 100644 index 000000000..1257fa766 --- /dev/null +++ b/lib/DataLayoutAnalysis/Middleend/ArrangeAccessesHierarchically.cpp @@ -0,0 +1,547 @@ +// +// Copyright (c) rev.ng Labs Srl. See LICENSE.md for details. +// + +#include +#include + +#include "llvm/ADT/GraphTraits.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" + +#include "revng/ADT/GenericGraph.h" +#include "revng/Support/Debug.h" + +#include "DLAStep.h" +#include "FieldSizeComputation.h" + +static Logger<> Log{ "sort-accesses-hierarchically" }; + +namespace dla { + +// Returns true if N has an incoming instance strided edge +static bool hasStridedParent(const LayoutTypeSystemNode *N) { + using InstanceGraph = EdgeFilteredGraph; + using InverseInstance = llvm::Inverse; + for (const auto &Edge : llvm::children_edges(N)) + if (isInstanceEdge(Edge) + and not Edge.second->getOffsetExpr().Strides.empty()) + return true; + return false; +} + +// Returns true if N has an incoming instance edge from a node different from +// Parent +static bool hasAnotherParent(const LayoutTypeSystemNode *N, + const LayoutTypeSystemNode *Parent) { + using InstanceGraph = EdgeFilteredGraph; + using InverseInstance = llvm::Inverse; + for (const auto *P : llvm::children(N)) + if (P != Parent) + return true; + + return false; +} + +// Returns true if N has a pointer successor (predecessor if OnInverse is true) +template +static bool hasPointerSuccessor(const LayoutTypeSystemNode *N) { + using ConstPointerGraph = EdgeFilteredGraph; + using Directed = std::conditional_t, + ConstPointerGraph>; + + for ([[maybe_unused]] const auto *_ : llvm::children(N)) + return true; + + return false; +} + +// Returns true if N has an incoming pointer edge +static bool hasPointerParent(const LayoutTypeSystemNode *N) { + return hasPointerSuccessor(N); +} + +// Returns true if N has an outgoing pointer edge +static bool hasPointerChild(const LayoutTypeSystemNode *N) { + return hasPointerSuccessor(N); +} + +// Returns true if Child is an instance child of Parent that should not be +// destroyed by this dla::Step. +static bool isFixedChild(const LayoutTypeSystemNode *Parent, + const LayoutTypeSystemNode *Child) { + + // If is'a a leaf node it's fixed because it represents an access. + // If Child has an outgoing or incoming pointer edge, then it's fixed. + // If there's a strided edge going to Child, then Child is fixed. + // If Child has another Parent that is different from Parent, then it's + // fixed + if (isLeaf(Child) or hasPointerChild(Child) or hasPointerParent(Child) + or hasStridedParent(Child) or hasAnotherParent(Child, Parent)) { + return true; + } + + // TODO: what if the Child has many incoming edges, and one comes + // from Parent and the others all come from stuff that's between + // Parent and Child? (e.g. P -> C1; P -> C2; C1 -> C2;) In the + // current implementation C2 is considered fixed, because it has + // another parent different from P. + + // TODO: what if the Child has both a in incoming strided edge and + // a non-strided edge that come from Parent??? + //(e.g. P -offset-> C; P -strided-> C;) + // In the current implementation C is considered fixed, because it + // has an incoming strided edge. + + return false; +} + +using NonPointerFilterT = EdgeFilteredGraph; + +static llvm::SetVector +getVolatileChildren(LayoutTypeSystemNode *Parent) { + llvm::SetVector Result; + for (auto *Child : llvm::children(Parent)) + if (not isFixedChild(Parent, Child)) + Result.insert(Child); + return Result; +} + +using NeighborIterator = LayoutTypeSystem::NeighborIterator; + +// This struct represents if an edge in LayoutTypeSystem can be pushed down +// another edge in LayoutTypeSystem, along with the resulting OffsetExpression +// if the push down takes place. +struct PushThroughComparisonResult { + NeighborIterator ToPush; + NeighborIterator Through; + OffsetExpression OEAfterPush; +}; + +static PushThroughComparisonResult +makePushThroughComparisonResult(NeighborIterator ToBePushed, + NeighborIterator ToBePushedThrough) { + OffsetExpression Final; + + revng_assert(isInstanceEdge(*ToBePushed)); + revng_assert(isInstanceEdge(*ToBePushedThrough)); + + const auto &ToPushOE = ToBePushed->second->getOffsetExpr(); + const auto &ThroughOE = ToBePushedThrough->second->getOffsetExpr(); + + revng_assert(ToPushOE.Strides.empty() or ToPushOE.Strides.size() == 1); + revng_assert(ThroughOE.Strides.empty() or ThroughOE.Strides.size() == 1); + + // The edge to push through always has the larger offset. Just subtract the + // offset of the other edge. + Final.Offset = ToPushOE.Offset - ThroughOE.Offset; + revng_assert(Final.Offset >= 0LL); + + // If a strided edge is being pushed down another edge it means that the + // whole array represented by the edge to push is contained inside the + // element represented by the edge we're pushing it through (or a single + // element of the array represented by the edge we're pushing it through, if + // the edge we're pushing through is strided). + // If the edge being pushed down is not strided, strides and trip counts are + // empty. + // So in all cases we can preserve Strides and TripCounts. + Final.Strides = ToPushOE.Strides; + Final.TripCounts = ToPushOE.TripCounts; + + // Pushing through a strided edge, we need to compute the reminder of the + // offset inside the element of the array represented by the edge we're + // pushing through. + if (not ThroughOE.Strides.empty()) { + revng_assert(ThroughOE.Strides.size() == 1); + Final.Offset %= ThroughOE.Strides.front(); + } + + auto ThroughElemSize = ToBePushedThrough->first->Size; + auto PushedFieldSize = getFieldSize(ToBePushed->first, ToBePushed->second); + revng_assert(ThroughElemSize >= PushedFieldSize + Final.Offset); + + return PushThroughComparisonResult{ .ToPush = ToBePushed, + .Through = ToBePushedThrough, + .OEAfterPush = std::move(Final) }; +} + +// Compare the edges *AIt and *BIt to check if any of them can be pushed through +// the other. If so return proper info. +static std::optional +canPushThrough(const NeighborIterator &AIt, const NeighborIterator &BIt) { + // An edge can never be pushed through itself + if (AIt == BIt) + return std::nullopt; + + // If any edge is not an instance edge, the edges are not comparable. + if (not isInstanceEdge(*AIt) or not isInstanceEdge(*BIt)) + return std::nullopt; + + // Here both edges are instance edges. + + const auto &[AChild, ATag] = *AIt; + const auto &[BChild, BTag] = *BIt; + + if (isLeaf(AChild) and isLeaf(BChild)) + return std::nullopt; + + const auto &[AOffset, AStrides, ATripCounts] = ATag->getOffsetExpr(); + const auto &[BOffset, BStrides, BTripCounts] = BTag->getOffsetExpr(); + + revng_assert(AStrides.size() == ATripCounts.size()); + revng_assert(BStrides.size() == BTripCounts.size()); + revng_assert(AStrides.empty() or AStrides.size() == 1); + revng_assert(BStrides.empty() or BStrides.size() == 1); + + auto AFieldSize = getFieldSize(AChild, ATag); + auto BFieldSize = getFieldSize(BChild, BTag); + + uint64_t AFieldEnd = AOffset + AFieldSize; + uint64_t BFieldEnd = BOffset + BFieldSize; + + // If A and B occupy disjoint ranges of memory, none of them can be pushed + // through the other, so they are not comparable. + if (AFieldEnd <= (uint64_t) (BOffset)) + return std::nullopt; + + if (BFieldEnd <= (uint64_t) (AOffset)) + return std::nullopt; + + // Here we have the guarantee that A and B occupy partly overlapping ranges. + // They are not necessarily comparable yet, because they could still be partly + // overlapping and not included. + // Detect the partly overlapping case and return unordered for them + if (AOffset < BOffset and AFieldEnd < BFieldEnd) + return std::nullopt; + if (AOffset > BOffset and AFieldEnd > BFieldEnd) + return std::nullopt; + + // The two fields start at the same point and have the same size. None of them + // strictly includes the other, so they need to be structurally inspected to + // be merged. There's a separate pass doing this later in DLA, so we're not + // taking care of it now. + if (AOffset == BOffset and AFieldEnd == BFieldEnd) + return std::nullopt; + + // Here we have the guarantee that one of the following holds: + // - A is included in or occupies the same range as B + // - B is included in or occupies the same range as A + // Detect what is the case. + bool AIsOuter = AFieldSize > BFieldSize; + + auto OuterIt = AIsOuter ? AIt : BIt; + const auto &[Outer, OuterTag] = *OuterIt; + + // If the larger node is a leaf we cannot push the other down, so we bail out. + if (isLeaf(Outer)) + return std::nullopt; + + auto InnerIt = AIsOuter ? BIt : AIt; + const auto &[Inner, InnerTag] = *InnerIt; + + const auto &InnerOffset = InnerTag->getOffsetExpr().Offset; + const auto &[OuterOffset, OuterStrides, _] = OuterTag->getOffsetExpr(); + + auto InnerFieldSize = AIsOuter ? BFieldSize : AFieldSize; + auto OuterElemSize = OuterStrides.empty() ? Outer->Size : + OuterStrides.front(); + + // The inner fields starts at an higher offset (or equal) than the outer + auto OffsetAfterPush = InnerOffset - OuterOffset; + revng_assert(OffsetAfterPush >= 0LL); + + auto OffsetInElem = OffsetAfterPush % OuterElemSize; + auto EndByteInElem = OffsetInElem + InnerFieldSize; + if (EndByteInElem > Outer->Size) + return std::nullopt; + + return makePushThroughComparisonResult(InnerIt, OuterIt); +} + +// Helper ordering for NeighborIterators. We need it here because we need to use +// such iterators and keys in associative containers. +// Notice that this might have undefined behaviour if dereferncing either LHS +// or RHS is undefined behaviour itself. +// The bottom line is that we should never insert invalid iterators into +// associative containers. +static std::weak_ordering +operator<=>(const NeighborIterator &LHS, const NeighborIterator &RHS) { + const auto &[LHSSucc, LHSTag] = *LHS; + const auto &[RHSSucc, RHSTag] = *RHS; + if (auto Cmp = LHSSucc <=> RHSSucc; Cmp != 0) + return Cmp; + return LHSTag <=> RHSTag; +} + +static llvm::SmallPtrSet +absorbVolatileChildren(LayoutTypeSystem &TS, LayoutTypeSystemNode *Parent) { + + llvm::SmallPtrSet Absorbed; + + if (isLeaf(Parent)) + return Absorbed; + + revng_assert(not hasPointerChild(Parent)); + + for (auto VolatileChildren = getVolatileChildren(Parent); + not VolatileChildren.empty(); + VolatileChildren = getVolatileChildren(Parent)) { + + struct InstanceEdge { + OffsetExpression OE; + LayoutTypeSystemNode *Target; + + // Ordering and comparison + std::strong_ordering operator<=>(const InstanceEdge &) const = default; + bool operator==(const InstanceEdge &) const = default; + }; + + llvm::SetVector, + llvm::SmallSet> + CompoundEdges; + + for (const auto &[Child, Tag] : + llvm::children_edges(Parent)) { + // Don't mess up fixed children + if (not VolatileChildren.contains(Child)) + continue; + + OffsetExpression OE = Tag->getOffsetExpr(); + + // At this point we know that Child doesn't have incoming or + // outgoing pointer edges. So all the edges involving Child are + // instance edges. + for (const auto &[GrandChild, ChildTag] : + llvm::children_edges(Child)) { + revng_assert(not VolatileChildren.contains(GrandChild)); + auto New = InstanceEdge{ + OffsetExpression::append(OE, ChildTag->getOffsetExpr()), GrandChild + }; + CompoundEdges.insert(New); + } + } + + // Remove all volatile nodes + for (LayoutTypeSystemNode *Volatile : VolatileChildren) { + TS.removeNode(Volatile); + Absorbed.insert(Volatile); + } + + for (auto &[OffsetExpr, Target] : CompoundEdges.takeVector()) + TS.addInstanceLink(Parent, Target, std::move(OffsetExpr)); + } + return Absorbed; +} + +bool ArrangeAccessesHierarchically::runOnTypeSystem(LayoutTypeSystem &TS) { + if (VerifyLog.isEnabled()) + revng_assert(TS.verifyDAG()); + + bool Changed = false; + + { + std::set Visited; + for (LayoutTypeSystemNode *Root : llvm::nodes(&TS)) { + + if (Visited.contains(Root)) + continue; + + if (not isRoot(Root)) + continue; + + for (LayoutTypeSystemNode *Node : + llvm::post_order_ext(NonPointerFilterT(Root), Visited)) + absorbVolatileChildren(TS, Node); + } + } + + // This dla::Step will heavily mutate the graph while iterating on it. + // Despite thinking hard about it, we couldn't come up with a sane visit order + // that could be pre-computed and guaranteed to be stable during the mutation, + // or could be updated cheaply. + // So we precompute some kind of global topological ordering, and use it as a + // hard-coded ordering for analysys throughout the dla::Step. + // Then we go fixed-point until we don't have anything left to recompute, but + // we always follow this order because it's still better than iterating + // randomly, or with some other ordering that is not stable. + std::vector Queue; + std::set ToAnalyze; + { + + std::set Visited; + for (LayoutTypeSystemNode *Root : llvm::nodes(&TS)) { + revng_assert(Root != nullptr); + if (not isRoot(Root)) + continue; + + for (LayoutTypeSystemNode *N : + llvm::post_order_ext(NonPointerFilterT(Root), Visited)) { + revng_assert(N->Size); + Queue.push_back(N); + ToAnalyze.insert(N); + } + } + + // Reverse the whole thing so it's more RPOT like, which is a topological + // ordering of all the nodes in the graph. + // The edges will change during the algorithm, but there's nothing we can do + // about it. + std::reverse(Queue.begin(), Queue.end()); + } + + while (not ToAnalyze.empty()) { + + for (LayoutTypeSystemNode *Parent : Queue) { + + // Erase Parent from ToAnalyze + bool AlreadyAnalyzed = not ToAnalyze.erase(Parent); + + // If erasure failed, Parent wasn't in ToAnalyze, so it was already + // analyzed in a previous iteration, and we can skip it. + if (AlreadyAnalyzed) + continue; + + revng_log(Log, "Analyzing parent: " << Parent->ID); + + // If we reach this point we have to analyze Parent, to see if some of its + // Instance children can be pushed down some other of its Instance chilren + + // We want to look at all instance children edges of Parent, and compute a + // partial ordering between them, with the relationship "A should be + // pushed down B". + + // Before doing this we want to absorb potential other nodes that became + // volatile when pushing them down. + // For instance if we had A->B, A->C, B->C, and C was pushed down B, then + // C becomes volatile inside B. + // + { + auto Absorbed = absorbVolatileChildren(TS, Parent); + for (auto *A : Absorbed) + ToAnalyze.erase(A); + } + + // We now define a custom graph. + // Each node of this custom graph represents an instange edge of the + // original LayoutTypeSystem, whose predecessor is Parent. + // Edges of this custom graph represent a relationship between the custom + // nodes (representing edges). + // If a EdgeNode A has an edge towards an EdgeNode B it means that the + // edge represented by B can be pushed through the edge represented by A. + // Whenever the graph is build, we also attach to each edge of the custom + // graph an OffsetExpression, that represents the computed final offset + // after the push down. + using EdgeNode = BidirectionalNode; + using EdgeInclusionGraph = GenericGraph; + EdgeInclusionGraph EdgeInclusion; + + using GT = llvm::GraphTraits; + auto AIt = GT::child_edge_begin(Parent); + auto ChildEnd = GT::child_edge_end(Parent); + + // Build an EdgeNode for each instance edge outgoing from Parent. + std::map ItEdgeNodes; + for (; AIt != ChildEnd; ++AIt) { + if (isInstanceEdge(*AIt)) + ItEdgeNodes[AIt] = EdgeInclusion.addNode(AIt); + } + + // Compute the relationships that tell us if an edge can be pushed through + // another. + AIt = GT::child_edge_begin(Parent); + for (; AIt != ChildEnd; ++AIt) { + if (not isInstanceEdge(*AIt)) + continue; + + // If for some reason we find a non-fixed child it means that we + // probably need to re-think this DLAStep in a fixed-point fashion + // across the first part and the second push-down part. + revng_assert(isFixedChild(Parent, AIt->first)); + + for (auto BIt = std::next(AIt); BIt != ChildEnd; ++BIt) { + + auto MaybePushThrough = canPushThrough(AIt, BIt); + if (not MaybePushThrough.has_value()) + continue; + + auto &[ToPush, Through, OEAfterPush] = MaybePushThrough.value(); + + ItEdgeNodes.at(Through)->addSuccessor(ItEdgeNodes.at(ToPush), + OEAfterPush); + + revng_log(Log, "======================================"); + revng_log(Log, "Through: " << Through->first->ID); + revng_log(Log, + "Through Off: " << Through->second->getOffsetExpr().Offset); + if (not Through->second->getOffsetExpr().Strides.empty()) + revng_log(Log, + "Through Stride: " + << Through->second->getOffsetExpr().Strides.front()); + revng_log(Log, "-----"); + revng_log(Log, "ToPush: " << ToPush->first->ID); + revng_log(Log, + "ToPush Off: " << ToPush->second->getOffsetExpr().Offset); + if (not ToPush->second->getOffsetExpr().Strides.empty()) + revng_log(Log, + "ToPush Stride: " + << ToPush->second->getOffsetExpr().Strides.front()); + revng_log(Log, "-----"); + revng_log(Log, "Final Off: " << OEAfterPush.Offset); + if (not OEAfterPush.Strides.empty()) + revng_log(Log, "Final Stride: " << OEAfterPush.Strides.front()); + } + } + + llvm::SmallSet PushedEdges; + for (auto *EdgeNodeToPushThrough : EdgeInclusion.nodes()) { + + // If EdgeNodeToPushThrough has some predecessors, it means that there + // are other edges across which it should be pushed through. At this + // point we don't want to push the others down EdgeNodeToPushThrough, + // because that operation could change the overall result on the graph. + // So we back off. The other edges that could be pushed down through + // EdgeNodeToPushThrough will be resolved at a later time. + if (not EdgeNodeToPushThrough->predecessors().empty()) + continue; + + // If EdgeNodeToPushThrough has no successors, there is no other edge to + // push through it, so we just skip it. + if (EdgeNodeToPushThrough->successors().empty()) + continue; + + auto &ToPushThroughEdgeIt = EdgeNodeToPushThrough->data(); + auto *ToPushThrough = ToPushThroughEdgeIt->first; + ToAnalyze.insert(ToPushThrough); + + for (auto &Edge : EdgeNodeToPushThrough->successor_edges()) { + auto &[EdgeNodeToPushDown, FinalOE] = Edge; + auto &PushedEdgeIt = EdgeNodeToPushDown->data(); + PushedEdges.insert(PushedEdgeIt); + auto *ToPushDown = PushedEdgeIt->first; + + revng_assert(not isLeaf(ToPushThrough)); + TS.addInstanceLink(ToPushThrough, ToPushDown, std::move(FinalOE)); + } + } + + // Now finally clean up the edges that were pushed down. + for (const auto &PushedEdge : PushedEdges) + TS.eraseEdge(Parent, PushedEdge); + } + } + + if (VerifyLog.isEnabled()) + revng_assert(TS.verifyDAG()); + + return Changed; +} + +} // end namespace dla diff --git a/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp b/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp index 1f0fe3523..a993c3ddf 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DLAStep.cpp @@ -9,6 +9,7 @@ namespace dla { +const char ArrangeAccessesHierarchically::ID = 0; const char CollapseEqualitySCC::ID = 0; const char CollapseInstanceAtOffset0SCC::ID = 0; const char CollapseSingleChild::ID = 0; @@ -79,8 +80,9 @@ void StepManager::run(LayoutTypeSystem &TS) { TS.dumpDotOnFile("type-system-0.dot", true); for (auto &S : Schedule) { S->runOnTypeSystem(TS); + ++x; if (DLADumpDot.isEnabled()) { - std::string DotName = "type-system-" + std::to_string(++x) + ".dot"; + std::string DotName = "type-system-" + std::to_string(x) + ".dot"; TS.dumpDotOnFile(DotName.c_str(), true); } } diff --git a/lib/DataLayoutAnalysis/Middleend/DLAStep.h b/lib/DataLayoutAnalysis/Middleend/DLAStep.h index f0082e5fb..47939e5f1 100644 --- a/lib/DataLayoutAnalysis/Middleend/DLAStep.h +++ b/lib/DataLayoutAnalysis/Middleend/DLAStep.h @@ -254,6 +254,26 @@ public: virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; }; +/// dla::Step that tries to pushes down instance edges that are actually part of +/// a child node. +class ArrangeAccessesHierarchically : public Step { + static const char ID; + +public: + static const constexpr void *getID() { return &ID; } + + ArrangeAccessesHierarchically() : + Step(ID, + { ComputeUpperMemberAccesses::getID(), + PruneLayoutNodesWithoutLayout::getID() }, + // Invalidated + {}) {} + + virtual ~ArrangeAccessesHierarchically() override = default; + + virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override; +}; + /// dla::Step that merges structurally identical subtrees of an interfering /// node. class DeduplicateFields : public Step { diff --git a/lib/DataLayoutAnalysis/Middleend/DeduplicateFields.cpp b/lib/DataLayoutAnalysis/Middleend/DeduplicateFields.cpp index c1a16ed76..c891f163f 100644 --- a/lib/DataLayoutAnalysis/Middleend/DeduplicateFields.cpp +++ b/lib/DataLayoutAnalysis/Middleend/DeduplicateFields.cpp @@ -405,7 +405,7 @@ bool DeduplicateFields::runOnTypeSystem(LayoutTypeSystem &TS) { // two nodes, we don't have to update other links in the worklist. llvm::SmallSetVector FieldsToCompare; llvm::SmallSet OriginalFields; - llvm::SmallSet AnalyzedNodesNotMerged; + llvm::SmallSetVector AnalyzedNodesNotMerged; // We keep a separate list of successors since we might need to re-enqueue // some of them. @@ -511,7 +511,7 @@ bool DeduplicateFields::runOnTypeSystem(LayoutTypeSystem &TS) { // want it to be processed again. bool Erased = FieldsToCompare.remove(ErasedNode); FieldsMerged |= Erased; - Erased = AnalyzedNodesNotMerged.erase(ErasedNode); + Erased = AnalyzedNodesNotMerged.remove(ErasedNode); AnalyzedNotMergedInvalidated |= Erased; } @@ -537,7 +537,7 @@ bool DeduplicateFields::runOnTypeSystem(LayoutTypeSystem &TS) { "Is an original field. Re-enqueue it for " "comparison"); FieldsToCompare.insert(PreservedNode); - bool Erased = AnalyzedNodesNotMerged.erase(PreservedNode); + bool Erased = AnalyzedNodesNotMerged.remove(PreservedNode); AnalyzedNotMergedInvalidated |= Erased; } } diff --git a/tests/unit/llvm-lit-tests/CheckDLA.ll.yml b/tests/unit/llvm-lit-tests/CheckDLA.ll.yml index 63222b2a2..ae26f5e0a 100644 --- a/tests/unit/llvm-lit-tests/CheckDLA.ll.yml +++ b/tests/unit/llvm-lit-tests/CheckDLA.ll.yml @@ -14,8 +14,6 @@ Types: UnqualifiedType: "/Types/StructType-14349066955625060511" Qualifiers: - Kind: Pointer - - Type: - UnqualifiedType: "/Types/PrimitiveType-1032" - Kind: StructType ID: 14349066955625060511 Fields: @@ -28,8 +26,6 @@ Types: UnqualifiedType: "/Types/StructType-14349066955625060511" Qualifiers: - Kind: Pointer - - Type: - UnqualifiedType: "/Types/PrimitiveType-1032" - Kind: RawFunctionType ID: 6646220838590018230 Arguments: From e47733eb66563c23e1942e1f183349e90079bed4 Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Wed, 14 Sep 2022 11:46:24 +0200 Subject: [PATCH 9/9] DLA: new Middleend pipeline --- lib/DataLayoutAnalysis/DLAPass.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/DataLayoutAnalysis/DLAPass.cpp b/lib/DataLayoutAnalysis/DLAPass.cpp index 211054c2b..7cb9ebbf2 100644 --- a/lib/DataLayoutAnalysis/DLAPass.cpp +++ b/lib/DataLayoutAnalysis/DLAPass.cpp @@ -56,8 +56,10 @@ 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()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); + revng_check(SM.addStep()); revng_check(SM.addStep()); revng_check(SM.addStep()); SM.run(TS);