Files
Pietro Fezzardi 18054eeee2 DLACollapseSingleChild: fix pointer loops
This commit fixes a situation where CollapseSingleChild could introduce
infinite loops composed entirely of pointer edges, which would end up in
making the DLA backend go out-of-memory.

This happened when collapsing a Child at offset 0 into a Parent node,
when Child had a pointer edge going to Parent.
The pointer edge needed not to be at step one, but it could possibly be
a pointer-to-pointer to Parent, at any depth.

After merging Child into Parent this would lead to a node pointing to
itself, which is a forbidden pointer loop in the graph.

This commit catches the pattern and prevents it to merge Child into
Parent.
2026-06-03 16:33:15 +02:00

136 lines
4.3 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "revng/DataLayoutAnalysis/DLATypeSystem.h"
#include "revng/Support/Assert.h"
#include "revng/Support/Debug.h"
#include "DLAStep.h"
using LTSN = dla::LayoutTypeSystemNode;
using PointerFilterT = EdgeFilteredGraph<LTSN *, dla::isPointerEdge>;
using namespace llvm;
static Logger Log("dla-collapse-single-child");
namespace dla {
bool CollapseSingleChild::collapseSingle(LayoutTypeSystem &TS,
LayoutTypeSystemNode *Node) {
LoggerIndent Indent{ Log };
bool Changed = false;
auto HasSingleNonStridedChild = [](const LTSN *Node) {
const auto &SuccBegin = Node->Successors.begin();
return (Node->Successors.size() == 1) and isInstanceEdge(*SuccBegin)
and SuccBegin->second->getOffsetExpr().Strides.empty();
};
// Get nodes that have a single instance-at-offset-0 child
bool Merged = true;
while (Merged and HasSingleNonStridedChild(Node)) {
auto &ChildEdge = *(Node->Successors.begin());
auto &Off = ChildEdge.second->getOffsetExpr().Offset;
LTSN *Child = ChildEdge.first;
Merged = false;
if (not Off) {
// If the only child is at offset 0, and it has the same size as the
// parent node, the two nodes are indistinguishable, hence they can be
// merged.
revng_log(Log, "Has single child at offset 0. Child: " << Child->ID);
LoggerIndent MoreIndent{ Log };
// If the parent has a size different from the child, bail out.
const auto ChildSize = Child->Size;
if (ChildSize != Node->Size) {
revng_log(Log,
"Size mismatch! Node = " << Node->Size
<< " Child = " << ChildSize);
break;
}
if (llvm::is_contained(llvm::depth_first(PointerFilterT(Child)), Node)) {
revng_log(Log, "Child points to Node");
break;
}
revng_log(Log, "Collapsing " << Child->ID << " into " << Node->ID);
TS.mergeNodes({ /*Into=*/Node, /*From=*/Child });
Changed = true;
Merged = true;
} else if (isPointerRoot(Node) and not isInstanceRoot(Node)) {
// If the node doesn't have incoming pointer nodes, we can try to absorbe
// even instance edges with offset different than zero, pushing the
// padding out of the parent Node.
// If it has an incoming pointer node we cannot do anything because we'd
// change the size of the pointee (i.e. the Child)
// If the parent has a size different from the child + offset, bail out.
const auto ChildSize = Child->Size;
if ((ChildSize + Off) != Node->Size) {
revng_log(Log,
"Size mismatch! Node = " << Node->Size << " Child = "
<< ChildSize << " Offset = " << Off);
break;
}
// If we would end up merging a NonScalar node into the the non-NonScalar,
// we'd end up losing information about the size of the NonScalar one,
// which is wrong since it's fixed and it comes from the Model. In that
// case we bail out.
if (Child->NonScalar) {
revng_log(Log,
"Cannot merge NonScalar Child into Node! Node = "
<< Node->Size << " Child = " << ChildSize
<< " Offset = " << Off);
break;
}
// Move Node's predecessor edges to Child, adding Off.
auto PredIt = Node->Predecessors.begin();
auto PredEnd = Node->Predecessors.end();
while (PredIt != PredEnd) {
auto Next = std::next(PredIt);
TS.moveEdgeTarget(Node, Child, PredIt, Off);
PredIt = Next;
}
TS.mergeNodes({ /*Into=*/Node, /*From=*/Child });
Node->Size = ChildSize;
Changed = true;
Merged = true;
}
}
return Changed;
}
bool CollapseSingleChild::runOnTypeSystem(LayoutTypeSystem &TS) {
bool Changed = false;
if (VerifyLog.isEnabled())
revng_assert(TS.verifyDAG());
for (LTSN *Node : llvm::nodes(&TS)) {
revng_log(Log, "Analyzing Node: " << Node->ID);
Changed |= collapseSingle(TS, Node);
}
if (VerifyLog.isEnabled())
revng_assert(TS.verifyDAG());
return Changed;
}
} // end namespace dla