Files
revng-revng/lib/Decompiler/DLAComputeNonInterferingComponents.cpp
T
Pietro Fezzardi 59076db31b [DLA] Avoid instance links for unsupported offsets
This commit prevents the creation of instance links for offsets that we
don't support yet, during the CreateIntraProceduralTypes DLAStep.
This makes possible to avoid explicitly handling those cases in many
DLASteps, such as ComputeUpperMemberAccess,
ComputeNonInterferingComponents, and DLAMakeLayouts.

The offsets that are not supported yet are:
- all negative offsets
- all strided offsets with non-strictly-positive stride values
- all strided offsets with non-strictly-positive stride values
- all strided offsets with known negative trip-count

Some beneficial side-effects of avoiding the creation of such links are:
- the total number of edges on the graph is reduced, reducing the worst
  case depth of most visits
- it's impossible to create types with null or negative size (that we
  wouldn't know how to emit in C)
2021-02-02 11:23:53 +01:00

318 lines
12 KiB
C++

//
// Copyright (c) rev.ng Srls. See LICENSE.md for details.
//
#include <algorithm>
#include <compare>
#include <numeric>
#include <tuple>
#include <type_traits>
#include "llvm/ADT/GraphTraits.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "revng/Support/Debug.h"
#include "DLAHelpers.h"
#include "DLAStep.h"
#include "DLATypeSystem.h"
namespace dla {
using LTSN = LayoutTypeSystemNode;
bool ComputeNonInterferingComponents::runOnTypeSystem(LayoutTypeSystem &TS) {
if (VerifyLog.isEnabled())
revng_assert(TS.verifyDAG() and TS.verifyInheritanceTree());
bool Changed = false;
// Helper set, to prevent visiting a node from multiple entry points.
std::set<const LTSN *> Visited;
for (LTSN *Root : llvm::nodes(&TS)) {
revng_assert(Root != nullptr);
if (not isRoot(Root))
continue;
for (LTSN *N : llvm::post_order_ext(Root, Visited)) {
revng_assert(not isLeaf(N) or hasValidLayout(N));
revng_assert(N->L.Size);
struct OrderedChild {
int64_t Offset;
decltype(N->L.Size) Size;
LTSN *Child;
// Make it sortable
std::strong_ordering operator<=>(const OrderedChild &) const = default;
};
using ChildrenVec = llvm::SmallVector<OrderedChild, 8>;
using OrderedChildIt = ChildrenVec::iterator;
// Collect the children in a vector. Here we use the OrderedChild struct,
// that embeds info on the size and offset of the children, so that we can
// later sort the vector according to it.
ChildrenVec Children;
bool InheritsFromOther = false;
for (auto &[Child, EdgeTag] : llvm::children_edges<LTSN *>(N)) {
auto OrdChild = OrderedChild{
/* .Offset */ 0LL,
/* .Size */ Child->L.Size,
/* .Child */ Child,
};
switch (EdgeTag->getKind()) {
case TypeLinkTag::LK_Instance: {
const OffsetExpression &OE = EdgeTag->getOffsetExpr();
revng_assert(OE.Offset >= 0LL);
revng_assert(OE.Strides.size() == OE.TripCounts.size());
OrdChild.Offset = OE.Offset;
for (const auto &[TripCount, Stride] :
llvm::reverse(llvm::zip(OE.TripCounts, OE.Strides))) {
revng_assert(Stride > 0LL);
auto StrideSize = static_cast<uint64_t>(Stride);
// If we have a TripCount, we expect it to be strictly positive.
revng_assert(not TripCount.has_value() or TripCount.value() > 0LL);
// Arrays with unknown numbers of elements are considered as if
// they had a single element
auto NumElems = TripCount.has_value() ? TripCount.value() : 1;
revng_assert(NumElems);
// Here we are computing the larger size that is known to be
// accessed. So if we have an array, we consider it to be one
// element shorter than expected, and we add ChildSize only once
// at the end.
// This is equivalent to:
// ChildSize = (NumElems * StrideSize) - (StrideSize - ChildSize);
OrdChild.Size = ((NumElems - 1) * StrideSize) + OrdChild.Size;
}
} break;
case TypeLinkTag::LK_Inheritance: {
revng_assert(not InheritsFromOther);
InheritsFromOther = true;
} break;
default:
revng_unreachable("unexpected edge tag");
}
revng_assert(OrdChild.Offset >= 0LL and OrdChild.Size > 0ULL);
Children.push_back(std::move(OrdChild));
}
// If there are no children, there's nothing to do. There might be some
// accesses performed directly from N, but they always interfere with each
// other (because they start at the same base address), so they always
// constitute a single non-interfering component and we can leave them
// alone.
if (Children.empty()) {
N->InterferingInfo = AllChildrenAreInterfering;
continue;
}
// If there is only one children and no accesses, we are sure that there's
// nothing to do, because the only children cannot interfere with anything
// else, and it is already a component on its own.
llvm::SmallSet<uint64_t, 8> AccessSizes;
for (const auto &A : N->L.Accesses)
AccessSizes.insert(getLoadStoreSizeFromPtrOpUse(TS, A));
auto NumAccesses = AccessSizes.size();
if (Children.size() == 1ULL and not NumAccesses) {
N->InterferingInfo = AllChildrenAreNonInterfering;
continue;
}
// Sort the children. Thanks to the ordering of std::tuple, children at
// lower offsets will be sorted before children with higher offsets, and
// for children at the same offset, the smaller will be sorted before the
// larger ones.
std::sort(Children.begin(), Children.end());
// Struct that represents a non-interfering component.
// StartChildIt and EndChildIt are iterators into Children.
// StartByte and EndByte are computed during the identification.
// They are necessary for the creation of the artificial children in the
// type system graph later.
// NumChildren is the number of children or accesses that contribute to
// the Component.
// HasAccesses is true is this Component includes the accesses.
struct Component {
OrderedChildIt StartChildIt;
OrderedChildIt EndChildIt;
int64_t StartByte;
uint64_t EndByte;
size_t NumChildren;
bool HasAccesses;
};
llvm::SmallVector<Component, 8> Components;
{
// Helper lambda to create a new component starting from the iterator to
// a children that becomes the first element of the component.
const auto MakeNewComponentFromChild = [](OrderedChildIt ChildIt) {
auto ChildBeginByte = ChildIt->Offset;
auto ChildEndByte = ChildBeginByte + ChildIt->Size;
return Component{
/* .StartChildIt */ ChildIt,
/* .EndChildIt */ std::next(ChildIt),
/* .StartByte */ ChildBeginByte,
/* .EndByte */ ChildEndByte,
/* .NumChildren */ 1ULL,
/* .HasAccesses */ false,
};
};
OrderedChildIt ChildIt = Children.begin();
{
auto FirstChildComp = MakeNewComponentFromChild(ChildIt);
if (NumAccesses) {
const auto MaxAccessSize = [&TS](uint64_t PrevMax,
const llvm::Use *PtrOpUse) {
const auto ASize = getLoadStoreSizeFromPtrOpUse(TS, PtrOpUse);
return std::max(ASize, PrevMax);
};
int64_t AccessStartByte = 0LL;
uint64_t AccEndByte = std::accumulate(N->L.Accesses.begin(),
N->L.Accesses.end(),
0ULL,
MaxAccessSize);
revng_assert(FirstChildComp.StartByte >= 0);
if (static_cast<uint64_t>(FirstChildComp.StartByte) < AccEndByte) {
// Accesses interfere with the first component.
// Update the current component to reflect it.
FirstChildComp.StartByte = AccessStartByte;
FirstChildComp.EndByte = std::max(FirstChildComp.EndByte,
AccEndByte);
FirstChildComp.NumChildren += NumAccesses;
FirstChildComp.HasAccesses = true;
} else {
// Accesses are present, but they don't interfere with the node
// children, so we can create a separate non-interfering
// components just for them.
Components.push_back(Component{
/* .StartChildIt */ ChildIt,
/* .EndChildIt */ ChildIt,
/* .StartByte */ AccessStartByte,
/* .EndByte */ AccEndByte,
/* .NumChildren */ NumAccesses,
/* .HasAccesses */ true,
});
}
}
Components.push_back(std::move(FirstChildComp));
}
OrderedChildIt ChildEnd = Children.end();
while (++ChildIt != ChildEnd) {
auto &CurrComp = Components.back();
revng_assert(CurrComp.StartByte >= 0);
auto CompStartByte = static_cast<uint64_t>(CurrComp.StartByte);
revng_assert(CompStartByte < CurrComp.EndByte);
const auto &[ChildStartByte, ChildSize, _] = *ChildIt;
revng_assert(ChildStartByte >= 0 and ChildSize > 0);
auto ChildBeginByte = static_cast<uint64_t>(ChildStartByte);
revng_assert(ChildBeginByte >= CompStartByte);
if (ChildBeginByte >= CurrComp.EndByte) {
// The next candidate child falls entirely past the end of the
// component that we've been accumulating until now.
// Create a new compoenent and push it into Components.
Components.push_back(MakeNewComponentFromChild(ChildIt));
} else {
// The next candidate child interferes with the current component,
// so it must be part of it.
// Make sure that we update the EndByte.
CurrComp.EndByte = std::max(CurrComp.EndByte,
ChildBeginByte + ChildSize);
CurrComp.EndChildIt = std::next(ChildIt);
++(CurrComp.NumChildren);
}
}
}
// If we have less than two components there's nothing to do.
if (Components.size() < 2) {
N->InterferingInfo = AllChildrenAreInterfering;
continue;
}
// Helper lambda to filter the Components with more than one element.
// We don't care about Components with 0 or 1 elements because they don't
// need to be changed, because they are already non-interfering.
const auto HasManyElements = [](const Component &C) {
return C.NumChildren > 1ULL;
};
// For each Component with more than one element we have to create a new
// node in the type system, and push the edges from N to the elements of
// the component down to the newly created node.
bool FoundAccesses = false;
for (auto &C : llvm::make_filter_range(Components, HasManyElements)) {
Changed = true;
// Create the node representing the component
LTSN *New = TS.createArtificialLayoutType();
New->InterferingInfo = AllChildrenAreInterfering;
// Set its size to the size of the component
revng_assert(C.StartByte >= 0);
revng_assert(C.EndByte > static_cast<uint64_t>(C.StartByte));
New->L.Size = C.EndByte - static_cast<uint64_t>(C.StartByte);
// Move edges that were going directly from N to the children in the
// component C, so that these edges now go from New to Child.
// This effectively disconnects N from its children that are part of C.
// Those children will have New instead of N as predecessor.
// While moving the edges, the offset on the edge is updated.
using llvm::iterator_range;
auto OrderedChildRange = iterator_range(C.StartChildIt, C.EndChildIt);
for (auto &OrderedChild : OrderedChildRange)
TS.moveEdges(N, New, OrderedChild.Child, -C.StartByte);
// If the component C includes the accesses we need to move the
// accessess down to New.
if (C.HasAccesses) {
revng_assert(not FoundAccesses);
FoundAccesses = true;
revng_assert(not C.StartByte);
New->L.Accesses = std::move(N->L.Accesses);
}
// Add a link between N and the New node representing the component.
// The component is at offset C.StartByte inside N.
// If this offset is zero we add an inheritance edge, otherwise an
// instance edge.
if (C.StartByte)
TS.addInstanceLink(N, New, OffsetExpression(C.StartByte));
else
TS.addInheritanceLink(N, New);
}
N->InterferingInfo = AllChildrenAreNonInterfering;
}
}
if (VerifyLog.isEnabled())
revng_assert(TS.verifyDAG() and TS.verifyInheritanceTree());
return Changed;
}
} // end namespace dla