mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
Merge feature/dla-arrange-accesses-hierarchically
This commit is contained in:
@@ -41,6 +41,45 @@ 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<int64_t>::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;
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -105,6 +144,7 @@ public:
|
||||
|
||||
friend void
|
||||
writeToLog(Logger<true> &L, const dla::TypeLinkTag &T, int /* Ignore */);
|
||||
|
||||
}; // end class TypeLinkTag
|
||||
|
||||
class LayoutTypeSystem;
|
||||
@@ -199,6 +239,7 @@ public:
|
||||
using Node = LayoutTypeSystemNode;
|
||||
using NodePtr = LayoutTypeSystemNode *;
|
||||
using NodeUniquePtr = std::unique_ptr<LayoutTypeSystemNode>;
|
||||
using NeighborIterator = LayoutTypeSystemNode::NeighborIterator;
|
||||
|
||||
static dla::LayoutTypeSystem::NodePtr
|
||||
getNodePtr(const dla::LayoutTypeSystem::NodeUniquePtr &P) {
|
||||
@@ -293,14 +334,17 @@ 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);
|
||||
|
||||
NeighborIterator
|
||||
eraseEdge(LayoutTypeSystemNode *Src, NeighborIterator EdgeIt);
|
||||
|
||||
private:
|
||||
uint64_t NID = 0ULL;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,11 +8,13 @@ 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
|
||||
Middleend/DLAPruneLayoutNodesWithoutLayout.cpp
|
||||
Middleend/DLACollapseSingleChild.cpp
|
||||
Middleend/DecomposeStridedEdges.cpp
|
||||
Middleend/DeduplicateFields.cpp
|
||||
Middleend/DLAStep.cpp
|
||||
Middleend/FieldSizeComputation.cpp
|
||||
|
||||
@@ -56,8 +56,10 @@ bool DLAPass::runOnModule(llvm::Module &M) {
|
||||
revng_check(SM.addStep<dla::RemoveInvalidStrideEdges>());
|
||||
revng_check(SM.addStep<dla::PruneLayoutNodesWithoutLayout>());
|
||||
revng_check(SM.addStep<dla::ComputeUpperMemberAccesses>());
|
||||
revng_check(SM.addStep<dla::CollapseSingleChild>());
|
||||
revng_check(SM.addStep<dla::MergePointerNodes>());
|
||||
revng_check(SM.addStep<dla::DecomposeStridedEdges>());
|
||||
revng_check(SM.addStep<dla::ArrangeAccessesHierarchically>());
|
||||
revng_check(SM.addStep<dla::CollapseSingleChild>());
|
||||
revng_check(SM.addStep<dla::DeduplicateFields>());
|
||||
revng_check(SM.addStep<dla::ComputeNonInterferingComponents>());
|
||||
SM.run(TS);
|
||||
|
||||
@@ -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,
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
//
|
||||
// Copyright (c) rev.ng Labs Srl. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#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<const dla::LayoutTypeSystemNode *,
|
||||
dla::isNotPointerEdge>;
|
||||
using InverseInstance = llvm::Inverse<InstanceGraph>;
|
||||
for (const auto &Edge : llvm::children_edges<InverseInstance>(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<const dla::LayoutTypeSystemNode *,
|
||||
dla::isNotPointerEdge>;
|
||||
using InverseInstance = llvm::Inverse<InstanceGraph>;
|
||||
for (const auto *P : llvm::children<InverseInstance>(N))
|
||||
if (P != Parent)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if N has a pointer successor (predecessor if OnInverse is true)
|
||||
template<bool OnInverse>
|
||||
static bool hasPointerSuccessor(const LayoutTypeSystemNode *N) {
|
||||
using ConstPointerGraph = EdgeFilteredGraph<const dla::LayoutTypeSystemNode *,
|
||||
dla::isPointerEdge>;
|
||||
using Directed = std::conditional_t<OnInverse,
|
||||
llvm::Inverse<ConstPointerGraph>,
|
||||
ConstPointerGraph>;
|
||||
|
||||
for ([[maybe_unused]] const auto *_ : llvm::children<Directed>(N))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if N has an incoming pointer edge
|
||||
static bool hasPointerParent(const LayoutTypeSystemNode *N) {
|
||||
return hasPointerSuccessor<true>(N);
|
||||
}
|
||||
|
||||
// Returns true if N has an outgoing pointer edge
|
||||
static bool hasPointerChild(const LayoutTypeSystemNode *N) {
|
||||
return hasPointerSuccessor<false>(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<dla::LayoutTypeSystemNode *,
|
||||
dla::isNotPointerEdge>;
|
||||
|
||||
static llvm::SetVector<LayoutTypeSystemNode *>
|
||||
getVolatileChildren(LayoutTypeSystemNode *Parent) {
|
||||
llvm::SetVector<LayoutTypeSystemNode *> Result;
|
||||
for (auto *Child : llvm::children<NonPointerFilterT>(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<PushThroughComparisonResult>
|
||||
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<LayoutTypeSystemNode *, 8>
|
||||
absorbVolatileChildren(LayoutTypeSystem &TS, LayoutTypeSystemNode *Parent) {
|
||||
|
||||
llvm::SmallPtrSet<LayoutTypeSystemNode *, 8> 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<InstanceEdge,
|
||||
llvm::SmallVector<InstanceEdge, 16>,
|
||||
llvm::SmallSet<InstanceEdge, 16>>
|
||||
CompoundEdges;
|
||||
|
||||
for (const auto &[Child, Tag] :
|
||||
llvm::children_edges<NonPointerFilterT>(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<NonPointerFilterT>(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<const dla::LayoutTypeSystemNode *> 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<LayoutTypeSystemNode *> Queue;
|
||||
std::set<const LayoutTypeSystemNode *> ToAnalyze;
|
||||
{
|
||||
|
||||
std::set<const dla::LayoutTypeSystemNode *> 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<NeighborIterator, OffsetExpression>;
|
||||
using EdgeInclusionGraph = GenericGraph<EdgeNode>;
|
||||
EdgeInclusionGraph EdgeInclusion;
|
||||
|
||||
using GT = llvm::GraphTraits<LayoutTypeSystemNode *>;
|
||||
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<NeighborIterator, EdgeNode *> 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<NeighborIterator, 4> 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
|
||||
@@ -9,11 +9,13 @@
|
||||
|
||||
namespace dla {
|
||||
|
||||
const char ArrangeAccessesHierarchically::ID = 0;
|
||||
const char CollapseEqualitySCC::ID = 0;
|
||||
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;
|
||||
@@ -78,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -239,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 {
|
||||
@@ -252,13 +287,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<typename IterT>
|
||||
bool intersect(IterT I1, IterT E1, IterT I2, IterT E2) {
|
||||
while ((I1 != E1) and (I2 != E2)) {
|
||||
|
||||
@@ -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<LayoutTypeSystemNode *>;
|
||||
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<LayoutTypeSystemNode *> 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
|
||||
@@ -405,7 +405,7 @@ bool DeduplicateFields::runOnTypeSystem(LayoutTypeSystem &TS) {
|
||||
// two nodes, we don't have to update other links in the worklist.
|
||||
llvm::SmallSetVector<LTSN *, 8> FieldsToCompare;
|
||||
llvm::SmallSet<LTSN *, 8> OriginalFields;
|
||||
llvm::SmallSet<LTSN *, 8> AnalyzedNodesNotMerged;
|
||||
llvm::SmallSetVector<LTSN *, 8> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// Copyright (c) rev.ng Labs Srl. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace dla {
|
||||
|
||||
struct LayoutTypeSystemNode;
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user