Files
revng-revng/lib/HeadersGeneration/ModelToHeader.cpp
T
2022-12-12 11:35:52 +01:00

521 lines
18 KiB
C++

//
// Copyright (c) rev.ng Labs Srl. See LICENSE.md for details.
//
#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/Model/Binary.h"
#include "revng/Model/Type.h"
#include "revng/PTML/ModelHelpers.h"
#include "revng/Pipeline/Location.h"
#include "revng/Support/Assert.h"
#include "revng/Support/Debug.h"
#include "revng/Support/YAMLTraits.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/ModelTypeNames.h"
#include "DependencyGraph.h"
using ArtificialTypes::ArrayWrapperFieldName;
using llvm::cast;
using llvm::isa;
using llvm::Twine;
using ptml::str;
using ptml::Tag;
namespace attributes = ptml::attributes;
namespace tokens = ptml::c::tokens;
namespace ranks = revng::ranks;
static Logger<> Log{ "model-to-header" };
static bool declarationIsDefinition(const model::Type *T) {
return not isa<model::StructType>(T) and not isa<model::UnionType>(T);
}
static ptml::Tag getTypeKeyword(const model::Type &T) {
ptml::Tag TypeKeyword;
switch (T.Kind()) {
case model::TypeKind::EnumType: {
TypeKeyword = keywords::Enum;
} break;
case model::TypeKind::StructType: {
TypeKeyword = keywords::Struct;
} break;
case model::TypeKind::UnionType: {
TypeKeyword = keywords::Union;
} break;
default:
revng_abort("unexpected type kind");
}
return TypeKeyword;
}
static void printForwardDeclaration(const model::Type &T,
ptml::PTMLIndentedOstream &Header) {
auto TypeNameReference = ptml::getLocationReference(T);
Header << keywords::Typedef << " " << getTypeKeyword(T) << " "
<< helpers::Packed << " " << TypeNameReference << " "
<< TypeNameReference << ";\n";
}
static void
printDeclaration(const model::EnumType &E, ptml::PTMLIndentedOstream &Header) {
// 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 << keywords::Typedef << " " << keywords::Enum << " " << helpers::Packed
<< " ";
{
Scope Scope(Header);
for (const auto &Entry : E.Entries()) {
revng_assert(not Entry.CustomName().empty());
Header << ptml::getLocationDefinition(E, Entry) << " "
<< operators::Assign << " " << constants::hex(Entry.Value())
<< ",\n";
}
// This ensures the enum is large exactly like the Underlying type
Header << ptml::tokenTag((E.name() + "_max_held_value").str(),
tokens::Field)
<< " " + operators::Assign + " "
<< constants::hex(MaxBitPatternInEnum) << ",\n";
}
Header << " " << ptml::getLocationDefinition(E) << ";\n";
}
static void
printDefinition(const model::StructType &S, ptml::PTMLIndentedOstream &Header) {
Header << keywords::Struct << " " << helpers::Packed << " ";
Header << ptml::getLocationDefinition(S) << " ";
{
Scope Scope(Header, scopeTags::Struct);
size_t NextOffset = 0ULL;
for (const auto &Field : S.Fields()) {
if (NextOffset < Field.Offset())
Header << ptml::tokenTag("uint8_t", tokens::Type) << " "
<< ptml::tokenTag("padding_at_offset_"
+ std::to_string(NextOffset),
tokens::Field)
<< "[" << constants::number(Field.Offset() - NextOffset)
<< "];\n";
auto F = ptml::getLocationDefinition(S, Field);
Header << getNamedCInstance(Field.Type(), F) << ";\n";
NextOffset = Field.Offset() + Field.Type().size().value();
}
if (NextOffset < S.Size())
Header << ptml::tokenTag("uint8_t", tokens::Type) << " "
<< ptml::tokenTag("padding_at_offset_"
+ std::to_string(NextOffset),
tokens::Field)
<< "[" << constants::number(S.Size() - NextOffset) << "];\n";
}
Header << ";\n";
}
static void
printDefinition(const model::UnionType &U, ptml::PTMLIndentedOstream &Header) {
Header << keywords::Union << " " << helpers::Packed << " ";
Header << ptml::getLocationDefinition(U) << " ";
{
Scope Scope(Header, scopeTags::Union);
for (const auto &Field : U.Fields()) {
auto F = ptml::getLocationDefinition(U, Field);
Header << getNamedCInstance(Field.Type(), F) << ";\n";
}
}
Header << ";\n";
}
static void printDeclaration(const model::TypedefType &TD,
ptml::PTMLIndentedOstream &Header) {
auto Type = ptml::getLocationDefinition(TD);
Header << keywords::Typedef << " "
<< getNamedCInstance(TD.UnderlyingType(), Type) << ";\n";
}
static void printSegmentsTypes(const model::Segment &Segment,
ptml::PTMLIndentedOstream &Header) {
auto S = ptml::getLocationDefinition(Segment);
Header << getNamedCInstance(Segment.Type(), S) << ";\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(const model::RawFunctionType &F,
ptml::PTMLIndentedOstream &Header,
const model::Binary &Model) {
revng_assert(F.ReturnValues().size() > 1);
if (Log.isEnabled())
Header << helpers::lineComment("definition the of return type needed");
Header << keywords::Typedef << " " << keywords::Struct << " "
<< helpers::Packed << " ";
{
Scope Scope(Header, scopeTags::Struct);
for (auto &Group : llvm::enumerate(F.ReturnValues())) {
const model::QualifiedType &RetTy = Group.value().Type();
const auto &FieldName = getReturnField(F, Group.index(), Model);
Header << getNamedCInstance(RetTy,
ptml::tokenTag(FieldName, tokens::Field)
.serialize())
<< ";\n";
}
}
Header << " " << getReturnTypeName(F) << ";\n";
}
/// If the function has more than one return value, generate a wrapper
/// struct that contains them.
static void printRawFunctionWrappers(const model::RawFunctionType *F,
ptml::PTMLIndentedOstream &Header,
const model::Binary &Model) {
if (F->ReturnValues().size() > 1)
generateReturnValueWrapper(*F, Header, 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(const model::RawFunctionType &F,
ptml::PTMLIndentedOstream &Header,
const model::Binary &Model) {
printRawFunctionWrappers(&F, Header, Model);
Header << keywords::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, Model);
Header << ";\n";
}
using QualifiedTypeNameMap = std::map<model::QualifiedType, std::string>;
/// 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,
QualifiedTypeNameMap &NamesCache) {
revng_assert(ArrayType.isArray());
auto WrapperName = getArrayWrapper(ArrayType);
// Check if the wrapper was already added
bool IsNew = NamesCache.emplace(ArrayType, WrapperName).second;
if (not IsNew)
return;
Header << keywords::Typedef << " " << keywords::Struct << " "
<< helpers::Packed << " ";
{
Scope Scope(Header, scopeTags::Struct);
Header << getNamedCInstance(ArrayType, ArrayWrapperFieldName) << ";\n";
}
Header << " " << ptml::tokenTag(WrapperName, 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,
QualifiedTypeNameMap &NamesCache) {
if (F->ReturnType().isArray())
generateArrayWrapper(F->ReturnType(), Header, NamesCache);
for (auto &Arg : F->Arguments())
if (Arg.Type().isArray())
generateArrayWrapper(Arg.Type(), Header, 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,
QualifiedTypeNameMap &NamesCache,
const model::Binary &Model) {
printCABIFunctionWrappers(&F, Header, NamesCache);
Header << keywords::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, Model);
Header << ";\n";
}
static void printDeclaration(const model::Type &T,
ptml::PTMLIndentedOstream &Header,
QualifiedTypeNameMap &AdditionalTypeNames,
const model::Binary &Model) {
if (Log.isEnabled()) {
auto Scope = helpers::LineComment(Header);
Header << "Declaration of " << getNameFromYAMLScalar(T.key());
}
revng_log(Log, "Declaring " << getNameFromYAMLScalar(T.key()));
switch (T.Kind()) {
case model::TypeKind::Invalid: {
if (Log.isEnabled())
Header << helpers::lineComment("invalid");
} break;
case model::TypeKind::PrimitiveType: {
// Do nothing. Primitive type declarations are all present in
// revng-primitive-types.h
} break;
case model::TypeKind::EnumType: {
printDeclaration(cast<model::EnumType>(T), Header);
} break;
case model::TypeKind::StructType: {
printForwardDeclaration(cast<model::StructType>(T), Header);
} break;
case model::TypeKind::UnionType: {
printForwardDeclaration(cast<model::UnionType>(T), Header);
} break;
case model::TypeKind::TypedefType: {
printDeclaration(cast<model::TypedefType>(T), Header);
} break;
case model::TypeKind::RawFunctionType: {
printDeclaration(cast<model::RawFunctionType>(T), Header, Model);
} break;
case model::TypeKind::CABIFunctionType: {
printDeclaration(cast<model::CABIFunctionType>(T),
Header,
AdditionalTypeNames,
Model);
} break;
default:
revng_abort();
}
}
static void printDefinition(const model::Type &T,
ptml::PTMLIndentedOstream &Header,
QualifiedTypeNameMap &AdditionalTypeNames,
const model::Binary &Model) {
if (Log.isEnabled())
Header << helpers::lineComment("Definition of "
+ getNameFromYAMLScalar(T.key()));
revng_log(Log, "Defining " << getNameFromYAMLScalar(T.key()));
if (declarationIsDefinition(&T)) {
printDeclaration(T, Header, AdditionalTypeNames, Model);
} else {
switch (T.Kind()) {
case model::TypeKind::Invalid: {
if (Log.isEnabled())
Header << helpers::lineComment("invalid");
} break;
case model::TypeKind::StructType: {
printDefinition(cast<model::StructType>(T), Header);
} break;
case model::TypeKind::UnionType: {
printDefinition(cast<model::UnionType>(T), Header);
} break;
default:
revng_abort();
}
}
}
/// Print all type definitions for the types in the model
static void printTypeDefinitions(const model::Binary &Model,
ptml::PTMLIndentedOstream &Header,
QualifiedTypeNameMap &AdditionalTypeNames) {
DependencyGraph Dependencies = buildDependencyGraph(Model.Types());
const auto &TypeNodes = Dependencies.TypeNodes();
std::set<const TypeDependencyNode *> Defined;
for (const auto *Root : Dependencies.nodes()) {
revng_log(Log, "======== PostOrder " << getNodeLabel(Root));
for (const auto *Node : llvm::post_order_ext(Root, Defined)) {
revng_log(Log, "== visiting " << getNodeLabel(Node));
for (const auto *Child :
llvm::children<const TypeDependencyNode *>(Node)) {
revng_log(Log, "= child " << getNodeLabel(Child));
if (Defined.count(Child))
revng_log(Log, " DEFINED");
else
revng_log(Log, " NOT DEFINED");
}
const model::Type *NodeT = Node->T;
const auto DeclKind = Node->K;
constexpr auto TypeName = TypeNode::Kind::TypeName;
constexpr auto FullType = TypeNode::Kind::FullType;
if (DeclKind == FullType) {
// When emitting a full definition we also want to emit a forward
// declaration first, if it wasn't already emitted somewhere else.
if (Defined.insert(TypeNodes.at({ NodeT, TypeName })).second)
printDeclaration(*NodeT, Header, AdditionalTypeNames, Model);
if (not declarationIsDefinition(NodeT))
printDefinition(*NodeT, Header, AdditionalTypeNames, Model);
// This is always a full type definition
Defined.insert(TypeNodes.at({ NodeT, FullType }));
} else {
printDeclaration(*NodeT, Header, AdditionalTypeNames, Model);
Defined.insert(TypeNodes.at({ NodeT, TypeNode::Kind::TypeName }));
// For primitive types and enums the forward declaration we emit is
// also a full definition, so we need to keep track of this.
if (isa<model::PrimitiveType>(NodeT) or isa<model::EnumType>(NodeT))
Defined.insert(TypeNodes.at({ NodeT, TypeNode::Kind::FullType }));
// For struct and unions the forward declaration is just a forward
// declaration, without body.
// TypedefType, RawFunctionType and CABIFunctionType are emitted in C
// as typedefs, so they don't represent fully defined types, but just
// names, unless all the types they depend from are also fully
// defined, but that happens when DeclKind == FullType, not here.
}
}
revng_log(Log, "====== PostOrder DONE");
}
}
bool dumpModelToHeader(const model::Binary &Model, llvm::raw_ostream &Out) {
ptml::PTMLIndentedOstream Header(Out, 4);
{
auto Scope = Tag(ptml::tags::Div).scope(Header);
Header << helpers::pragmaOnce();
Header << helpers::includeAngle("stdint.h");
Header << helpers::includeAngle("stdbool.h");
Header << helpers::includeQuote("revng-primitive-types.h");
Header << "\n";
Header << directives::IfNotDef << " " << constants::Null << "\n"
<< directives::Define << " " << constants::Null << " ("
<< constants::Zero << ")\n"
<< directives::EndIf << "\n";
if (not Model.Types().empty()) {
auto Foldable = scopeTags::TypeDeclarations.scope(Out,
/* Newline */ true);
Header << helpers::lineComment("===============");
Header << helpers::lineComment("==== Types ====");
Header << helpers::lineComment("===============");
Header << '\n';
QualifiedTypeNameMap AdditionalTypeNames;
printTypeDefinitions(Model, Header, AdditionalTypeNames);
}
if (not Model.Functions().empty()) {
auto Foldable = scopeTags::FunctionDeclarations.scope(Out,
/* Newline */ true);
Header << helpers::lineComment("===================");
Header << helpers::lineComment("==== Functions ====");
Header << helpers::lineComment("===================");
Header << '\n';
for (const model::Function &MF : Model.Functions()) {
const model::Type *FT = MF.Prototype().get();
auto FName = model::Identifier::fromString(MF.name());
if (Log.isEnabled()) {
helpers::BlockComment CommentScope(Header);
Header << "Analyzing Model function " << FName << "\n";
serialize(Header, MF);
Header << "Prototype\n";
serialize(Header, *FT);
}
printFunctionPrototype(*FT, MF, Header, Model, true);
Header << ";\n";
}
}
if (not Model.ImportedDynamicFunctions().empty()) {
auto Foldable = scopeTags::DynamicFunctionDeclarations
.scope(Out, /* Newline */ true);
Header << helpers::lineComment("==================================");
Header << helpers::lineComment("==== ImportedDynamicFunctions ====");
Header << helpers::lineComment("==================================");
Header << '\n';
for (const model::DynamicFunction &MF :
Model.ImportedDynamicFunctions()) {
const model::Type *FT = MF.prototype(Model).get();
revng_assert(FT != nullptr);
auto FName = model::Identifier::fromString(MF.name());
if (Log.isEnabled()) {
helpers::BlockComment CommentScope(Header);
Header << "Analyzing dynamic function " << FName << "\n";
serialize(Header, MF);
Header << "Prototype\n";
serialize(Header, *FT);
}
printFunctionPrototype(*FT, MF, Header, Model, true);
Header << ";\n";
}
}
if (not Model.Segments().empty()) {
auto Foldable = scopeTags::SegmentDeclarations.scope(Out,
/* Newline */ true);
Header << helpers::lineComment("==================");
Header << helpers::lineComment("==== Segments ====");
Header << helpers::lineComment("==================");
Header << '\n';
for (const model::Segment &Segment : Model.Segments())
printSegmentsTypes(Segment, Header);
Header << '\n';
}
}
return true;
}