Files
revng-revng/lib/TypeNames/ModelToPTMLTypeHelpers.cpp
T
2024-06-07 17:59:06 +02:00

715 lines
26 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <unordered_map>
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/GraphTraits.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include "revng/ADT/GenericGraph.h"
#include "revng/Model/Binary.h"
#include "revng/Model/Helpers.h"
#include "revng/Model/Type.h"
#include "revng/Pipeline/Location.h"
#include "revng/Support/Assert.h"
#include "revng/Support/Debug.h"
#include "revng/Support/YAMLTraits.h"
#include "revng/Yield/PTML.h"
#include "revng-c/HeadersGeneration/ModelToHeader.h"
#include "revng-c/Pipes/Ranks.h"
#include "revng-c/Support/ModelHelpers.h"
#include "revng-c/Support/PTMLC.h"
#include "revng-c/TypeNames/ModelToPTMLTypeHelpers.h"
#include "revng-c/TypeNames/ModelTypeNames.h"
using QualifiedTypeNameMap = std::map<model::QualifiedType, std::string>;
using TypeSet = std::set<const model::Type *>;
using GraphInfo = TypeInlineHelper::GraphInfo;
using Node = TypeInlineHelper::Node;
using StackTypesMap = std::unordered_map<const model::Function *,
std::set<const model::Type *>>;
TypeInlineHelper::TypeInlineHelper(const model::Binary &Model) {
// Create graph that represents type system.
TypeGraph = buildTypeGraph(Model);
TypesToInline = findTypesToInline(Model, TypeGraph);
}
const TypeSet &TypeInlineHelper::getTypesToInline() const {
return TypesToInline;
}
bool TypeInlineHelper::isReachableFromRootType(const model::Type *Type,
const model::Type *RootType,
const GraphInfo &TypeGraph) {
for (Node *N : llvm::depth_first(TypeGraph.TypeToNode.at(RootType)))
if (N->data().T == Type)
return true;
return false;
}
/// Collect candidates for emitting inline types.
TypeSet TypeInlineHelper::findTypesToInline(const model::Binary &Model,
const GraphInfo &TypeGraph) {
std::unordered_map<const model::Type *, uint64_t> Candidates;
std::set<const model::Type *> ShouldIgnore;
// We may find a struct that represents stack type that is being used exactly
// once somewhere else in Types:, but we do not want to inline it if that is
// the case.
for (auto &Function : Model.Functions()) {
if (not Function.StackFrameType().empty()) {
const model::Type *StackT = Function.StackFrameType().getConst();
ShouldIgnore.insert(StackT);
}
}
for (const UpcastablePointer<model::Type> &T : Model.Types()) {
for (const model::QualifiedType &QT : T->edges()) {
auto *DependantType = QT.UnqualifiedType().get();
if (declarationIsDefinition(T.get())) {
// Should never be inlined
ShouldIgnore.insert(DependantType);
} else {
// If it comes from a Type other than a function, consider that we are
// interested for the type, or if it was referenced from a type other
// than itself.
Candidates[DependantType]++;
// To inline a pointer type we need to know the sizes of all nested
// types, which may not be the case at the moment of inlining, so we
// avoid inlining it for now. In addition, we avoid inlining the types
// pointing to itself.
if (QT.isPointer() or T.get()->key() == DependantType->key()) {
ShouldIgnore.insert(DependantType);
} else if (isReachableFromRootType(T.get(), DependantType, TypeGraph)) {
// Or the type could point to itself on a nested level.
ShouldIgnore.insert(T.get());
ShouldIgnore.insert(DependantType);
}
}
}
}
// A candidate for inline is the type IFF it was referenced only once.
std::set<const model::Type *> Result;
using TypeReferences = const pair<const model::Type *, uint64_t>;
for_each(Candidates.begin(),
Candidates.end(),
[&Result, &ShouldIgnore](TypeReferences &TheType) {
if (TheType.second == 1
and not ShouldIgnore.contains(TheType.first)) {
Result.insert(TheType.first);
}
});
return Result;
}
GraphInfo TypeInlineHelper::buildTypeGraph(const model::Binary &Model) {
GraphInfo Result;
for (const UpcastablePointer<model::Type> &T : Model.Types()) {
Result.TypeToNode[T.get()] = Result.TypeGraph.addNode(NodeData{ T.get() });
}
// Create type system edges.
for (const UpcastablePointer<model::Type> &T : Model.Types()) {
for (const model::QualifiedType &QT : T->edges()) {
auto *UType = QT.UnqualifiedType().get();
Result.TypeToNode.at(T.get())->addSuccessor(Result.TypeToNode.at(UType));
}
}
return Result;
}
/// Returns a set of types that are referred to by at least one other type in
/// the \a Model. It does not take into consideration other references to the
/// types that are not cross-references among types, like e.g. stack frame types
/// that refer to model::Types from model::Functions.
static TypeSet getCrossReferencedTypes(const model::Binary &Model) {
TypeSet Result;
for (const UpcastablePointer<model::Type> &T : Model.Types())
for (const model::QualifiedType &QT : T->edges())
Result.insert(QT.UnqualifiedType().get());
return Result;
}
StackTypesMap
TypeInlineHelper::findTypesToInlineInStacks(const model::Binary &Model) const {
TypeSet CrossReferencedTypes = getCrossReferencedTypes(Model);
StackTypesMap Result;
for (auto &Function : Model.Functions()) {
if (not Function.StackFrameType().empty()) {
const model::Type *StackT = Function.StackFrameType().getConst();
revng_assert(StackT and StackT->Kind() == model::TypeKind::StructType);
// Do not inline stack types that are used by at least one other type.
if (CrossReferencedTypes.contains(StackT))
continue;
Result[&Function].insert(StackT);
auto AllNestedTypes = getTypesToInlineInTypeTy(Model, StackT);
Result[&Function].merge(AllNestedTypes);
}
}
return Result;
}
TypeSet
TypeInlineHelper::collectTypesInlinableInStacks(const model::Binary &Model)
const {
StackTypesMap TypesToInlineInStacks = findTypesToInlineInStacks(Model);
TypeSet Result;
for (auto [Function, TypesToInlineInStack] : TypesToInlineInStacks)
Result.merge(std::move(TypesToInlineInStack));
return Result;
}
bool declarationIsDefinition(const model::Type *T) {
return not llvm::isa<model::StructType>(T)
and not llvm::isa<model::UnionType>(T)
and not llvm::isa<model::EnumType>(T);
}
static ptml::Tag getTypeKeyword(const model::Type &T,
const ptml::PTMLCBuilder &B) {
switch (T.Kind()) {
case model::TypeKind::EnumType: {
return B.getKeyword(ptml::PTMLCBuilder::Keyword::Enum);
}
case model::TypeKind::StructType: {
return B.getKeyword(ptml::PTMLCBuilder::Keyword::Struct);
}
case model::TypeKind::UnionType: {
return B.getKeyword(ptml::PTMLCBuilder::Keyword::Union);
}
default:
revng_abort("unexpected type kind");
}
}
void printForwardDeclaration(const model::Type &T,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B) {
if (declarationIsDefinition(&T))
Header << B.getModelComment(T);
auto TypeNameReference = B.getLocationReference(T);
Header << B.getKeyword(ptml::PTMLCBuilder::Keyword::Typedef) << " "
<< getTypeKeyword(T, B) << " " << B.getAttributePacked() << " "
<< TypeNameReference << " " << TypeNameReference << ";\n";
}
static void printDefinition(const model::EnumType &E,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const TypeSet &TypesToInline,
llvm::StringRef NameOfInlineInstance,
const std::vector<model::Qualifier> &Qualifiers,
bool ForEditing) {
// We have to make the enum of the correct size of the underlying type
auto ByteSize = *E.size();
revng_assert(ByteSize <= 8);
size_t FullMask = std::numeric_limits<size_t>::max();
size_t MaxBitPatternInEnum = (ByteSize == 8) ?
FullMask :
((FullMask) xor (FullMask << (8 * ByteSize)));
Header
<< B.getModelComment(E) << B.getKeyword(ptml::PTMLCBuilder::Keyword::Enum)
<< " "
<< B.getAnnotateEnum(E.UnderlyingType().UnqualifiedType().get()->name())
<< " " << B.getAttributePacked() << " " << B.getLocationDefinition(E)
<< " ";
{
Scope Scope(Header);
using PTMLOperator = ptml::PTMLCBuilder::Operator;
for (const auto &Entry : E.Entries()) {
Header << B.getModelComment(Entry) << B.getLocationDefinition(E, Entry)
<< " " << B.getOperator(PTMLOperator::Assign) << " "
<< B.getHex(Entry.Value()) << ",\n";
}
if (not ForEditing) {
// This ensures the enum is large exactly like the Underlying type
Header << B.tokenTag(("_enum_max_value_" + E.name()).str(),
ptml::c::tokens::Field)
<< " " + B.getOperator(PTMLOperator::Assign) + " "
<< B.getHex(MaxBitPatternInEnum) << ",\n";
}
}
if (not NameOfInlineInstance.empty())
Header << " " << NameOfInlineInstance << ";\n";
else
Header << ";\n";
}
void printDefinition(Logger<> &Log,
const model::StructType &S,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model,
QualifiedTypeNameMap &AdditionalNames,
const TypeSet &TypesToInline,
llvm::StringRef NameOfInlineInstance,
const std::vector<model::Qualifier> &Qualifiers) {
Header << B.getModelComment(S)
<< B.getKeyword(ptml::PTMLCBuilder::Keyword::Struct) << " "
<< B.getAttributePacked() << " ";
Header << B.getLocationDefinition(S) << " ";
{
Scope Scope(Header, ptml::c::scopes::StructBody);
size_t NextOffset = 0ULL;
for (const auto &Field : S.Fields()) {
if (NextOffset < Field.Offset()) {
Header << B.tokenTag("uint8_t", ptml::c::tokens::Type) << " "
<< B.tokenTag(StructPaddingPrefix + std::to_string(NextOffset),
ptml::c::tokens::Field)
<< "[" << B.getNumber(Field.Offset() - NextOffset) << "];\n";
}
auto TheType = Field.Type().UnqualifiedType().get();
if (not TypesToInline.contains(TheType)) {
auto F = B.getLocationDefinition(S, Field);
Header << B.getModelComment(Field)
<< getNamedCInstance(Field.Type(), F, B) << ";\n";
} else {
auto Qualifiers = Field.Type().Qualifiers();
printDefinition(Log,
*TheType,
Header,
B,
Model,
AdditionalNames,
TypesToInline,
Field.name().str(),
Qualifiers);
}
NextOffset = Field.Offset() + Field.Type().size().value();
}
if (NextOffset < S.Size())
Header << B.tokenTag("uint8_t", ptml::c::tokens::Type) << " "
<< B.tokenTag(StructPaddingPrefix + std::to_string(NextOffset),
ptml::c::tokens::Field)
<< "[" << B.getNumber(S.Size() - NextOffset) << "];\n";
}
if (not NameOfInlineInstance.empty()) {
if (Qualifiers.empty())
Header << " " << NameOfInlineInstance;
else
Header << getNamedCInstance("", Qualifiers, NameOfInlineInstance, B);
}
Header << ";\n";
}
static void printDefinition(Logger<> &Log,
const model::UnionType &U,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model,
QualifiedTypeNameMap &AdditionalTypeNames,
const TypeSet &TypesToInline,
llvm::StringRef NameOfInlineInstance,
const std::vector<model::Qualifier> &Qualifiers) {
Header << B.getModelComment(U)
<< B.getKeyword(ptml::PTMLCBuilder::Keyword::Union) << " "
<< B.getAttributePacked() << " ";
Header << B.getLocationDefinition(U) << " ";
{
Scope Scope(Header, ptml::c::scopes::UnionBody);
for (const auto &Field : U.Fields()) {
auto TheType = Field.Type().UnqualifiedType().get();
if (not TypesToInline.contains(TheType)) {
auto F = B.getLocationDefinition(U, Field);
Header << B.getModelComment(Field)
<< getNamedCInstance(Field.Type(), F, B) << ";\n";
} else {
std::string Name = Field.name().str().str();
auto Qualifiers = Field.Type().Qualifiers();
printDefinition(Log,
*TheType,
Header,
B,
Model,
AdditionalTypeNames,
TypesToInline,
llvm::StringRef(Name.c_str()),
Qualifiers);
}
}
}
if (not NameOfInlineInstance.empty()) {
if (Qualifiers.empty())
Header << " " << NameOfInlineInstance;
else
Header << getNamedCInstance("", Qualifiers, NameOfInlineInstance, B);
}
Header << ";\n";
}
void printDeclaration(const model::TypedefType &TD,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B) {
if (declarationIsDefinition(&TD))
Header << B.getModelComment(TD);
auto Type = B.getLocationDefinition(TD);
Header << B.getKeyword(ptml::PTMLCBuilder::Keyword::Typedef) << " "
<< getNamedCInstance(TD.UnderlyingType(), Type, B) << ";\n";
}
/// Generate the definition of a new struct type that wraps all the return
/// values of \a F. The name of the struct type is provided by the caller.
static void generateReturnValueWrapper(Logger<> &Log,
const model::RawFunctionType &F,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model) {
revng_assert(F.ReturnValues().size() > 1);
if (Log.isEnabled())
Header << B.getLineComment("definition the of return type "
"needed");
Header << B.getKeyword(ptml::PTMLCBuilder::Keyword::Typedef) << " "
<< B.getKeyword(ptml::PTMLCBuilder::Keyword::Struct) << " "
<< B.getAttributePacked() << " ";
{
Scope Scope(Header, ptml::c::scopes::StructBody);
for (auto &Group : llvm::enumerate(F.ReturnValues())) {
const model::NamedTypedRegister &RetVal = Group.value();
const model::QualifiedType &RetTy = Group.value().Type();
using pipeline::serializedLocation;
std::string
ActionLocation = serializedLocation(revng::ranks::ReturnRegister,
F.key(),
RetVal.key());
std::string
FieldString = B.tokenTag(RetVal.name(), ptml::c::tokens::Field)
.addAttribute(ptml::attributes::ActionContextLocation,
ActionLocation)
.serialize();
Header << getNamedCInstance(RetTy, FieldString, B) << ";\n";
}
}
Header << " " << getReturnTypeName(F, B, true) << ";\n";
}
/// If the function has more than one return value, generate a wrapper struct
/// that contains them.
static void printRawFunctionWrappers(Logger<> &Log,
const model::RawFunctionType *F,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model) {
if (F->ReturnValues().size() > 1)
generateReturnValueWrapper(Log, *F, Header, B, Model);
for (auto &Arg : F->Arguments())
revng_assert(Arg.Type().isScalar());
}
/// Print a typedef for a RawFunctionType, that can be used when you have a
/// variable that is a pointer to a function.
static void printDeclaration(Logger<> &Log,
const model::RawFunctionType &F,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model) {
printRawFunctionWrappers(Log, &F, Header, B, Model);
Header << B.getModelComment(F)
<< B.getKeyword(ptml::PTMLCBuilder::Keyword::Typedef) << " ";
// In this case, we are defining a type for the function, not the function
// itself, so the token right before the parenthesis is the name of the type.
printFunctionTypeDeclaration(F, Header, B, Model);
Header << ";\n";
}
/// Generate the definition of a new struct type that wraps \a ArrayType. This
/// is used to wrap array arguments or array return values of CABIFunctionTypes.
static void generateArrayWrapper(const model::QualifiedType &ArrayType,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
QualifiedTypeNameMap &NamesCache) {
revng_assert(ArrayType.isArray());
auto WrapperName = getArrayWrapper(ArrayType, B);
// Check if the wrapper was already added
bool IsNew = NamesCache.emplace(ArrayType, WrapperName).second;
if (not IsNew)
return;
Header << B.getKeyword(ptml::PTMLCBuilder::Keyword::Typedef) << " "
<< B.getKeyword(ptml::PTMLCBuilder::Keyword::Struct) << " "
<< B.getAttributePacked() << " ";
{
Scope Scope(Header, ptml::c::scopes::StructBody);
Header << getNamedCInstance(ArrayType,
ArtificialTypes::ArrayWrapperFieldName,
B)
<< ";\n";
}
Header << " " << B.tokenTag(WrapperName, ptml::c::tokens::Type) << ";\n";
}
/// If the return value or any of the arguments is an array, generate a wrapper
/// struct for each of them, if it's not already in the cache.
static void printCABIFunctionWrappers(const model::CABIFunctionType *F,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
QualifiedTypeNameMap &NamesCache) {
if (F->ReturnType().isArray())
generateArrayWrapper(F->ReturnType(), Header, B, NamesCache);
for (auto &Arg : F->Arguments())
if (Arg.Type().isArray())
generateArrayWrapper(Arg.Type(), Header, B, NamesCache);
}
/// Print a typedef for a CABIFunctionType, that can be used when you have a
/// variable that is a pointer to a function.
static void printDeclaration(const model::CABIFunctionType &F,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
QualifiedTypeNameMap &NamesCache,
const model::Binary &Model) {
printCABIFunctionWrappers(&F, Header, B, NamesCache);
Header << B.getModelComment(F)
<< B.getKeyword(ptml::PTMLCBuilder::Keyword::Typedef) << " ";
// In this case, we are defining a type for the function, not the function
// itself, so the token right before the parenthesis is the name of the type.
printFunctionTypeDeclaration(F, Header, B, Model);
Header << ";\n";
}
void printDeclaration(Logger<> &Log,
const model::Type &T,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model,
QualifiedTypeNameMap &AdditionalNames,
const TypeSet &TypesToInline,
llvm::StringRef NameOfInlineInstance,
const std::vector<model::Qualifier> &Qualifiers,
bool ForEditing) {
if (Log.isEnabled()) {
auto Scope = helpers::LineComment(Header, B.isGenerateTagLessPTML());
Header << "Declaration of " << getNameFromYAMLScalar(T.key()) << "\n";
}
revng_log(Log, "Declaring " << getNameFromYAMLScalar(T.key()));
switch (T.Kind()) {
case model::TypeKind::Invalid: {
if (Log.isEnabled())
Header << B.getLineComment("invalid");
} break;
case model::TypeKind::PrimitiveType: {
// Do nothing. Primitive type declarations are all present in
// revng-primitive-types.h
} break;
case model::TypeKind::EnumType: {
printForwardDeclaration(llvm::cast<model::EnumType>(T), Header, B);
} break;
case model::TypeKind::StructType: {
printForwardDeclaration(llvm::cast<model::StructType>(T), Header, B);
} break;
case model::TypeKind::UnionType: {
printForwardDeclaration(llvm::cast<model::UnionType>(T), Header, B);
} break;
case model::TypeKind::TypedefType: {
printDeclaration(llvm::cast<model::TypedefType>(T), Header, B);
} break;
case model::TypeKind::RawFunctionType: {
printDeclaration(Log,
llvm::cast<model::RawFunctionType>(T),
Header,
B,
Model);
} break;
case model::TypeKind::CABIFunctionType: {
printDeclaration(llvm::cast<model::CABIFunctionType>(T),
Header,
B,
AdditionalNames,
Model);
} break;
default:
revng_abort();
}
}
void printDefinition(Logger<> &Log,
const model::Type &T,
ptml::PTMLIndentedOstream &Header,
ptml::PTMLCBuilder &B,
const model::Binary &Model,
QualifiedTypeNameMap &AdditionalNames,
const TypeSet &TypesToInline,
llvm::StringRef NameOfInlineInstance,
const std::vector<model::Qualifier> &Qualifiers,
bool ForEditing) {
if (Log.isEnabled())
Header << B.getLineComment("Definition of "
+ getNameFromYAMLScalar(T.key()));
revng_log(Log, "Defining " << getNameFromYAMLScalar(T.key()));
if (declarationIsDefinition(&T)) {
printDeclaration(Log,
T,
Header,
B,
Model,
AdditionalNames,
TypesToInline,
NameOfInlineInstance,
Qualifiers,
ForEditing);
} else {
switch (T.Kind()) {
case model::TypeKind::Invalid: {
if (Log.isEnabled())
Header << B.getLineComment("invalid");
} break;
case model::TypeKind::StructType: {
printDefinition(Log,
llvm::cast<model::StructType>(T),
Header,
B,
Model,
AdditionalNames,
TypesToInline,
NameOfInlineInstance,
Qualifiers);
} break;
case model::TypeKind::UnionType: {
printDefinition(Log,
llvm::cast<model::UnionType>(T),
Header,
B,
Model,
AdditionalNames,
TypesToInline,
NameOfInlineInstance,
Qualifiers);
} break;
case model::TypeKind::EnumType: {
printDefinition(llvm::cast<model::EnumType>(T),
Header,
B,
TypesToInline,
NameOfInlineInstance,
Qualifiers,
ForEditing);
} break;
default:
revng_abort();
}
}
}
using UPtrTy = UpcastablePointer<model::Type>;
TypeSet TypeInlineHelper::getNestedTypesToInline(const model::Type *RootType,
const UPtrTy &NestedTy) const {
model::Type *CurrentTy = NestedTy.get();
TypeSet Result;
do {
Result.insert(CurrentTy);
auto
ParentNode = TypeGraph.TypeToNode.at(CurrentTy)->predecessors().begin();
if ((*ParentNode)->data().T == RootType) {
return Result;
} else if (TypesToInline.contains((*ParentNode)->data().T)) {
CurrentTy = (*ParentNode)->data().T;
} else {
return {};
}
} while (CurrentTy);
return {};
}
TypeSet
TypeInlineHelper::getTypesToInlineInTypeTy(const model::Binary &Model,
const model::Type *RootType) const {
TypeSet Result;
auto TheTypeToNode = TypeGraph.TypeToNode;
// Visit all the nodes reachable from RootType.
llvm::df_iterator_default_set<Node *> Visited;
for ([[maybe_unused]] Node *N :
depth_first_ext(TheTypeToNode.at(RootType), Visited))
;
for (auto &Type : Model.Types()) {
if (Visited.contains(TheTypeToNode.at(Type.get()))
and TypesToInline.contains(Type.get())
and TheTypeToNode.at(Type.get())->predecessorCount() == 1) {
auto ParentNode = TheTypeToNode.at(Type.get())->predecessors().begin();
// In the case the parent is stack type itself, just insert the type.
if ((*ParentNode)->data().T == RootType) {
Result.insert(Type.get());
} else if (TypesToInline.contains((*ParentNode)->data().T)) {
// In the case the parent type is not the type RootType itself, make
// sure that the parent is inlinable into the type RootType. NOTE: This
// goes as further as possible in opposite direction in order to find
// all types that we should inline into the type RootType.
auto NestedTypesToInline = getNestedTypesToInline(RootType, Type);
Result.merge(NestedTypesToInline);
}
}
}
return Result;
}