Files
revng-revng/lib/TypeNames/TypePrinters.cpp
T
2024-11-27 17:18:12 +01:00

458 lines
16 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/PostOrderIterator.h"
#include "revng/Support/Annotations.h"
#include "revng/TypeNames/PTMLCTypeBuilder.h"
using T = model::TypeDefinition;
void ptml::CTypeBuilder::printForwardTypeDeclaration(const T &Type) {
revng_assert(not isDeclarationTheSameAsDefinition(Type));
auto TypeNameReference = getLocationReference(Type);
*Out << getKeyword(ptml::CBuilder::Keyword::Typedef) << " "
<< getTypeKeyword(Type) << " "
<< ptml::AttributeRegistry::getAttribute<"_PACKED">() << " "
<< TypeNameReference << " " << TypeNameReference << ";\n";
}
void ptml::CTypeBuilder::printTypeDefinition(const model::EnumDefinition &E,
std::string &&Suffix) {
// 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)));
std::string Underlying = E.underlyingType().getCName();
*Out << getModelComment(E) << getKeyword(ptml::CBuilder::Keyword::Enum) << " "
<< ptml::AttributeRegistry::getAnnotation<"_ENUM_UNDERLYING">(Underlying)
<< " " << ptml::AttributeRegistry::getAttribute<"_PACKED">() << " "
<< getLocationDefinition(E) << " ";
{
Scope Scope(*Out);
using COperator = ptml::CBuilder::Operator;
for (const auto &Entry : E.Entries()) {
*Out << getModelComment(Entry) << getLocationDefinition(E, Entry) << " "
<< getOperator(COperator::Assign) << " " << getHex(Entry.Value())
<< ",\n";
}
if (Configuration.EnablePrintingOfTheMaximumEnumValue) {
// This ensures the enum is exactly as large as the Underlying type
*Out << tokenTag(("_enum_max_value_" + NameBuilder.name(E)).str(),
ptml::c::tokens::Field)
<< " " + getOperator(COperator::Assign) + " "
<< getHex(MaxBitPatternInEnum) << ",\n";
}
}
*Out << std::move(Suffix) << ";\n";
}
void ptml::CTypeBuilder::printPadding(uint64_t FieldOffset,
uint64_t NextOffset) {
revng_assert(FieldOffset <= NextOffset);
if (FieldOffset == NextOffset)
return; // There is no padding
if (Configuration.EnableExplicitPaddingMode) {
*Out << tokenTag("uint8_t", ptml::c::tokens::Type) << " "
<< tokenTag(NameBuilder.paddingFieldName(FieldOffset),
ptml::c::tokens::Field)
<< "[" << getNumber(NextOffset - FieldOffset) << "];\n";
} else {
*Out << ptml::AttributeRegistry::getAnnotation<"_START_AT">(NextOffset)
<< "\n";
}
}
void ptml::CTypeBuilder::printTypeDefinition(const model::StructDefinition &S,
std::string &&Suffix) {
*Out << getModelComment(S) << getKeyword(ptml::CBuilder::Keyword::Struct)
<< " " << ptml::AttributeRegistry::getAttribute<"_PACKED">() << " ";
if (S.CanContainCode())
*Out << ptml::AttributeRegistry::getAttribute<"_CAN_CONTAIN_CODE">() << " ";
if (Configuration.EnableStructSizeAnnotation)
*Out << ptml::AttributeRegistry::getAnnotation<"_SIZE">(S.Size()) << " ";
*Out << getLocationDefinition(S) << " ";
{
Scope Scope(*Out, ptml::c::scopes::StructBody);
size_t PreviousOffset = 0ULL;
for (const auto &Field : S.Fields()) {
printPadding(PreviousOffset, Field.Offset());
auto *Definition = Field.Type()->skipToDefinition();
if (not Definition or not shouldInline(*Definition)) {
auto F = getLocationDefinition(S, Field);
*Out << getModelComment(Field) << getNamedCInstance(*Field.Type(), F)
<< ";\n";
} else {
printInlineDefinition(NameBuilder.name(S, Field).str(), *Field.Type());
}
PreviousOffset = Field.Offset() + Field.Type()->size().value();
}
if (Configuration.EnableExplicitPaddingMode)
printPadding(PreviousOffset, S.Size());
}
*Out << std::move(Suffix) << ";\n";
}
void ptml::CTypeBuilder::printTypeDefinition(const model::UnionDefinition &U,
std::string &&Suffix) {
*Out << getModelComment(U) << getKeyword(ptml::CBuilder::Keyword::Union)
<< " " << ptml::AttributeRegistry::getAttribute<"_PACKED">() << " ";
*Out << getLocationDefinition(U) << " ";
{
Scope Scope(*Out, ptml::c::scopes::UnionBody);
for (const auto &Field : U.Fields()) {
auto *Definition = Field.Type()->skipToDefinition();
if (not Definition or not shouldInline(*Definition)) {
auto F = getLocationDefinition(U, Field);
*Out << getModelComment(Field) << getNamedCInstance(*Field.Type(), F)
<< ";\n";
} else {
printInlineDefinition(NameBuilder.name(U, Field).str(), *Field.Type());
}
}
}
*Out << std::move(Suffix) << ";\n";
}
using TD = model::TypedefDefinition;
void ptml::CTypeBuilder::printTypeDeclaration(const TD &Typedef) {
if (isDeclarationTheSameAsDefinition(Typedef))
*Out << getModelComment(Typedef);
auto Type = getLocationDefinition(Typedef);
*Out << getKeyword(ptml::CBuilder::Keyword::Typedef) << " "
<< getNamedCInstance(*Typedef.UnderlyingType(), Type) << ";\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.
using RFT = model::RawFunctionDefinition;
void ptml::CTypeBuilder::generateReturnValueWrapper(const RFT &F) {
revng_assert(F.ReturnValues().size() > 1);
*Out << getKeyword(ptml::CBuilder::Keyword::Typedef) << " "
<< getKeyword(ptml::CBuilder::Keyword::Struct) << " "
<< ptml::AttributeRegistry::getAttribute<"_PACKED">() << " ";
{
Scope Scope(*Out, ptml::c::scopes::StructBody);
for (auto &[Index, ReturnValue] : llvm::enumerate(F.ReturnValues())) {
std::string
ActionLocation = pipeline::locationString(revng::ranks::ReturnRegister,
F.key(),
ReturnValue.key());
std::string
FieldString = tokenTag(NameBuilder.returnValueName(F, ReturnValue),
ptml::c::tokens::Field)
.addAttribute(ptml::attributes::ActionContextLocation,
ActionLocation)
.toString();
*Out << getNamedCInstance(*ReturnValue.Type(), FieldString) << ";\n";
}
}
*Out << " " << getReturnTypeName(F, true) << ";\n";
}
/// If the function has more than one return value, generate a wrapper struct
/// that contains them.
void ptml::CTypeBuilder::printFunctionWrappers(const RFT &F) {
if (F.ReturnValues().size() > 1)
generateReturnValueWrapper(F);
for (auto &Arg : F.Arguments())
revng_assert(Arg.Type()->isScalar());
}
/// Print a typedef for a RawFunctionDefinition, that can be used when you have
/// a variable that is a pointer to a function.
void ptml::CTypeBuilder::printTypeDeclaration(const RFT &F) {
printFunctionWrappers(F);
*Out << getModelComment(F) << getKeyword(ptml::CBuilder::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.
printFunctionPrototype(F);
*Out << ";\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
/// CABI functions.
void ptml::CTypeBuilder::generateArrayWrapper(const model::ArrayType
&ArrayType) {
// Check if the wrapper was already added
auto [It, IsNew] = ArtificialNameCache.emplace(ArrayType,
getArrayWrapper(ArrayType));
if (not IsNew)
return;
*Out << getKeyword(ptml::CBuilder::Keyword::Typedef) << " "
<< getKeyword(ptml::CBuilder::Keyword::Struct) << " "
<< ptml::AttributeRegistry::getAttribute<"_PACKED">() << " ";
{
Scope Scope(*Out, ptml::c::scopes::StructBody);
*Out << getNamedCInstance(ArrayType,
NameBuilder.artificialArrayWrapperFieldName())
<< ";\n";
}
*Out << " " << tokenTag(It->second, 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.
using CFT = model::CABIFunctionDefinition;
void ptml::CTypeBuilder::printFunctionWrappers(const CFT &F) {
if (not F.ReturnType().isEmpty())
if (auto *Array = F.ReturnType()->getArray())
generateArrayWrapper(*Array);
for (auto &Arg : F.Arguments())
if (auto *Array = Arg.Type()->getArray())
generateArrayWrapper(*Array);
}
/// Print a typedef for a CABI function, that can be used when you have
/// a variable that is a pointer to a function.
void ptml::CTypeBuilder::printTypeDeclaration(const CFT &F) {
printFunctionWrappers(F);
*Out << getModelComment(F) << getKeyword(ptml::CBuilder::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.
printFunctionPrototype(F);
*Out << ";\n";
}
void ptml::CTypeBuilder::printTypeDeclaration(const model::TypeDefinition &T) {
if (auto *Enum = llvm::dyn_cast<model::EnumDefinition>(&T))
printForwardTypeDeclaration(*Enum);
else if (auto *Struct = llvm::dyn_cast<model::StructDefinition>(&T))
printForwardTypeDeclaration(*Struct);
else if (auto *Union = llvm::dyn_cast<model::UnionDefinition>(&T))
printForwardTypeDeclaration(*Union);
else if (auto *Typedef = llvm::dyn_cast<model::TypedefDefinition>(&T))
printTypeDeclaration(*Typedef);
else if (auto *RFT = llvm::dyn_cast<model::RawFunctionDefinition>(&T))
printTypeDeclaration(*RFT);
else if (auto *CFT = llvm::dyn_cast<model::CABIFunctionDefinition>(&T))
printTypeDeclaration(*CFT);
else
revng_abort("Unsupported type definition.");
}
void ptml::CTypeBuilder::printTypeDefinition(const model::TypeDefinition &T) {
if (isDeclarationTheSameAsDefinition(T))
printTypeDeclaration(T);
else if (auto *Struct = llvm::dyn_cast<model::StructDefinition>(&T))
printTypeDefinition(*Struct);
else if (auto *Union = llvm::dyn_cast<model::UnionDefinition>(&T))
printTypeDefinition(*Union);
else if (auto *Enum = llvm::dyn_cast<model::EnumDefinition>(&T))
printTypeDefinition(*Enum);
else
revng_abort("Unsupported type definition.");
}
void ptml::CTypeBuilder::printInlineDefinition(llvm::StringRef Name,
const model::Type &T) {
const model::TypeDefinition *Definition = T.skipToDefinition();
revng_assert(Definition, "Primitives cannot be printed inline.");
auto Suffix = getNamedCInstance(T, Name, {}, true).str().str();
if (auto *Struct = llvm::dyn_cast<model::StructDefinition>(Definition)) {
printTypeDefinition(*Struct, std::move(Suffix));
} else if (auto *U = llvm::dyn_cast<model::UnionDefinition>(Definition)) {
printTypeDefinition(*U, std::move(Suffix));
} else if (auto *Enum = llvm::dyn_cast<model::EnumDefinition>(Definition)) {
printTypeDefinition(*Enum, std::move(Suffix));
} else {
revng_abort("Only enums, structs, and unions can be printed inline.");
}
}
static Logger<> InlineTypeLog{ "inline-type-selection" };
void ptml::CTypeBuilder::collectInlinableTypes() {
if (not DependencyCache.has_value())
DependencyCache = buildDependencyGraph(Binary.TypeDefinitions());
StackFrameTypeCache = {};
for (const model::Function &Function : Binary.Functions())
if (auto *StackFrame = Function.stackFrameType())
StackFrameTypeCache.insert(StackFrame->key());
if (Configuration.EnableTypeInlining
|| Configuration.EnableStackFrameInlining) {
std::map<model::TypeDefinition::Key, uint64_t> DependentTypeCount;
for (const auto *Node : DependencyCache->nodes()) {
if (isDeclarationTheSameAsDefinition(*Node->T)) {
// Skip types that never produce a definition since there's no point
// inlining them.
continue;
}
auto [Iterator, _] = DependentTypeCount.try_emplace(Node->T->key(), 0);
Iterator->second += Node->predecessorCount();
if (Node->K == TypeNode::Kind::Declaration) {
// Ignore a reference from a type definition to its own declaration.
// But only do so if there is exactly one. If there are more, keep it in
// order to ensure it is never marked for inlining.
auto SelfEdgeCounter = [Key = Node->T->key()](auto *N) {
return N->T->key() == Key;
};
if (llvm::count_if(Node->predecessors(), SelfEdgeCounter) == 1)
--Iterator->second;
// Since dependency graph does not take functions into account,
// explicitly add one "use" to each struct that appears as a function
// stack frame.
if (StackFrameTypeCache.contains(Node->T->key()))
++Iterator->second;
}
if (InlineTypeLog.isEnabled()) {
if (Node->K == TypeNode::Kind::Declaration)
InlineTypeLog << "Declaration of '";
else
InlineTypeLog << "Definition of '";
InlineTypeLog << ::toString(Node->T->key())
<< "' is depended on by: {\n";
for (auto *Predecessor : Node->predecessors()) {
if (Predecessor->K == TypeNode::Kind::Declaration)
InlineTypeLog << "- Declaration of '";
else
InlineTypeLog << "- Definition of '";
InlineTypeLog << ::toString(Predecessor->T->key()) << "'\n";
}
InlineTypeLog << "}\n" << DoLog;
}
}
auto SingleDependencyFilter = std::views::filter([](const auto &Pair) {
return Pair.second == 1;
});
TypesToInlineCache = DependentTypeCount | SingleDependencyFilter
| std::views::keys
| revng::to<std::set<model::TypeDefinition::Key>>();
if (InlineTypeLog.isEnabled()) {
revng_log(InlineTypeLog, "Final list of types that can be inlined: {");
{
LoggerIndent Indent{ InlineTypeLog };
for (const model::TypeDefinition::Key &T : TypesToInlineCache)
revng_log(InlineTypeLog, ::toString(T));
}
revng_log(InlineTypeLog, "}");
}
}
if (Configuration.EnableStackFrameInlining && InlineTypeLog.isEnabled()) {
revng_log(InlineTypeLog, "Which also includes stack frames: {");
{
LoggerIndent Indent{ InlineTypeLog };
for (const model::TypeDefinition::Key &T : StackFrameTypeCache)
if (TypesToInlineCache.contains(T))
revng_log(InlineTypeLog, ::toString(T));
}
revng_log(InlineTypeLog, "}");
}
InlinableCacheIsReady = true;
}
static Logger<> TypePrinterLog{ "type-definition-printer" };
void ptml::CTypeBuilder::printTypeDefinitions() {
if (not DependencyCache.has_value())
DependencyCache = buildDependencyGraph(Binary.TypeDefinitions());
const auto &TypeNodes = DependencyCache->TypeNodes();
std::set<const TypeDependencyNode *> Defined;
for (const auto *Root : DependencyCache->nodes()) {
revng_log(TypePrinterLog, "PostOrder from Root:" << getNodeLabel(Root));
for (const auto *Node : llvm::post_order_ext(Root, Defined)) {
LoggerIndent PostOrderIndent{ TypePrinterLog };
revng_log(TypePrinterLog, "post_order visiting: " << getNodeLabel(Node));
const model::TypeDefinition *NodeT = Node->T;
const auto DeclKind = Node->K;
if (Configuration.TypesToOmit.contains(NodeT->key())) {
revng_log(TypePrinterLog, "Omitted");
continue;
}
constexpr auto Declaration = TypeNode::Kind::Declaration;
if (DeclKind == Declaration) {
revng_log(TypePrinterLog, "Declaration");
// Print the declaration. Notice that the forward declarations are
// emitted even for inlined types, because it's only the full definition
// that will be inlined.
printTypeDeclaration(*NodeT);
} else {
revng_log(TypePrinterLog, "Definition");
revng_assert(Defined.contains(TypeNodes.at({ NodeT, Declaration })));
if (isDeclarationTheSameAsDefinition(*NodeT) or shouldInline(*NodeT)) {
continue;
}
revng_log(TypePrinterLog, "printTypeDefinition");
printTypeDefinition(*NodeT);
}
*Out << "\n";
}
revng_log(TypePrinterLog, "PostOrder DONE");
}
}