Files
revng-revng/lib/TypeNames/DependencyGraph.cpp
T
2025-10-31 17:25:03 +01:00

415 lines
15 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <optional>
#include <type_traits>
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/DOTGraphTraits.h"
#include "llvm/Support/GraphWriter.h"
#include "revng/ABI/ModelHelpers.h"
#include "revng/ADT/FilteredGraphTraits.h"
#include "revng/ADT/GenericGraph.h"
#include "revng/ADT/ScopedExchange.h"
#include "revng/Model/ArrayType.h"
#include "revng/Model/Binary.h"
#include "revng/Model/TypeDefinition.h"
#include "revng/Support/Assert.h"
#include "revng/Support/Debug.h"
#include "revng/TypeNames/DependencyGraph.h"
#include "revng/TypeNames/ModelCBuilder.h"
static Logger Log{ "type-dependency-graph" };
using namespace llvm;
static bool hasSeparateForwardDeclaration(const model::TypeDefinition &TD) {
return not ptml::ModelCBuilder::isDeclarationTheSameAsDefinition(TD);
}
static llvm::StringRef toString(TypeNode::Kind K) {
switch (K) {
case TypeNode::Kind::Declaration:
return "Declaration";
case TypeNode::Kind::Definition:
return "Definition";
case TypeNode::Kind::ArtificialWrapperDeclaration:
return "ArtificialWrapperDeclaration";
case TypeNode::Kind::ArtificialWrapperDefinition:
return "ArtificialWrapperDefinition";
}
return "Invalid";
}
std::string getNodeLabel(const TypeDependencyNode *N) {
return (Twine(getNameFromYAMLScalar(N->T->key())) + Twine("-")
+ Twine(toString(N->K)))
.str();
}
using DepNode = TypeDependencyNode;
using DepGraph = DependencyGraph;
std::string llvm::DOTGraphTraits<DepGraph *>::getNodeLabel(const DepNode *N,
const DepGraph *G) {
return ::getNodeLabel(N);
}
class DependencyGraph::Builder {
/// A pointer to the DependencyGraph being constructed and initialized.
DependencyGraph *Graph = nullptr;
/// A pointer to the TypeVector for which the Builder is building a
/// DependencyGraph.
const TypeVector *Types = nullptr;
public:
Builder(const TypeVector &TV) : Graph(nullptr), Types(&TV) {}
// Ensure we don't initialize Types to the address of a temporary.
Builder(TypeVector &&TV) = delete;
public:
/// Create and initialize a DependencyGraph.
DependencyGraph make() {
// Set up an empty DependencyGraph, and the Graph pointer to point to it, so
// that all methods that are used to build the graph from makeImpl down can
// just use Graph.
// The Graph pointer is then reset to nullptr via the ScopedExchange when
// construction is done.
DependencyGraph Dependencies;
ScopedExchange ExchangeGraphPtr(Graph, &Dependencies);
makeImpl();
if (Log.isEnabled())
llvm::ViewGraph(Graph, "type-deps.dot");
return Dependencies;
}
/// Create and initialize a DependencyGraph from a TypeVector.
static DependencyGraph make(const TypeVector &TV) {
return Builder(TV).make();
}
private:
/// Actual implementation of the make method.
void makeImpl() const;
/// Add a declaration node and a definition node to Graph for \p T.
AssociatedNodes addNodes(const model::TypeDefinition &T) const;
/// Add a declaration node and a definition node to Graph for an artificial
/// struct wrapper intended to wrap \p T. \p T is required to return a
/// RegisterSet, and the artificial wrapper is a struct with each of those
/// registers' types as fields.
AssociatedNodes
addArtificialNodes(const model::RawFunctionDefinition &T) const;
/// Add all the necessary dependency edges to Graph for the nodes that
/// represent the declaration and definition of \p T.
void addDependencies(const model::TypeDefinition &T) const;
/// Add all the necessary dependencies edges from the \p Dependent to the
/// nodes associated with the \p DependedOn type.
void addDependenciesFrom(const AssociatedNodes Dependent,
const model::Type &DependedOn) const;
/// Given a model::RawFunctionDefinition \p RFD returning a RegisterSet
/// declaration, adds a definition node for an artificial struct wrapper
/// whose fields are all the return register types, along with all the
/// necessary dependency edges to the wrapped types.
/// \returns the AssociatedNodes of the wrapper.
AssociatedNodes
addRFTReturnWrapper(const model::RawFunctionDefinition &RFD) const;
};
static void addAndLogSuccessor(TypeDependencyNode *From,
TypeDependencyNode *To) {
revng_assert(From);
revng_assert(To);
revng_log(Log,
"Adding edge " << getNodeLabel(From) << " --> "
<< getNodeLabel(To));
From->addSuccessor(To);
}
DependencyGraph::AssociatedNodes
DependencyGraph::Builder::addNodes(const model::TypeDefinition &T) const {
constexpr auto Declaration = TypeNode::Kind::Declaration;
auto *DeclNode = Graph->addNode(TypeNode{ &T, Declaration });
revng_log(Log, "Added DeclNode node " << getNodeLabel(DeclNode));
TypeDependencyNode *DefNode = nullptr;
if (hasSeparateForwardDeclaration(T)) {
constexpr auto Definition = TypeNode::Kind::Definition;
DefNode = Graph->addNode(TypeNode{ &T, Definition });
revng_log(Log, "Added DefNode node " << getNodeLabel(DefNode));
// The definition always depends on the declaration.
// This is not strictly necessary (e.g. when definition and declaration are
// the same, or when printing a the body of a struct without having forward
// declared it) but it doesn't introduce cycles and it enables the algorithm
// that decides on the ordering on the declarations and definitions to make
// more assumptions about definitions being emitted before declarations.
addAndLogSuccessor(DefNode, DeclNode);
}
revng_assert(not Graph->TypeToNodes.contains(&T));
revng_log(Log, "Added DeclNode: " << DeclNode << ", DefNode: " << DefNode);
return Graph->TypeToNodes[&T] = AssociatedNodes{
.Declaration = DeclNode,
.Definition = DefNode,
};
}
DependencyGraph::AssociatedNodes
DependencyGraph::Builder::addArtificialNodes(const model::RawFunctionDefinition
&T) const {
auto Layout = abi::FunctionType::Layout::make(T);
using namespace abi::FunctionType::ReturnMethod;
revng_assert(Layout.returnMethod() == RegisterSet);
constexpr auto Declaration = TypeNode::Kind::ArtificialWrapperDeclaration;
auto *DeclNode = Graph->addNode(TypeNode{ &T, Declaration });
revng_log(Log, "Added DeclNode node " << getNodeLabel(DeclNode));
constexpr auto Definition = TypeNode::Kind::ArtificialWrapperDefinition;
auto *DefNode = Graph->addNode(TypeNode{ &T, Definition });
revng_log(Log, "Added DefNode node " << getNodeLabel(DefNode));
// The definition always depends on the declaration.
// This is not strictly necessary (e.g. when definition and declaration are
// the same, or when printing a the body of a struct without having forward
// declared it) but it doesn't introduce cycles and it enables the algorithm
// that decides on the ordering on the declarations and definitions to make
// more assumptions about definitions being emitted before declarations.
addAndLogSuccessor(DefNode, DeclNode);
return AssociatedNodes{
.Declaration = DeclNode,
.Definition = DefNode,
};
}
struct TypeSpecifierResult {
const model::TypeDefinition *Definition;
bool FoundPointer;
bool LastArray;
};
static RecursiveCoroutine<TypeSpecifierResult>
getTypeSpecifierResult(const model::Type &T,
bool FoundPointer = false,
bool LastArray = false) {
if (const auto *P = dyn_cast<model::PointerType>(&T)) {
const model::Type &Pointee = *P->PointeeType();
rc_return rc_recur getTypeSpecifierResult(Pointee, true, false);
}
if (const auto *A = dyn_cast<model::ArrayType>(&T)) {
const model::Type &Element = *A->ElementType();
rc_return rc_recur getTypeSpecifierResult(Element, FoundPointer, true);
}
if (const auto *D = T.tryGetAsDefinition()) {
rc_return TypeSpecifierResult{ .Definition = D,
.FoundPointer = FoundPointer,
.LastArray = LastArray };
}
rc_return TypeSpecifierResult{ .Definition = nullptr,
.FoundPointer = FoundPointer,
.LastArray = LastArray };
}
void DependencyGraph::Builder::addDependenciesFrom(const AssociatedNodes
Dependent,
const model::Type
&DependedOn) const {
TypeSpecifierResult SpecifierDependedOn = getTypeSpecifierResult(DependedOn);
const auto &[DefinitionDependedOn,
FoundPointer,
LastArray] = SpecifierDependedOn;
// If the DefinitionDependedOn is not a TypeDefinition, we're done, because it
// hasn't any real dependency, except on primitives, arrays of primitives, and
// pointers to primitives.
if (not DefinitionDependedOn)
return;
const auto &[DependentDeclNode, DependentDefNode] = Dependent;
const model::TypeDefinition *DependentDefinition = DependentDeclNode->T;
revng_assert(DependentDeclNode);
revng_assert(DependentDefinition);
TypeDependencyNode *DependentNode = nullptr;
bool
HasForwardDeclaration = hasSeparateForwardDeclaration(*DependentDefinition)
or DependentDeclNode->isArtificial();
if (HasForwardDeclaration) {
revng_assert(DependentDefNode);
DependentNode = DependentDefNode;
} else {
revng_assert(not DependentDefNode);
DependentNode = DependentDeclNode;
}
revng_assert(DependentNode);
AssociatedNodes NodesDependedOn = Graph->TypeToNodes.at(DefinitionDependedOn);
revng_assert(NodesDependedOn.Declaration);
revng_assert(not NodesDependedOn.Declaration->isArtificial());
revng_assert(not NodesDependedOn.Definition
or not NodesDependedOn.Definition->isArtificial());
// If LastArray is true, the node depended on is always the node
// representing the full definition of the type, which in some cases might
// be the Declaration node (e.g. when the type depended on doesn't have a
// separate Definition and Declaration node, but only a Declaration, such as
// for TypedefDefinitions and function type definitions).
if (LastArray) {
TypeDependencyNode *NodeDependedOn = NodesDependedOn.Definition ?
NodesDependedOn.Definition :
NodesDependedOn.Declaration;
revng_assert(NodeDependedOn);
addAndLogSuccessor(DependentNode, NodeDependedOn);
return;
}
// If FoundPointer is true, and LastArray is false, the node depended on is
// always the Declaration node, because we don't need the full definition.
if (FoundPointer) {
addAndLogSuccessor(DependentNode, NodesDependedOn.Declaration);
return;
}
// Otherwise we fall back in the baseline case.
// The DependentNode always depends on the the Declaration node of DependedOn.
addAndLogSuccessor(DependentNode, NodesDependedOn.Declaration);
// If both Dependent and DependedOn has a separate forward declaration we add
// a dependency from DependentNode to the Definition of the DependedOn.
if (HasForwardDeclaration
and hasSeparateForwardDeclaration(*DefinitionDependedOn)) {
addAndLogSuccessor(DependentNode, NodesDependedOn.Definition);
}
// Finally, if the DependentDefinition has a forward declaration, it also
// means that it has a separate definition from the forward declaration.
// In that case, if DefinitionDependedOn is a typedef, we also have to look
// across all those typedefs and ensure the full definition of the dependent
// also depends on the full definition of the depended-on, across typedefs.
if (HasForwardDeclaration
and isa<model::TypedefDefinition>(DefinitionDependedOn)) {
const model::Type *Underlying = DefinitionDependedOn->skipTypedefs();
if (auto *UnderlyingDefinition = Underlying->tryGetAsDefinition();
UnderlyingDefinition
and hasSeparateForwardDeclaration(*UnderlyingDefinition)) {
AssociatedNodes NodesTransitivelyDependedOn = Graph->TypeToNodes
.at(UnderlyingDefinition);
revng_assert(NodesTransitivelyDependedOn.Definition);
addAndLogSuccessor(DependentNode, NodesTransitivelyDependedOn.Definition);
}
}
}
DependencyGraph::AssociatedNodes
DependencyGraph::Builder::addRFTReturnWrapper(const model::RawFunctionDefinition
&RFD) const {
AssociatedNodes ReturnValuesWrapper = addArtificialNodes(RFD);
const auto &[WrapperDecl, WrapperDef] = ReturnValuesWrapper;
auto Layout = abi::FunctionType::Layout::make(RFD);
// Then the artificial wrapper wrapping the return values should depend on the
// relevant return value types, that are the types of its fields.
for (const model::UpcastableType &ReturnType : Layout.returnValueTypes()) {
addDependenciesFrom(ReturnValuesWrapper, *ReturnType);
}
return ReturnValuesWrapper;
}
void DependencyGraph::Builder::addDependencies(const model::TypeDefinition &T)
const {
const auto &TDNodes = Graph->TypeToNodes.at(&T);
switch (T.Kind()) {
case model::TypeDefinitionKind::EnumDefinition:
case model::TypeDefinitionKind::StructDefinition:
case model::TypeDefinitionKind::UnionDefinition:
case model::TypeDefinitionKind::TypedefDefinition: {
for (const model::Type *Edge : T.edges())
addDependenciesFrom(TDNodes, *Edge);
} break;
case model::TypeDefinitionKind::RawFunctionDefinition:
case model::TypeDefinitionKind::CABIFunctionDefinition: {
using abi::FunctionType::Layout;
auto TheLayout = Layout::make(T);
using namespace abi::FunctionType::ReturnMethod;
if (TheLayout.returnMethod() != RegisterSet) {
for (const model::UpcastableType &ReturnType :
TheLayout.returnValueTypes())
addDependenciesFrom(TDNodes, *ReturnType);
} else {
// If T is a RawFunctionDefinition returning a RegisterSet, we create an
// artificial struct wrapper around the returned registers.
const auto *RF = cast<model::RawFunctionDefinition>(&T);
AssociatedNodes WrapperNodes = addRFTReturnWrapper(*RF);
// The declaration of the function type depends only on the declaration of
// the struct wrapper for the return type.
addAndLogSuccessor(TDNodes.Declaration, WrapperNodes.Declaration);
revng_assert(not TDNodes.Definition);
}
for (const model::UpcastableType &ArgumentType : TheLayout.argumentTypes())
addDependenciesFrom(TDNodes, *ArgumentType);
} break;
default:
revng_abort("Undexpected T.Kind()");
}
}
void DependencyGraph::Builder::makeImpl() const {
// Create declaration and definition nodes for all the type definitions
for (const model::UpcastableTypeDefinition &MT : *Types)
addNodes(*MT);
// Compute dependencies and add them to the graph
for (const model::UpcastableTypeDefinition &MT : *Types)
addDependencies(*MT);
}
DependencyGraph DependencyGraph::make(const TypeVector &TV) {
static_assert(std::is_copy_constructible_v<DependencyGraph::Builder>);
static_assert(std::is_copy_assignable_v<DependencyGraph::Builder>);
static_assert(std::is_move_constructible_v<DependencyGraph::Builder>);
static_assert(std::is_move_assignable_v<DependencyGraph::Builder>);
return DependencyGraph::Builder::make(TV);
}
void DependencyGraph::viewGraph() const {
llvm::ViewGraph(this, "type-dependency-graph.dot");
}