Files
revng-revng/lib/Backend/DecompileFunction.cpp
T
Alessandro Di Federico 911ab00107 s/CDecompilation/Decompile/g
2024-02-09 09:03:34 +01:00

2285 lines
84 KiB
C++

//
// Copyright rev.ng Labs Srl. See LICENSE.md for details.
//
#include <utility>
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Progress.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include "revng/ABI/FunctionType/Layout.h"
#include "revng/EarlyFunctionAnalysis/FunctionMetadataCache.h"
#include "revng/Model/Binary.h"
#include "revng/Model/Helpers.h"
#include "revng/Model/IRHelpers.h"
#include "revng/Model/Identifier.h"
#include "revng/Model/PrimitiveTypeKind.h"
#include "revng/Model/QualifiedType.h"
#include "revng/Model/Qualifier.h"
#include "revng/Model/RawFunctionType.h"
#include "revng/Model/Segment.h"
#include "revng/Model/StructType.h"
#include "revng/Model/Type.h"
#include "revng/Model/VerifyHelper.h"
#include "revng/PTML/Constants.h"
#include "revng/PTML/IndentedOstream.h"
#include "revng/Pipeline/Location.h"
#include "revng/Support/Assert.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/YAMLTraits.h"
#include "revng/Yield/PTML.h"
#include "revng-c/Backend/DecompileFunction.h"
#include "revng-c/Backend/DecompiledCCodeIndentation.h"
#include "revng-c/InitModelTypes/InitModelTypes.h"
#include "revng-c/Pipes/Ranks.h"
#include "revng-c/RestructureCFG/ASTNode.h"
#include "revng-c/RestructureCFG/ASTNodeUtils.h"
#include "revng-c/RestructureCFG/ASTTree.h"
#include "revng-c/RestructureCFG/BeautifyGHAST.h"
#include "revng-c/RestructureCFG/RestructureCFG.h"
#include "revng-c/Support/DecompilationHelpers.h"
#include "revng-c/Support/FunctionTags.h"
#include "revng-c/Support/IRHelpers.h"
#include "revng-c/Support/ModelHelpers.h"
#include "revng-c/Support/PTMLC.h"
#include "revng-c/TypeNames/LLVMTypeNames.h"
#include "revng-c/TypeNames/ModelToPTMLTypeHelpers.h"
#include "revng-c/TypeNames/ModelTypeNames.h"
using llvm::cast;
using llvm::dyn_cast;
using llvm::isa;
using llvm::BasicBlock;
using llvm::CallInst;
using llvm::Instruction;
using llvm::raw_ostream;
using llvm::StringRef;
using model::Binary;
using model::CABIFunctionType;
using model::QualifiedType;
using model::RawFunctionType;
using model::TypedefType;
using pipeline::serializedLocation;
using ptml::Tag;
namespace ranks = revng::ranks;
namespace attributes = ptml::attributes;
namespace tokens = ptml::c::tokens;
using tokenDefinition::types::StringToken;
using TokenMapT = std::map<const llvm::Value *, std::string>;
using ModelTypesMap = std::map<const llvm::Value *, const model::QualifiedType>;
using InlineableTypesMap = std::unordered_map<const model::Function *,
std::set<const model::Type *>>;
using LocalVarDeclSet = llvm::SmallSetVector<const CallInst *, 4>;
using ASTVarDeclMap = std::unordered_map<const ASTNode *, LocalVarDeclSet>;
static constexpr const char *StackFrameVarName = "_stack";
static Logger<> Log{ "c-backend" };
static Logger<> VisitLog{ "c-backend-visit-order" };
static bool isAssignment(const llvm::Value *I) {
return isCallToTagged(I, FunctionTags::Assign);
}
static bool isLocalVarDecl(const llvm::Value *I) {
return isCallToTagged(I, FunctionTags::LocalVariable);
}
static bool isCallStackArgumentDecl(const llvm::Value *I) {
auto *Call = dyn_cast_or_null<llvm::CallInst>(I);
if (not Call)
return false;
auto *Callee = Call->getCalledFunction();
if (not Callee)
return false;
return Callee->getName().startswith("revng_call_stack_arguments");
}
static bool isStackFrameDecl(const llvm::Value *I) {
auto *Call = dyn_cast_or_null<llvm::CallInst>(I);
if (not Call)
return false;
auto *Callee = Call->getCalledFunction();
if (not Callee)
return false;
return Callee->getName().startswith("revng_stack_frame");
}
static const llvm::CallInst *isCallToNonIsolated(const llvm::Value *I) {
if (isCallToTagged(I, FunctionTags::QEMU)
or isCallToTagged(I, FunctionTags::Helper)
or isCallToTagged(I, FunctionTags::Exceptional)
or llvm::isa<llvm::IntrinsicInst>(I))
return llvm::cast<CallInst>(I);
return nullptr;
}
static bool isArtificialAggregateLocalVarDecl(const llvm::Value *I) {
return isCallToIsolatedFunction(I) and I->getType()->isAggregateType();
}
static bool isHelperAggregateLocalVarDecl(const llvm::Value *I) {
return isCallToNonIsolated(I) and I->getType()->isAggregateType();
}
static bool isCallToCustomOpcode(const llvm::Instruction *I) {
return isCallToTagged(I, FunctionTags::Copy)
or isCallToTagged(I, FunctionTags::Assign)
or isCallToTagged(I, FunctionTags::ModelCast)
or isCallToTagged(I, FunctionTags::ModelGEP)
or isCallToTagged(I, FunctionTags::ModelGEPRef)
or isCallToTagged(I, FunctionTags::AddressOf)
or isCallToTagged(I, FunctionTags::Parentheses)
or isCallToTagged(I, FunctionTags::OpaqueCSVValue)
or isCallToTagged(I, FunctionTags::OpaqueExtractValue)
or isCallToTagged(I, FunctionTags::StructInitializer)
or isCallToTagged(I, FunctionTags::SegmentRef)
or isCallToTagged(I, FunctionTags::UnaryMinus)
or isCallToTagged(I, FunctionTags::BinaryNot)
or isCallToTagged(I, FunctionTags::BooleanNot)
or isCallToTagged(I, FunctionTags::StringLiteral);
}
static bool isCConstant(const llvm::Value *V) {
return isa<llvm::Constant>(V)
or isCallToTagged(V, FunctionTags::LiteralPrintDecorator);
}
static std::string addAlwaysParentheses(llvm::StringRef Expr) {
return std::string("(") + Expr.str() + ")";
}
static std::string get128BitIntegerHexConstant(llvm::APInt Value,
const ptml::PTMLCBuilder &B,
const model::Binary &Model) {
revng_assert(Value.getBitWidth() > 64);
revng_assert(Value.getBitWidth() <= 128);
using PTMLOperator = ptml::PTMLCBuilder::Operator;
using model::PrimitiveTypeKind::Unsigned;
model::QualifiedType
U128 = model::QualifiedType(Model.getPrimitiveType(Unsigned, 16), {});
std::string Cast = addAlwaysParentheses(getTypeName(U128, B));
if (Value.isZero())
return addAlwaysParentheses(Cast + " " + B.getNumber(0));
// In C, even if you can have 128-bit variables, you cannot have 128-bit
// literals, so we need this hack to assign a big constant value to a
// 128-bit variable.
llvm::APInt HighBits = Value.getHiBits(Value.getBitWidth() - 64);
llvm::APInt LowBits = Value.getLoBits(64);
bool NeedsOr = not HighBits.isZero() and not LowBits.isZero();
std::string CompositeConstant = Cast + " ";
if (not HighBits.isZero()) {
StringToken HighBitsString;
HighBits.toString(HighBitsString,
/*radix=*/16,
/*signed=*/false,
/*formatAsCLiteral=*/true);
auto HighConst = B.getConstantTag(HighBitsString) + " "
+ B.getOperator(PTMLOperator::LShift) + " "
+ B.getNumber(64);
CompositeConstant += HighConst;
}
if (NeedsOr)
CompositeConstant += " " + B.getOperator(PTMLOperator::Or) + " ";
if (not LowBits.isZero()) {
StringToken LowBitsString;
LowBits.toString(LowBitsString,
/*radix=*/16,
/*signed=*/false,
/*formatAsCLiteral=*/true);
CompositeConstant += B.getConstantTag(LowBitsString).serialize();
}
return addAlwaysParentheses(CompositeConstant);
}
static std::string hexLiteral(const llvm::ConstantInt *Int,
const ptml::PTMLCBuilder &B,
const model::Binary &Model) {
StringToken Formatted;
if (Int->getBitWidth() <= 64) {
Int->getValue().toString(Formatted,
/*radix*/ 16,
/*signed*/ false,
/*formatAsCLiteral*/ true);
return Formatted.str().str();
}
return get128BitIntegerHexConstant(Int->getValue(), B, Model);
}
static std::string charLiteral(const llvm::ConstantInt *Int) {
revng_assert(Int->getValue().getBitWidth() == 8);
const auto LimitedValue = Int->getLimitedValue(0xffu);
const auto CharValue = static_cast<char>(LimitedValue);
std::string EscapedC;
llvm::raw_string_ostream EscapeCStream(EscapedC);
EscapeCStream.write_escaped(std::string(&CharValue, 1));
std::string EscapedHTML;
llvm::raw_string_ostream EscapeHTMLStream(EscapedHTML);
llvm::printHTMLEscaped(EscapedC, EscapeHTMLStream);
return llvm::formatv("'{0}'", EscapeHTMLStream.str());
}
static std::string boolLiteral(const llvm::ConstantInt *Int) {
revng_assert(Int->getBitWidth() == 1);
if (Int->isZero()) {
return "false";
} else {
return "true";
}
}
struct CCodeGenerator {
private:
/// The model of the binary being analysed
const Binary &Model;
/// The LLVM function that is being decompiled
const llvm::Function &LLVMFunction;
/// The model function corresponding to LLVMFunction
const model::Function &ModelFunction;
/// The model prototype of ModelFunction
const model::Type &Prototype;
/// The (combed) control flow AST
const ASTTree &GHAST;
/// A map that associates to each ASTNode, a set of variables to be declared
/// in that scope, with a specific order.
/// A variable is represented by a CallInst to LocalVariable
const ASTVarDeclMap &VariablesToDeclare;
/// A map containing a model type for each LLVM value in the function
const ModelTypesMap TypeMap;
/// Where to output the decompiled C code
ptml::PTMLIndentedOstream Out;
ptml::PTMLCBuilder B;
/// Name of the local variable used to break out of loops from within nested
/// switches
std::vector<std::string> SwitchStateVars;
FunctionMetadataCache &Cache;
private:
class VarNameGenerator {
private:
uint64_t CurVarID = 0;
public:
std::string nextVarName() { return "_var_" + to_string(CurVarID++); }
StringToken nextSwitchStateVar() {
StringToken StateVar("_break_from_loop_");
StateVar += to_string(CurVarID++);
return StateVar;
}
};
/// Stateful generator for variable names
VarNameGenerator NameGenerator;
/// Keep track of the names associated with function arguments, and local
/// variables. In the past it also kept track of intermediate expressions, but
/// with the new design all the tokens corresponding to instructions that
/// don't represent local variables are recomputed every time.
TokenMapT TokenMap;
private:
/// Name of the local variable used to break out from loops
std::string LoopStateVar;
std::string LoopStateVarDeclaration;
private:
/// Emission of parentheses may change whether the OPRP is enabled or not
bool IsOperatorPrecedenceResolutionPassEnabled = false;
public:
CCodeGenerator(FunctionMetadataCache &Cache,
const Binary &Model,
const llvm::Function &LLVMFunction,
const ASTTree &GHAST,
const ASTVarDeclMap &VarToDeclare,
raw_ostream &Out,
ptml::PTMLCBuilder &B) :
Model(Model),
LLVMFunction(LLVMFunction),
ModelFunction(*llvmToModelFunction(Model, LLVMFunction)),
Prototype(*ModelFunction.prototype(Model).getConst()),
GHAST(GHAST),
VariablesToDeclare(VarToDeclare),
TypeMap(initModelTypes(Cache,
LLVMFunction,
&ModelFunction,
Model,
/*PointersOnly=*/false)),
Out(Out, DecompiledCCodeIndentation),
B(B),
SwitchStateVars(),
Cache(Cache) {
// TODO: don't use a global loop state variable
static const char *LoopStateVarName = "_loop_state_var";
LoopStateVar = getVariableLocationReference(LoopStateVarName,
ModelFunction,
B);
LoopStateVarDeclaration = getVariableLocationDefinition(LoopStateVarName,
ModelFunction,
B);
if (LLVMFunction.getMetadata(ExplicitParenthesesMDName))
IsOperatorPrecedenceResolutionPassEnabled = true;
}
void emitFunction(bool NeedsLocalStateVar, InlineableTypesMap &StackTypes);
private:
/// Visit a GHAST node and all its children recursively, emitting BBs
/// and control flow statements in the process.
RecursiveCoroutine<void> emitGHASTNode(const ASTNode *Node);
/// Recursively build a C string representing the condition contained
/// in an ExprNode (which might be composed by one or more subexpressions).
/// An additional parameter is used to decide whether the basic block
/// associated to an atomic or compare node should be emitted on-the-fly.
RecursiveCoroutine<std::string> buildGHASTCondition(const ExprNode *E,
bool EmitBB);
RecursiveCoroutine<std::string>
makeLoopCondition(const IfNode *LoopCondition) {
revng_assert(LoopCondition);
// Retrieve the expression of the condition as well as emitting its
// associated basic block
bool EmitBB = not LoopCondition->isWeaved();
rc_return rc_recur buildGHASTCondition(LoopCondition->getCondExpr(),
EmitBB);
}
/// Serialize a basic block into a series of C statements.
void emitBasicBlock(const BasicBlock *BB, bool EmitReturn);
private:
RecursiveCoroutine<std::string> getToken(const llvm::Value *V) const;
RecursiveCoroutine<std::string>
getCallToken(const llvm::CallInst *Call,
const llvm::StringRef FuncName,
const model::Type *Prototype) const;
RecursiveCoroutine<std::string> getConstantToken(const llvm::Value *V) const;
RecursiveCoroutine<std::string>
getInstructionToken(const llvm::Instruction *I) const;
RecursiveCoroutine<std::string>
getCustomOpcodeToken(const llvm::CallInst *C) const;
RecursiveCoroutine<std::string>
getModelGEPToken(const llvm::CallInst *C) const;
RecursiveCoroutine<std::string>
getIsolatedCallToken(const llvm::CallInst *C) const;
RecursiveCoroutine<std::string>
getNonIsolatedCallToken(const llvm::CallInst *C) const;
private:
std::string addParentheses(llvm::StringRef Expr) const;
std::string buildDerefExpr(llvm::StringRef Expr) const;
std::string buildAddressExpr(llvm::StringRef Expr) const;
/// Return a C string that represents a cast of \a ExprToCast to a given
/// \a DestType. If no casting is needed between the two expression, the
/// original expression is returned.
std::string buildCastExpr(StringRef ExprToCast,
const model::QualifiedType &SrcType,
const model::QualifiedType &DestType) const;
private:
std::string createStackFrameVarDeclName(const llvm::Instruction *I) {
revng_assert(isStackFrameDecl(I));
revng_assert(not TokenMap.contains(I));
std::string VarName = StackFrameVarName;
TokenMap[I] = getVariableLocationReference(VarName, ModelFunction, B);
return getVariableLocationDefinition(VarName, ModelFunction, B);
}
std::string createLocalVarDeclName(const llvm::Instruction *I) {
revng_assert(isLocalVarDecl(I) or isArtificialAggregateLocalVarDecl(I)
or isCallStackArgumentDecl(I));
std::string VarName = NameGenerator.nextVarName();
// This may override the entry for I, if I belongs to a "duplicated"
// BasicBlock that is reachable from many paths on the GHAST.
TokenMap[I] = getVariableLocationReference(VarName, ModelFunction, B);
return getVariableLocationDefinition(VarName, ModelFunction, B);
}
std::string getVarName(const llvm::Instruction *I) const {
revng_assert(isStackFrameDecl(I) or isLocalVarDecl(I)
or isArtificialAggregateLocalVarDecl(I)
or isCallStackArgumentDecl(I));
revng_assert(TokenMap.contains(I));
return TokenMap.at(I);
};
};
std::string CCodeGenerator::addParentheses(llvm::StringRef Expr) const {
if (IsOperatorPrecedenceResolutionPassEnabled)
return Expr.str();
return addAlwaysParentheses(Expr);
}
std::string CCodeGenerator::buildDerefExpr(llvm::StringRef Expr) const {
using PTMLOperator = ptml::PTMLCBuilder::Operator;
return B.getOperator(PTMLOperator::PointerDereference) + addParentheses(Expr);
}
std::string CCodeGenerator::buildAddressExpr(llvm::StringRef Expr) const {
return B.getOperator(ptml::PTMLCBuilder::Operator::AddressOf)
+ addParentheses(Expr);
}
std::string
CCodeGenerator::buildCastExpr(StringRef ExprToCast,
const model::QualifiedType &SrcType,
const model::QualifiedType &DestType) const {
if (SrcType == DestType or SrcType.UnqualifiedType().empty()
or DestType.UnqualifiedType().empty())
return ExprToCast.str();
revng_assert((SrcType.isScalar() or SrcType.isPointer())
and (DestType.isScalar() or DestType.isPointer()));
return addAlwaysParentheses(getTypeName(DestType, B)) + " "
+ addParentheses(ExprToCast);
}
static std::string getUndefToken(model::QualifiedType UndefType,
const ptml::PTMLCBuilder &B) {
UndefType = peelConstAndTypedefs(UndefType);
revng_assert(UndefType.isPrimitive());
revng_assert(UndefType.Qualifiers().empty());
std::string Result = "_undef_";
Result += UndefType.UnqualifiedType().getConst()->name().str().str() + "()";
return Result;
}
static std::string getFormattedIntegerToken(const llvm::CallInst *Call,
const ptml::PTMLCBuilder &B,
const model::Binary &Model) {
if (isCallToTagged(Call, FunctionTags::HexInteger)) {
const auto Operand = Call->getArgOperand(0);
const auto *Value = cast<llvm::ConstantInt>(Operand);
return B.getConstantTag(hexLiteral(Value, B, Model)).serialize();
}
if (isCallToTagged(Call, FunctionTags::CharInteger)) {
const auto Operand = Call->getArgOperand(0);
const auto *Value = cast<llvm::ConstantInt>(Operand);
return B.getConstantTag(charLiteral(Value)).serialize();
}
if (isCallToTagged(Call, FunctionTags::BoolInteger)) {
const auto Operand = Call->getArgOperand(0);
const auto *Value = cast<llvm::ConstantInt>(Operand);
return B.getConstantTag(boolLiteral(Value)).serialize();
}
if (isCallToTagged(Call, FunctionTags::NullPtr)) {
const auto Operand = Call->getArgOperand(0);
const auto *Value = cast<llvm::ConstantInt>(Operand);
revng_assert(Value->isZero());
return B.getNullTag().serialize();
}
std::string Error = "Cannot get token for custom opcode: "
+ dumpToString(Call);
revng_abort(Error.c_str());
return "";
}
RecursiveCoroutine<std::string>
CCodeGenerator::getConstantToken(const llvm::Value *C) const {
revng_assert(isCConstant(C));
if (auto *Undef = dyn_cast<llvm::UndefValue>(C))
rc_return getUndefToken(TypeMap.at(Undef), B);
if (auto *Null = dyn_cast<llvm::ConstantPointerNull>(C))
rc_return B.getNullTag().serialize();
if (auto *Const = dyn_cast<llvm::ConstantInt>(C)) {
llvm::APInt Value = Const->getValue();
if (Value.isIntN(64))
rc_return B.getNumber(Value).serialize();
else
rc_return get128BitIntegerHexConstant(Value, B, Model);
}
if (auto *Global = dyn_cast<llvm::GlobalVariable>(C)) {
using namespace llvm;
// Check if initializer is a CString
auto *Initializer = Global->getInitializer();
StringRef Content = "";
if (auto StringInit = dyn_cast<ConstantDataArray>(Initializer)) {
// If it's not a C string, bail out
if (not StringInit->isCString())
revng_abort(dumpToString(Global).c_str());
// If it's a C string, Drop the terminator
Content = StringInit->getAsString().drop_back();
} else {
// Zero initializers are always valid c empty strings, in all the
// other cases, bail out
if (not isa<llvm::ConstantAggregateZero>(Initializer))
revng_abort(dumpToString(Global).c_str());
}
std::string Escaped;
{
raw_string_ostream Stream(Escaped);
Stream << "\"";
Stream.write_escaped(Content);
Stream << "\"";
}
rc_return Escaped;
}
if (auto *ConstExpr = dyn_cast<llvm::ConstantExpr>(C)) {
switch (ConstExpr->getOpcode()) {
case Instruction::IntToPtr: {
const auto *Operand = cast<llvm::Constant>(ConstExpr->getOperand(0));
const QualifiedType &SrcType = TypeMap.at(Operand);
const QualifiedType &DstType = TypeMap.at(ConstExpr);
// IntToPtr has no effect on values that we already know to be pointers
if (SrcType.isPointer())
rc_return rc_recur getConstantToken(Operand);
else
rc_return buildCastExpr(rc_recur getConstantToken(Operand),
SrcType,
DstType);
} break;
default:
revng_abort(dumpToString(ConstExpr).c_str());
}
}
if (isCallToTagged(C, FunctionTags::LiteralPrintDecorator))
rc_return getFormattedIntegerToken(cast<llvm::CallInst>(C), B, Model);
std::string Error = "Cannot get token for llvm::Constant: ";
Error += dumpToString(C).c_str();
revng_abort(Error.c_str());
rc_return "";
}
/// Traverse all nested typedefs inside \a QT, skipping const Qualifiers, and
/// returns a QualifiedType that represents the full traversal.
static RecursiveCoroutine<QualifiedType>
flattenTypedefsIgnoringConst(const QualifiedType &QT) {
QualifiedType Result = peelConstAndTypedefs(QT);
if (auto *TD = dyn_cast<TypedefType>(Result.UnqualifiedType().getConst())) {
auto &Underlying = TD->UnderlyingType();
QualifiedType Nested = rc_recur flattenTypedefsIgnoringConst(Underlying);
Result.UnqualifiedType() = Nested.UnqualifiedType();
llvm::move(Nested.Qualifiers(), std::back_inserter(Result.Qualifiers()));
}
rc_return Result;
}
RecursiveCoroutine<std::string>
CCodeGenerator::getModelGEPToken(const llvm::CallInst *Call) const {
revng_assert(isCallToTagged(Call, FunctionTags::ModelGEP)
or isCallToTagged(Call, FunctionTags::ModelGEPRef));
revng_assert(Call->arg_size() >= 2);
bool IsRef = isCallToTagged(Call, FunctionTags::ModelGEPRef);
// First argument is a string containing the base type
auto *CurArg = Call->arg_begin();
QualifiedType CurType = deserializeFromLLVMString(CurArg->get(), Model);
// Second argument is the base llvm::Value
++CurArg;
llvm::Value *BaseValue = CurArg->get();
std::string BaseString = rc_recur getToken(BaseValue);
bool UseArrow = false;
if (IsRef) {
// In ModelGEPRefs, the base value is a reference, and the base type is
// its type
revng_assert(TypeMap.at(BaseValue) == CurType,
"The ModelGEP base type is not coherent with the "
"propagated type.");
// If there are no further arguments we're just dereferencing the base value
if (std::next(CurArg) == Call->arg_end()) {
// But dereferencing a reference does not produce any code so we're done
rc_return BaseString;
}
} else {
// In ModelGEPs, the base value is a pointer, and the base type is the
// type pointed by the base value
QualifiedType PointerQt = CurType.getPointerTo(Model.Architecture());
revng_assert(TypeMap.at(BaseValue) == PointerQt,
"The ModelGEP base type is not coherent with the "
"propagated type.");
auto *ThirdArgument = Call->getArgOperand(2);
auto *ConstantArrayIndex = dyn_cast<llvm::ConstantInt>(ThirdArgument);
// Check if the ModelGEP represents an additional access with square
// brackets on the pointer
bool HasInitialArrayAccess = not ConstantArrayIndex
or not ConstantArrayIndex->isZero();
// If this doesn't have any variadic argument just dereference the base
// pointer and we're done.
if (Call->arg_size() < 4) {
// There are actually various ways to do it.
// If we're not using square brackets to dereference the pointer, we just
// emit a dereference expression.
if (not HasInitialArrayAccess)
rc_return buildDerefExpr(BaseString);
// Here we have square brackets, that effectively replace the dereference
// operator, so we just emit the square brackets with the appropriate
// index.
std::string IndexExpr;
if (auto *Const = dyn_cast<llvm::ConstantInt>(ThirdArgument)) {
IndexExpr = B.getNumber(Const->getValue()).serialize();
} else {
IndexExpr = rc_recur getToken(ThirdArgument);
}
rc_return BaseString + "[" + IndexExpr + "]";
}
// Here we know that there is at least one variadic argument.
if (HasInitialArrayAccess) {
// If we're using the square brackets to dereference the base pointer we
// have to change the base type so that it represents the "fake" array
// being accessed.
// We make it with only 1 element because in the following the number of
// elements of the array is not actually used for generating the C code,
// so we can get away with it.
auto LongArray = model::Qualifier::createArray(1);
PointerQt.Qualifiers().front() = std::move(LongArray);
CurType = PointerQt;
} else {
// Otherwise, we're not accessing the base pointer as an array.
// So we can skip an additional argument.
++CurArg;
// But the base type could still be an array.
if (CurType.isArray()) {
// If the base type is an array the first level of indirection will be
// represented by square brackets that want to access elements of the
// array. So we have to first dereference the pointer-to-array in order
// to be able to access elements via [] in C.
BaseString = "(" + buildDerefExpr(BaseString) + ")";
} else {
// If CurType is not an array we're going to represent the first level
// of the traversal with the `->` operator rather than `.`, so let's
// take note of this fact.
UseArrow = true;
}
}
}
++CurArg;
std::string CurExpr = addParentheses(BaseString);
using PTMLOperator = ptml::PTMLCBuilder::Operator;
Tag Deref = UseArrow ? B.getOperator(PTMLOperator::Arrow) :
B.getOperator(PTMLOperator::Dot);
// Traverse the model to decide whether to emit "." or "[]"
for (; CurArg != Call->arg_end(); ++CurArg) {
CurType = flattenTypedefsIgnoringConst(CurType);
auto &Qualifiers = CurType.Qualifiers();
if (not Qualifiers.empty()) {
// If it's an array or a pointer, add "[]"
// Get the ArrayQualifier out, and drop it.
model::Qualifier ArrayQualifier = Qualifiers.front();
revng_assert(model::Qualifier::isArray(ArrayQualifier));
Qualifiers.erase(Qualifiers.begin());
std::string IndexExpr;
if (auto *Const = dyn_cast<llvm::ConstantInt>(CurArg->get())) {
IndexExpr = B.getNumber(Const->getValue()).serialize();
} else {
IndexExpr = rc_recur getToken(CurArg->get());
}
CurExpr += "[" + IndexExpr + "]";
} else {
// If it's a struct or union, we can only navigate it with fixed
// indexes.
// TODO: decide how to emit constants
auto *FieldIdxConst = cast<llvm::ConstantInt>(CurArg->get());
uint64_t FieldIdx = FieldIdxConst->getValue().getLimitedValue();
CurExpr += Deref.serialize();
// Find the field name
const auto *UnqualType = CurType.UnqualifiedType().getConst();
if (auto *Struct = dyn_cast<model::StructType>(UnqualType)) {
const model::StructField &Field = Struct->Fields().at(FieldIdx);
CurExpr += B.getLocationReference(*Struct, Field);
CurType = Struct->Fields().at(FieldIdx).Type();
} else if (auto *Union = dyn_cast<model::UnionType>(UnqualType)) {
const model::UnionField &Field = Union->Fields().at(FieldIdx);
CurExpr += B.getLocationReference(*Union, Field);
CurType = Union->Fields().at(FieldIdx).Type();
} else {
CurType.dump();
revng_abort("Unexpected ModelGEP type found: ");
}
}
// Regardless if the base type was a pointer or not, we are now
// navigating only references
Deref = B.getOperator(PTMLOperator::Dot);
}
rc_return CurExpr;
}
RecursiveCoroutine<std::string>
CCodeGenerator::getCustomOpcodeToken(const llvm::CallInst *Call) const {
if (isAssignment(Call)) {
const llvm::Value *StoredVal = Call->getArgOperand(0);
const llvm::Value *PointerVal = Call->getArgOperand(1);
rc_return rc_recur getToken(PointerVal) + " "
+ B.getOperator(ptml::PTMLCBuilder::Operator::Assign) + " "
+ rc_recur getToken(StoredVal);
}
if (isCallToTagged(Call, FunctionTags::Copy))
rc_return rc_recur getToken(Call->getArgOperand(0));
if (isCallToTagged(Call, FunctionTags::ModelGEP)
or isCallToTagged(Call, FunctionTags::ModelGEPRef))
rc_return rc_recur getModelGEPToken(Call);
if (isCallToTagged(Call, FunctionTags::ModelCast)) {
// First argument is a string containing the base type
auto *CurArg = Call->arg_begin();
QualifiedType CurType = deserializeFromLLVMString(CurArg->get(), Model);
// Second argument is the base llvm::Value
++CurArg;
llvm::Value *BaseValue = CurArg->get();
// Emit the parenthesized cast expr, and we are done
std::string StringToCast = rc_recur getToken(BaseValue);
rc_return buildCastExpr(StringToCast, TypeMap.at(BaseValue), CurType);
}
if (isCallToTagged(Call, FunctionTags::AddressOf)) {
// First operand is the type of the value being addressed (should not
// introduce casts)
QualifiedType ArgType = deserializeFromLLVMString(Call->getArgOperand(0),
Model);
// Second argument is the value being addressed
llvm::Value *Arg = Call->getArgOperand(1);
revng_assert(ArgType == TypeMap.at(Arg));
std::string ArgString = rc_recur getToken(Arg);
rc_return buildAddressExpr(ArgString);
}
if (isCallToTagged(Call, FunctionTags::Parentheses)) {
std::string Operand0 = rc_recur getToken(Call->getArgOperand(0));
rc_return addAlwaysParentheses(Operand0);
}
if (isCallToTagged(Call, FunctionTags::StructInitializer)) {
// Struct initializers should be used only to pack together return
// values of RawFunctionTypes that return multiple values, therefore
// they must have the same type as the function's return type
auto *StructTy = cast<llvm::StructType>(Call->getType());
revng_assert(Call->getFunction()->getReturnType() == StructTy);
revng_assert(LLVMFunction.getReturnType() == StructTy);
auto StrucTypeName = getNamedInstanceOfReturnType(Prototype, "", B, false);
std::string StructInit = addAlwaysParentheses(StrucTypeName);
// Emit RHS
llvm::StringRef Separator = " {";
for (const auto &Arg : Call->args()) {
StructInit += Separator.str() + " " + rc_recur getToken(Arg);
Separator = ",";
}
StructInit += " }";
rc_return StructInit;
}
if (isCallToTagged(Call, FunctionTags::OpaqueExtractValue)) {
const llvm::Value *AggregateOp = Call->getArgOperand(0);
const auto *Idx = llvm::cast<llvm::ConstantInt>(Call->getArgOperand(1));
const auto *CallReturnsStruct = llvm::cast<llvm::CallInst>(AggregateOp);
const llvm::Function *Callee = CallReturnsStruct->getCalledFunction();
const auto CalleePrototype = Cache.getCallSitePrototype(Model,
CallReturnsStruct);
std::string StructFieldRef;
if (CalleePrototype.empty()) {
// The call returning a struct is a call to a helper function.
// It must be a direct call.
revng_assert(Callee);
StructFieldRef = getReturnStructFieldLocationReference(Callee,
Idx
->getZExtValue(),
B);
} else {
const model::Type *CalleeType = CalleePrototype.getConst();
auto RFT = llvm::cast<const model::RawFunctionType>(CalleeType);
uint64_t Index = Idx->getZExtValue();
StructFieldRef = std::next(RFT->ReturnValues().begin(), Index)
->name()
.str();
}
rc_return rc_recur getToken(AggregateOp) + "." + StructFieldRef;
}
if (isCallToTagged(Call, FunctionTags::SegmentRef)) {
auto *Callee = Call->getCalledFunction();
const auto &[StartAddress,
VirtualSize] = extractSegmentKeyFromMetadata(*Callee);
model::Segment Segment = Model.Segments().at({ StartAddress, VirtualSize });
auto Name = Segment.name();
rc_return B.getLocationReference(Segment);
}
if (isCallToTagged(Call, FunctionTags::Copy))
rc_return rc_recur getToken(Call->getArgOperand(0));
if (isCallToTagged(Call, FunctionTags::OpaqueCSVValue)) {
auto *Callee = Call->getCalledFunction();
std::string HelperRef = getHelperFunctionLocationReference(Callee, B);
rc_return rc_recur getCallToken(Call, HelperRef, /*prototype=*/nullptr);
}
using PTMLOperator = ptml::PTMLCBuilder::Operator;
if (isCallToTagged(Call, FunctionTags::UnaryMinus)) {
auto Operand = Call->getArgOperand(0);
std::string ToNegate = rc_recur getToken(Operand);
rc_return B.getOperator(PTMLOperator::UnaryMinus) + ToNegate;
}
if (isCallToTagged(Call, FunctionTags::BinaryNot)) {
auto Operand = Call->getArgOperand(0);
std::string ToNegate = rc_recur getToken(Operand);
rc_return(Operand->getType()->isIntegerTy(1) ?
B.getOperator(PTMLOperator::BoolNot) :
B.getOperator(PTMLOperator::BinaryNot))
+ ToNegate;
}
if (isCallToTagged(Call, FunctionTags::BooleanNot)) {
auto Operand = Call->getArgOperand(0);
std::string ToNegate = rc_recur getToken(Operand);
rc_return B.getOperator(PTMLOperator::BoolNot) + ToNegate;
}
if (isCallToTagged(Call, FunctionTags::StringLiteral)) {
const auto Operand = Call->getArgOperand(0);
std::string StringLiteral = rc_recur getToken(Operand);
std::string EscapedHTML;
{
llvm::raw_string_ostream EscapeHTMLStream(EscapedHTML);
llvm::printHTMLEscaped(StringLiteral, EscapeHTMLStream);
}
rc_return B.getStringLiteral(EscapedHTML).serialize();
}
std::string Error = "Cannot get token for custom opcode: "
+ dumpToString(Call);
revng_abort(Error.c_str());
rc_return "";
}
RecursiveCoroutine<std::string>
CCodeGenerator::getIsolatedCallToken(const llvm::CallInst *Call) const {
// Retrieve the CallEdge
const auto &[CallEdge, _] = Cache.getCallEdge(Model, Call);
revng_assert(CallEdge);
const auto &PrototypePath = Cache.getCallSitePrototype(Model, Call);
// Construct the callee token (can be a function name or a function
// pointer)
std::string CalleeToken;
if (not isa<llvm::Function>(Call->getCalledOperand())) {
std::string CalledString = rc_recur getToken(Call->getCalledOperand());
CalleeToken = addParentheses(CalledString);
} else {
if (not CallEdge->DynamicFunction().empty()) {
// Dynamic Function
auto &DynFuncID = CallEdge->DynamicFunction();
auto &DynamicFunc = Model.ImportedDynamicFunctions().at(DynFuncID);
std::string Location = serializedLocation(ranks::DynamicFunction,
DynamicFunc.key());
CalleeToken = B.getTag(ptml::tags::Span, DynamicFunc.name().str())
.addAttribute(attributes::Token, tokens::Function)
.addAttribute(attributes::ActionContextLocation, Location)
.addAttribute(attributes::LocationReferences, Location)
.serialize();
} else {
// Isolated function
llvm::Function *CalledFunc = Call->getCalledFunction();
revng_assert(CalledFunc);
const model::Function *ModelFunc = llvmToModelFunction(Model,
*CalledFunc);
revng_assert(ModelFunc);
std::string Location = serializedLocation(ranks::Function,
ModelFunc->key());
CalleeToken = B.getTag(ptml::tags::Span, ModelFunc->name().str())
.addAttribute(attributes::Token, tokens::Function)
.addAttribute(attributes::ActionContextLocation, Location)
.addAttribute(attributes::LocationReferences, Location)
.serialize();
}
}
// Build the call expression
revng_assert(not CalleeToken.empty());
auto *Prototype = PrototypePath.get();
rc_return rc_recur getCallToken(Call, CalleeToken, Prototype);
}
RecursiveCoroutine<std::string>
CCodeGenerator::getNonIsolatedCallToken(const llvm::CallInst *Call) const {
auto *CalledFunc = Call->getCalledFunction();
revng_assert(CalledFunc and CalledFunc->hasName(),
"Special functions should all have a name");
std::string HelperRef = getHelperFunctionLocationReference(CalledFunc, B);
rc_return rc_recur getCallToken(Call, HelperRef, /*prototype=*/nullptr);
}
static bool shouldGenerateDebugInfoAsPTML(const llvm::Instruction &I) {
if (!I.getDebugLoc() || !I.getDebugLoc()->getScope())
return false;
// If the next instruction in the BB has different DebugLoc, generate the
// PTML location now.
auto NextInstr = std::next(I.getIterator());
if (NextInstr == I.getParent()->end() || !NextInstr->getDebugLoc()
|| NextInstr->getDebugLoc() != I.getDebugLoc())
return true;
return false;
}
static std::string addDebugInfo(const llvm::Instruction *I,
const std::string &Str,
const ptml::PTMLCBuilder &B) {
if (shouldGenerateDebugInfoAsPTML(*I)) {
std::string Location = I->getDebugLoc()->getScope()->getName().str();
return B.getTag(ptml::tags::Span, Str)
.addAttribute(ptml::attributes::LocationReferences, Location)
.addAttribute(ptml::attributes::ActionContextLocation, Location)
.serialize();
}
return Str;
}
/// Return the string that represents the given binary operator in C
static const std::string getBinOpString(const llvm::BinaryOperator *BinOp,
const ptml::PTMLCBuilder &B) {
const Tag Op = [&BinOp, &B]() {
bool IsBool = BinOp->getType()->isIntegerTy(1);
using PTMLOperator = ptml::PTMLCBuilder::Operator;
switch (BinOp->getOpcode()) {
case Instruction::Add:
return B.getOperator(ptml::PTMLCBuilder::Operator::Add);
case Instruction::Sub:
return B.getOperator(ptml::PTMLCBuilder::Operator::Sub);
case Instruction::Mul:
return B.getOperator(ptml::PTMLCBuilder::Operator::Mul);
case Instruction::SDiv:
case Instruction::UDiv:
return B.getOperator(ptml::PTMLCBuilder::Operator::Div);
case Instruction::SRem:
case Instruction::URem:
return B.getOperator(ptml::PTMLCBuilder::Operator::Modulo);
case Instruction::LShr:
case Instruction::AShr:
return B.getOperator(ptml::PTMLCBuilder::Operator::RShift);
case Instruction::Shl:
return B.getOperator(ptml::PTMLCBuilder::Operator::LShift);
case Instruction::And:
return IsBool ? B.getOperator(PTMLOperator::BoolAnd) :
B.getOperator(ptml::PTMLCBuilder::Operator::And);
case Instruction::Or:
return IsBool ? B.getOperator(PTMLOperator::BoolOr) :
B.getOperator(ptml::PTMLCBuilder::Operator::Or);
case Instruction::Xor:
return B.getOperator(ptml::PTMLCBuilder::Operator::Xor);
default:
revng_abort("Unknown const Binary operation");
}
}();
return " " + Op + " ";
}
/// Return the string that represents the given comparison operator in C
static const std::string getCmpOpString(const llvm::CmpInst::Predicate &Pred,
const ptml::PTMLCBuilder &B) {
using llvm::CmpInst;
const Tag Op = [&Pred, &B]() {
switch (Pred) {
case CmpInst::ICMP_EQ: ///< equal
return B.getOperator(ptml::PTMLCBuilder::Operator::CmpEq);
case CmpInst::ICMP_NE: ///< not equal
return B.getOperator(ptml::PTMLCBuilder::Operator::CmpNeq);
case CmpInst::ICMP_UGT: ///< unsigned greater than
case CmpInst::ICMP_SGT: ///< signed greater than
return B.getOperator(ptml::PTMLCBuilder::Operator::CmpGt);
case CmpInst::ICMP_UGE: ///< unsigned greater or equal
case CmpInst::ICMP_SGE: ///< signed greater or equal
return B.getOperator(ptml::PTMLCBuilder::Operator::CmpGte);
case CmpInst::ICMP_ULT: ///< unsigned less than
case CmpInst::ICMP_SLT: ///< signed less than
return B.getOperator(ptml::PTMLCBuilder::Operator::CmpLt);
case CmpInst::ICMP_ULE: ///< unsigned less or equal
case CmpInst::ICMP_SLE: ///< signed less or equal
return B.getOperator(ptml::PTMLCBuilder::Operator::CmpLte);
default:
revng_abort("Unknown comparison operator");
}
}();
return " " + Op + " ";
}
/// Returns a pair of QualifiedTypes to which LHS and RHS has to be casted to
/// for enabling an == or != comparison in C while preserving semantic.
static std::pair<model::QualifiedType, model::QualifiedType>
getCastTargetTypesForEqualityComparisons(model::QualifiedType LHS,
model::QualifiedType RHS) {
revng_assert(LHS.isScalar() and RHS.isScalar());
revng_assert(not LHS.isFloat() and not RHS.isFloat());
revng_assert(*LHS.size() == *RHS.size());
// If they are the same we don't have to cast anything.
if (LHS == RHS)
return { std::move(LHS), std::move(RHS) };
// If they are both pointer we don't have to cast anything.
// This could cause UB in case of strict-aliasing, but that's not something
// that we're trying to guarantee in decompiled code.
if (LHS.isPointer() and RHS.isPointer())
return { std::move(LHS), std::move(RHS) };
// In case only one is a pointer, given that they both have the same size, we
// can always cast the non-pointer to the pointer-type.
if (bool LHSIsPointer = LHS.isPointer(); LHSIsPointer != RHS.isPointer()) {
model::QualifiedType &Pointer = LHSIsPointer ? LHS : RHS;
return { Pointer, Pointer };
}
// At this point we have 2 non-pointer scalar types.
// Given that we've ruled out Float by assertions, we can just leave them as
// they are.
// Even if they mismatch, they have the same size, and in C we'll get an
// implicit reinterpret cast. This might raise some warning, but we'll deal
// with those.
// TODO: this is definitely sloppy, but doing the right thing would require to
// really think thoroughly about what's the best way to treat casts in
// general, and we haven't done it yet.
// At the moment some casts are emitted as ModelCast on the IR others are
// emitted on the fly during c-code-generation. Until we don't solve that
// problem systematically, this is a sloppy solution to prevent proliferation
// of casts, trading off the fact of not having warnings. So in practice this
// works at the cost of disabling more warnings on decompiled C code. Once
// we've solved this properly the warning can be re-enabled.
return { std::move(LHS), std::move(RHS) };
}
RecursiveCoroutine<std::string>
CCodeGenerator::getInstructionToken(const llvm::Instruction *I) const {
if (isa<llvm::BinaryOperator>(I) or isa<llvm::ICmpInst>(I)) {
const llvm::Value *Op0 = I->getOperand(0);
const llvm::Value *Op1 = I->getOperand(1);
std::string Op0Token = rc_recur getToken(Op0);
std::string Op1Token = rc_recur getToken(Op1);
const QualifiedType &OpType0 = TypeMap.at(Op0);
const QualifiedType &OpType1 = TypeMap.at(Op1);
revng_assert(OpType0.isScalar() and OpType1.isScalar());
uint64_t ByteSize = *OpType0.size();
// In principle, OpType0 and OpType1 should always have the same size.
// There is a notable exception though: we use LLVM with a 64bit DataLayout
// for which pointers are always 64-bits wide on LLVM IR, while they can be
// 32-bits wide on the model, depending on the binary we're decompiling.
// So the only situation where sizes are allowed to mismatch is when one of
// the operands is a pointer (on the model) and the other isn't.
if (*OpType0.size() != *OpType1.size()) {
// If this happens, only one of the two operands must be a pointer, and
// the other must be a constant integer that fits in the pointer size, at
// most masked behind a decorator.
revng_assert(OpType0.isPointer() xor OpType1.isPointer());
const QualifiedType &PointerModelType = OpType0.isPointer() ? OpType0 :
OpType1;
const QualifiedType &IntegerModelType = OpType0.isPointer() ? OpType1 :
OpType0;
auto PointerByteSize = *PointerModelType.size();
auto IntegerByteSize = *IntegerModelType.size();
revng_assert(PointerByteSize < IntegerByteSize);
const llvm::Value *IntegerOperand = OpType0.isPointer() ? Op1 : Op0;
auto *Integer = dyn_cast<llvm::ConstantInt>(IntegerOperand);
if (not Integer) {
using namespace FunctionTags;
auto *CallToDecorator = getCallToTagged(IntegerOperand,
LiteralPrintDecorator);
revng_assert(CallToDecorator);
Integer = cast<llvm::ConstantInt>(CallToDecorator->getArgOperand(0));
}
revng_assert(nullptr != Integer);
// In this case the size of the pointer wins
ByteSize = PointerByteSize;
}
if (auto *ICmp = dyn_cast<llvm::ICmpInst>(I)) {
revng_assert(not OpType0.isFloat() and not OpType1.isFloat());
if (ICmp->isEquality()) {
// Cast the two operands to a same common type for equality comparison.
const auto
&[TargetOp0Type,
TargetOp1Type] = getCastTargetTypesForEqualityComparisons(OpType0,
OpType1);
Op0Token = buildCastExpr(Op0Token, OpType0, TargetOp0Type);
Op1Token = buildCastExpr(Op1Token, OpType1, TargetOp0Type);
} else {
// If we're not doing eq or neq, we have to make sure that the
// signedness is compatible, otherwise it would break semantics.
using model::PrimitiveTypeKind::Signed;
using model::PrimitiveTypeKind::Unsigned;
auto ICmpKind = ICmp->isSigned() ? Signed : Unsigned;
auto TargetType = model::QualifiedType(Model.getPrimitiveType(ICmpKind,
ByteSize),
{});
if (OpType0.isPointer()) {
Op0Token = buildCastExpr(Op0Token, OpType0, TargetType);
} else {
const model::Type *TheType = peelConstAndTypedefs(OpType0)
.UnqualifiedType()
.getConst();
revng_assert(isa<model::PrimitiveType>(TheType)
or isa<model::EnumType>(TheType));
const auto *Primitive = dyn_cast<model::PrimitiveType>(TheType);
if (nullptr == Primitive) {
const auto *Enum = cast<model::EnumType>(TheType);
const auto
*Underlying = Enum->UnderlyingType().UnqualifiedType().getConst();
Primitive = cast<model::PrimitiveType>(Underlying);
}
auto CurrentKind = Primitive->PrimitiveKind();
if (ICmpKind == Signed and CurrentKind != Signed)
Op0Token = buildCastExpr(Op0Token, OpType0, TargetType);
if (ICmpKind == Unsigned and CurrentKind == Signed)
Op0Token = buildCastExpr(Op0Token, OpType0, TargetType);
}
if (OpType1.isPointer()) {
Op1Token = buildCastExpr(Op1Token, OpType1, TargetType);
} else {
const model::Type *TheType = peelConstAndTypedefs(OpType1)
.UnqualifiedType()
.getConst();
const auto *Primitive = cast<model::PrimitiveType>(TheType);
auto CurrentKind = Primitive->PrimitiveKind();
if (ICmpKind == Signed and CurrentKind != Signed)
Op1Token = buildCastExpr(Op1Token, OpType1, TargetType);
if (ICmpKind == Unsigned and CurrentKind == Signed)
Op1Token = buildCastExpr(Op1Token, OpType1, TargetType);
}
}
} else {
const QualifiedType &ResultType = TypeMap.at(I);
Op0Token = buildCastExpr(Op0Token, OpType0, ResultType);
Op1Token = buildCastExpr(Op1Token, OpType1, ResultType);
}
auto *Bin = dyn_cast<llvm::BinaryOperator>(I);
auto *Cmp = dyn_cast<llvm::ICmpInst>(I);
revng_assert(Bin or Cmp);
auto OperatorString = Bin ? getBinOpString(Bin, B) :
getCmpOpString(Cmp->getPredicate(), B);
// TODO: Integer promotion
rc_return addDebugInfo(I,
addParentheses(Op0Token) + OperatorString
+ addParentheses(Op1Token),
B);
}
if (isa<llvm::CastInst>(I) or isa<llvm::FreezeInst>(I)) {
const llvm::Value *Op = I->getOperand(0);
std::string ToCast = rc_recur getToken(Op);
rc_return addDebugInfo(I,
buildCastExpr(ToCast, TypeMap.at(Op), TypeMap.at(I)),
B);
}
switch (I->getOpcode()) {
case llvm::Instruction::Call: {
auto *Call = cast<llvm::CallInst>(I);
revng_assert(isCallToCustomOpcode(Call) or isCallToIsolatedFunction(Call)
or isCallToNonIsolated(Call));
if (isCallToCustomOpcode(Call))
rc_return addDebugInfo(I, rc_recur getCustomOpcodeToken(Call), B);
if (isCallToIsolatedFunction(Call))
rc_return addDebugInfo(I, rc_recur getIsolatedCallToken(Call), B);
if (isCallToNonIsolated(Call))
rc_return addDebugInfo(I, rc_recur getNonIsolatedCallToken(Call), B);
std::string Error = "Cannot get token for CallInst: " + dumpToString(Call);
revng_abort(Error.c_str());
rc_return "";
} break;
case llvm::Instruction::Ret: {
std::string Result = B.getKeyword(ptml::PTMLCBuilder::Keyword::Return)
.serialize();
if (auto *Ret = llvm::cast<llvm::ReturnInst>(I);
llvm::Value *ReturnedVal = Ret->getReturnValue())
Result += " " + rc_recur getToken(ReturnedVal);
rc_return addDebugInfo(I, Result, B);
} break;
case llvm::Instruction::Unreachable:
rc_return addDebugInfo(I, "__builtin_trap()", B);
case llvm::Instruction::Select: {
auto *Select = llvm::cast<llvm::SelectInst>(I);
std::string Condition = rc_recur getToken(Select->getCondition());
const llvm::Value *Op1 = Select->getOperand(1);
const llvm::Value *Op2 = Select->getOperand(2);
std::string Op1String = rc_recur getToken(Op1);
std::string Op1Token = buildCastExpr(Op1String,
TypeMap.at(Op1),
TypeMap.at(Select));
std::string Op2String = rc_recur getToken(Op2);
std::string Op2Token = buildCastExpr(Op2String,
TypeMap.at(Op2),
TypeMap.at(Select));
rc_return addDebugInfo(I,
addParentheses(Condition) + " ? "
+ addParentheses(Op1Token) + " : "
+ addParentheses(Op2Token),
B);
} break;
default: {
std::string Error = "Cannot getToken for llvm::Instruction: "
+ dumpToString(I);
revng_abort(Error.c_str());
}
}
std::string Error = "Cannot getToken for llvm::Instruction: "
+ dumpToString(I);
revng_abort(Error.c_str());
rc_return "";
}
RecursiveCoroutine<std::string>
CCodeGenerator::getToken(const llvm::Value *V) const {
revng_log(Log, "getToken(): " << dumpToString(V));
LoggerIndent Indent{ Log };
// If we already have a variable name for this, return it.
auto It = TokenMap.find(V);
if (It != TokenMap.end()) {
revng_assert(isa<llvm::Argument>(V) or isStackFrameDecl(V)
or isCallStackArgumentDecl(V) or isLocalVarDecl(V)
or isArtificialAggregateLocalVarDecl(V)
or isHelperAggregateLocalVarDecl(V));
revng_log(Log, "Found!");
rc_return It->second;
}
// We should always have names for stuff that is expected to have a name.
revng_assert(not isa<llvm::Argument>(V) and not isStackFrameDecl(V)
and not isCallStackArgumentDecl(V) and not isLocalVarDecl(V)
and not isArtificialAggregateLocalVarDecl(V)
and not isHelperAggregateLocalVarDecl(V));
if (isCConstant(V))
rc_return rc_recur getConstantToken(V);
if (auto *I = dyn_cast<llvm::Instruction>(V))
rc_return rc_recur getInstructionToken(I);
std::string Error = "Cannot get token for llvm::Value: ";
Error += dumpToString(V).c_str();
revng_abort(Error.c_str());
rc_return "";
}
RecursiveCoroutine<std::string>
CCodeGenerator::getCallToken(const llvm::CallInst *Call,
const llvm::StringRef FuncName,
const model::Type *Prototype) const {
std::string Expression = FuncName.str();
if (Call->arg_size() == 0) {
Expression += "()";
} else {
llvm::StringRef Separator = "(";
for (const auto &Arg : Call->args()) {
Expression += Separator.str() + rc_recur getToken(Arg);
Separator = ", ";
}
Expression += ')';
}
rc_return Expression;
}
static bool isStatement(const llvm::Instruction *I) {
// Return are statements
if (isa<llvm::ReturnInst>(I))
return true;
// Instructions that are not calls are never statement.
auto *Call = dyn_cast<llvm::CallInst>(I);
if (not Call)
return false;
// Calls to Assign and LocalVariable are statemements.
// Stack frame declarations and call stack arguments declarations are
// statements.
if (isAssignment(Call))
return true;
// Calls to isolated functions or helpers that return struct types on LLVM IR
// need a statement.
// This is necessary as a result of the fact that there is no direct mapping
// between struct types on LLVM IR and on the model, so whenever a function
// returns a struct in LLVM IR we cannot generally create a call to
// LocalVariable nor to Copy/Assign (because we'd need to tag them with model
// Type and we can't do that.), so we have to deal with it here on the fly.
// We do it by marking these as statements, and emitting an assignment in C
if (isArtificialAggregateLocalVarDecl(Call)
or isHelperAggregateLocalVarDecl(Call))
return true;
// Calls to isolated functions and helpers that return void are statements.
// If they don't return void, they are not statements. They are expressions
// that will be assigned to some local variables in some other assign
// statements.
if (isCallToIsolatedFunction(Call) or isCallToNonIsolated(Call))
return Call->getType()->isVoidTy();
return false;
}
void CCodeGenerator::emitBasicBlock(const llvm::BasicBlock *BB,
bool EmitReturn) {
LoggerIndent Indent{ VisitLog };
revng_log(VisitLog, "|__ Visiting BB " << BB->getName());
LoggerIndent MoreIndent{ VisitLog };
revng_log(Log, "--------- BB " << BB->getName());
for (const Instruction &I : *BB) {
revng_log(Log, "Analyzing: " << dumpToString(I));
auto *Call = dyn_cast<llvm::CallInst>(&I);
if (not isStatement(&I)) {
revng_log(Log, "Ignoring: non-statement instruction");
} else if (I.getType()->isVoidTy()) {
revng_assert(isa<llvm::ReturnInst>(I) or isCallToIsolatedFunction(&I)
or isCallToNonIsolated(&I) or isAssignment(&I));
// Handle the implicit `return` emission. If the correct parameter is set,
// avoid the emission of the `Instruction` token.
if (not(llvm::isa<llvm::ReturnInst>(I) and not EmitReturn)) {
Out << getToken(&I) << ";\n";
}
} else if (isHelperAggregateLocalVarDecl(Call)
or isArtificialAggregateLocalVarDecl(Call)) {
// This is a call but it actually needs an assignment to the associated
// variable. The variable has not been declared in the IR with
// LocalVariable, because LocalVariable needs a model type, and aggregates
// types on the LLVM IR are not on the model.
revng_assert(Call->getType()->isAggregateType());
std::string VarName = getVarName(Call);
revng_assert(not VarName.empty());
// Get the token. If the Call is a call to an isolated function that
// returns an aggregate we want to get the token of the call, not of the
// local variable. For all the other cases we can just get the regular
// token.
std::string RHSExpression = isArtificialAggregateLocalVarDecl(Call) ?
getIsolatedCallToken(Call) :
getToken(Call);
// Assign to the local variable
Out << VarName << " "
<< B.getOperator(ptml::PTMLCBuilder::Operator::Assign) << " "
<< std::move(RHSExpression) << ";\n";
} else {
std::string Error = "Cannot emit statement: ";
Error += dumpToString(Call).c_str();
revng_abort(Error.c_str());
}
if (Call != nullptr and isCallToIsolatedFunction(Call)) {
const auto &[CallEdge, _] = Cache.getCallEdge(Model, Call);
if (CallEdge->hasAttribute(Model, model::FunctionAttribute::NoReturn))
Out << "// The previous function call does not return\n";
}
}
}
RecursiveCoroutine<std::string>
CCodeGenerator::buildGHASTCondition(const ExprNode *E, bool EmitBB) {
LoggerIndent Indent{ VisitLog };
revng_log(VisitLog, "|__ Visiting Condition " << E);
LoggerIndent MoreIndent{ VisitLog };
using NodeKind = ExprNode::NodeKind;
switch (E->getKind()) {
case NodeKind::NK_ValueCompare:
case NodeKind::NK_LoopStateCompare: {
revng_log(VisitLog, "(compare)");
// A compare node, is used to represent a pre-computed condition, which is
// the result of a `switch` promotion to `if`. The compare node can appear
// in multiple variants. Specifically, we may have that the LHS of the
// condition is an actual `llvm::Value` on the IR, or it is a placeholder
// for the loop state variable.
const CompareNode *Compare = cast<CompareNode>(E);
// String that will contain the serialization of the `CompareNode`
std::string CompareNodeString;
// Decide whether to emit the LHS in the form of a pre-existing
// `llvm::Value` or the use of the `LoopStateVar`
switch (E->getKind()) {
case NodeKind::NK_ValueCompare: {
revng_log(VisitLog, "(value compare)");
const ValueCompareNode *ValueCompare = cast<ValueCompareNode>(E);
// We emit the instruction in the basic block before the llvm::Value
llvm::BasicBlock *BB = ValueCompare->getBasicBlock();
revng_assert(BB != nullptr);
// If we are emitting an `IfNode` which derives from the promotion of a
// `DualSwitch`, which in turn was a weaved one, we should not double emit
// the instructions that compute the condition, because they have been
// already emitted by the above switch.
if (EmitBB) {
emitBasicBlock(BB, true);
}
// Retrieve the `llvm::Value` representing the switch condition
llvm::Instruction *Terminator = BB->getTerminator();
llvm::SwitchInst *SwitchInst = llvm::cast<llvm::SwitchInst>(Terminator);
llvm::Value *ConditionValue = SwitchInst->getCondition();
revng_assert(ConditionValue);
// Emit the condition variable
std::string ConditionVarString = getToken(ConditionValue);
CompareNodeString += ConditionVarString;
} break;
case NodeKind::NK_LoopStateCompare: {
revng_log(VisitLog, "(loop state compare)");
// Insert the loop state variable representing string
CompareNodeString += LoopStateVar;
} break;
default: {
revng_abort();
}
}
// If the `ComparisonKind` is of the `NotPresent` kind, we don't need to
// print out the comparison operator nor the RHS
auto Comparison = Compare->getComparison();
if (Comparison != CompareNode::ComparisonKind::Comparison_NotPresent) {
// We either generate the `==` or a `!=`, depending on the operator
// contained in the `CompareNode`
auto Comparison = Compare->getComparison();
using Operator = ptml::PTMLCBuilder::Operator;
switch (Comparison) {
case CompareNode::ComparisonKind::Comparison_Equal: {
auto CmpString = B.getOperator(Operator::CmpEq);
CompareNodeString += " " + CmpString;
} break;
case CompareNode::ComparisonKind::Comparison_NotEqual: {
auto CmpString = B.getOperator(Operator::CmpNeq);
CompareNodeString += " " + CmpString;
} break;
default: {
revng_abort();
}
}
// Build the RHS comparison constant
size_t Constant = Compare->getConstant();
CompareNodeString += " " + B.getNumber(Constant);
}
rc_return CompareNodeString;
} break;
case NodeKind::NK_Atomic: {
revng_log(VisitLog, "(atomic)");
// An atomic node holds a reference to the Basic Block that contains the
// condition used in the conditional expression. In particular, the
// condition is the value used in the last expression of the basic
// block.
// First, emit the BB
const AtomicNode *Atomic = cast<AtomicNode>(E);
llvm::BasicBlock *BB = Atomic->getConditionalBasicBlock();
revng_assert(BB);
// If we are emitting an `IfNode` which derives from the promotion of a
// `DualSwitch`, which in turn was a weaved one, we should not double emit
// the instructions that compute the condition, because they have been
// already emitted by the above switch.
if (EmitBB) {
emitBasicBlock(BB, true);
}
// Then, extract the token of the last instruction (must be a
// conditional branch instruction)
llvm::Instruction *CondTerminator = BB->getTerminator();
llvm::BranchInst *Br = cast<llvm::BranchInst>(CondTerminator);
revng_assert(Br->isConditional());
// Emit code for x != 0 case with cast.
auto *I = dyn_cast<llvm::Instruction>(Br->getCondition());
if (I) {
auto *Cmp = dyn_cast<llvm::CmpInst>(I);
const llvm::Value *Op1 = Cmp != nullptr ? I->getOperand(1) : nullptr;
if (Cmp and Cmp->getPredicate() == llvm::CmpInst::ICMP_NE
and dyn_cast<llvm::Constant>(Op1)
and cast<llvm::Constant>(Op1)->isZeroValue()) {
const llvm::Value *Op0 = I->getOperand(0);
std::string Op0String = rc_recur getToken(Op0);
rc_return addDebugInfo(I, Op0String, B);
}
}
rc_return rc_recur getToken(Br->getCondition());
} break;
case NodeKind::NK_Not: {
revng_log(VisitLog, "(not)");
const NotNode *N = cast<NotNode>(E);
ExprNode *Negated = N->getNegatedNode();
rc_return B.getOperator(ptml::PTMLCBuilder::Operator::BoolNot)
+ addAlwaysParentheses(rc_recur buildGHASTCondition(Negated, EmitBB));
} break;
case NodeKind::NK_And:
case NodeKind::NK_Or: {
revng_log(VisitLog, "(and/or)");
const BinaryNode *Binary = cast<BinaryNode>(E);
const auto &[Child1, Child2] = Binary->getInternalNodes();
std::string Child1Token = rc_recur buildGHASTCondition(Child1, EmitBB);
std::string Child2Token = rc_recur buildGHASTCondition(Child2, EmitBB);
using PTMLOperator = ptml::PTMLCBuilder::Operator;
const Tag &OpToken = E->getKind() == NodeKind::NK_And ?
B.getOperator(PTMLOperator::BoolAnd) :
B.getOperator(PTMLOperator::BoolOr);
rc_return addAlwaysParentheses(Child1Token) + " " + OpToken.serialize()
+ " " + addAlwaysParentheses(Child2Token);
} break;
default:
revng_abort("Unknown ExprNode kind");
}
}
static std::string makeWhile(const ptml::PTMLCBuilder &B,
const std::string &CondExpr) {
revng_assert(not CondExpr.empty());
return B.getKeyword(ptml::PTMLCBuilder::Keyword::While).serialize() + " ("
+ CondExpr + ")";
}
RecursiveCoroutine<void> CCodeGenerator::emitGHASTNode(const ASTNode *N) {
if (N == nullptr)
rc_return;
auto VarToDeclareIt = VariablesToDeclare.find(N);
if (VarToDeclareIt != VariablesToDeclare.end()) {
for (const CallInst *VarDeclCall : VarToDeclareIt->second) {
// Emit missing local variable declarations
if (isLocalVarDecl(VarDeclCall) or isCallStackArgumentDecl(VarDeclCall)) {
std::string VarName = createLocalVarDeclName(VarDeclCall);
revng_assert(not VarName.empty());
Out << getNamedCInstance(TypeMap.at(VarDeclCall), VarName, B) << ";\n";
} else if (isHelperAggregateLocalVarDecl(VarDeclCall)
or isArtificialAggregateLocalVarDecl(VarDeclCall)) {
// Create missing local variable declarations
std::string VarName = createLocalVarDeclName(VarDeclCall);
revng_assert(not VarName.empty());
const auto &Prototype = Cache.getCallSitePrototype(Model, VarDeclCall);
revng_assert(Prototype.isValid() and not Prototype.empty());
const auto *FunctionType = Prototype.getConst();
Out << getNamedInstanceOfReturnType(*FunctionType, VarName, B, false)
<< ";\n";
} else {
revng_assert(not VarDeclCall->getType()->isAggregateType());
}
}
}
revng_log(VisitLog, "|__ GHAST Node " << N->getID());
LoggerIndent Indent{ VisitLog };
auto Kind = N->getKind();
switch (Kind) {
case ASTNode::NodeKind::NK_Break: {
revng_log(VisitLog, "(NK_Break)");
const BreakNode *Break = llvm::cast<BreakNode>(N);
using PTMLOperator = ptml::PTMLCBuilder::Operator;
if (Break->breaksFromWithinSwitch()) {
revng_assert(not SwitchStateVars.empty()
and not SwitchStateVars.back().empty());
Out << SwitchStateVars.back()
<< " " + B.getOperator(PTMLOperator::Assign) + " " + B.getTrueTag()
+ ";\n";
}
};
[[fallthrough]];
case ASTNode::NodeKind::NK_SwitchBreak: {
revng_log(VisitLog, "(NK_SwitchBreak)");
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Break) << ";\n";
} break;
case ASTNode::NodeKind::NK_Continue: {
revng_log(VisitLog, "(NK_Continue)");
const ContinueNode *Continue = cast<ContinueNode>(N);
// Print the condition computation code of the if statement.
if (Continue->hasComputation()) {
IfNode *ComputationIfNode = Continue->getComputationIfNode();
bool EmitBB = not ComputationIfNode->isWeaved();
rc_recur buildGHASTCondition(ComputationIfNode->getCondExpr(), EmitBB);
}
// Actually print the continue statement only if the continue is not
// implicit (i.e. it is not the last statement of the loop).
if (not Continue->isImplicit())
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Continue) << ";\n";
} break;
case ASTNode::NodeKind::NK_Code: {
revng_log(VisitLog, "(NK_Code)");
const CodeNode *Code = cast<CodeNode>(N);
llvm::BasicBlock *BB = Code->getOriginalBB();
revng_assert(BB != nullptr);
emitBasicBlock(BB, not Code->containsImplicitReturn());
} break;
case ASTNode::NodeKind::NK_If: {
revng_log(VisitLog, "(NK_If)");
const IfNode *If = cast<IfNode>(N);
std::string CondExpr;
if (If->getCondExpr()) {
// If we are in presence of a standard `IfNode`, construct the `CondExpr`
bool EmitBB = not If->isWeaved();
CondExpr = rc_recur buildGHASTCondition(If->getCondExpr(), EmitBB);
} else {
// We are emitting a `IfNode` promoted from a dispatcher `SwitchNode` with
// two `case`s
CondExpr = LoopStateVar;
}
// "If" expression
// TODO: possibly cast the CondExpr if it's not convertible to boolean?
revng_assert(not CondExpr.empty());
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::If)
<< " (" + CondExpr + ") ";
{
Scope TheScope(Out);
// "Then" expression (always emitted)
if (nullptr == If->getThen())
Out << B.getLineComment("Empty");
else
rc_recur emitGHASTNode(If->getThen());
}
// "Else" expression (optional)
if (If->hasElse()) {
Out << " " + B.getKeyword(ptml::PTMLCBuilder::Keyword::Else) + " ";
Scope TheScope(Out);
rc_recur emitGHASTNode(If->getElse());
}
Out << "\n";
} break;
case ASTNode::NodeKind::NK_Scs: {
revng_log(VisitLog, "(NK_Scs)");
const ScsNode *Loop = cast<ScsNode>(N);
std::string CondExpr;
// Emit loop entry
if (Loop->isDoWhile()) {
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Do) << " ";
} else {
if (Loop->isWhileTrue()) {
CondExpr = B.getTrueTag().serialize();
} else {
revng_assert(Loop->isWhile());
CondExpr = rc_recur makeLoopCondition(Loop->getRelatedCondition());
}
Out << makeWhile(B, CondExpr) << " ";
}
{
Scope TheScope(Out);
revng_assert(Loop->hasBody() or Loop->isDoWhile());
if (Loop->hasBody())
rc_recur emitGHASTNode(Loop->getBody());
// If the loop is a do while we have to build the condition here, because
// the computation of the condition must be emitted before TheScope is
// closed to stay inside the loop body in C.
if (Loop->isDoWhile()) {
revng_assert(CondExpr.empty());
CondExpr = rc_recur makeLoopCondition(Loop->getRelatedCondition());
}
}
// Emit loop exit
if (Loop->isDoWhile()) {
Out << " " << makeWhile(B, CondExpr) << ";";
}
Out << "\n";
} break;
case ASTNode::NodeKind::NK_List: {
revng_log(VisitLog, "(NK_List)");
const SequenceNode *Seq = cast<SequenceNode>(N);
for (const ASTNode *Child : Seq->nodes())
rc_recur emitGHASTNode(Child);
} break;
case ASTNode::NodeKind::NK_Switch: {
revng_log(VisitLog, "(NK_Switch)");
const SwitchNode *Switch = cast<SwitchNode>(N);
// If needed, print the declaration of the switch state variable, which
// is used by nested switches inside loops to break out of the loop
if (Switch->needsStateVariable()) {
revng_assert(Switch->needsLoopBreakDispatcher());
StringToken NewVarName = NameGenerator.nextSwitchStateVar();
std::string SwitchStateVar = getVariableLocationReference(NewVarName,
ModelFunction,
B);
SwitchStateVars.push_back(std::move(SwitchStateVar));
using PTMLOperator = ptml::PTMLCBuilder::Operator;
Out << B.tokenTag("bool", ptml::c::tokens::Type) << " "
<< getVariableLocationDefinition(NewVarName, ModelFunction, B)
<< " " + B.getOperator(PTMLOperator::Assign) + " " + B.getFalseTag()
+ ";\n";
}
// Generate the condition of the switch
StringToken SwitchVarToken;
model::QualifiedType SwitchVarType;
llvm::Value *SwitchVar = Switch->getCondition();
if (SwitchVar) {
// If the switch is not weaved we need to print the instructions in
// the basic block before it.
if (not Switch->isWeaved()) {
llvm::BasicBlock *BB = Switch->getOriginalBB();
revng_assert(BB != nullptr); // This is not a switch dispatcher.
emitBasicBlock(BB, true);
}
std::string SwitchVarString = getToken(SwitchVar);
SwitchVarToken = SwitchVarString;
SwitchVarType = TypeMap.at(SwitchVar);
} else {
revng_assert(Switch->getOriginalBB() == nullptr);
revng_assert(!LoopStateVar.empty());
// This switch does not come from an instruction: it's a dispatcher
// for the loop state variable
SwitchVarToken = LoopStateVar;
// TODO: finer decision on the type of the loop state variable
using model::PrimitiveTypeKind::Unsigned;
SwitchVarType.UnqualifiedType() = Model.getPrimitiveType(Unsigned, 8);
}
revng_assert(not SwitchVarToken.empty());
if (not SwitchVarType.is(model::TypeKind::PrimitiveType)) {
model::QualifiedType BoolTy;
// TODO: finer decision on how to cast structs used in a switch
using model::PrimitiveTypeKind::Unsigned;
BoolTy.UnqualifiedType() = Model.getPrimitiveType(Unsigned, 8);
SwitchVarToken = buildCastExpr(SwitchVarToken, SwitchVarType, BoolTy);
}
// Generate the switch statement
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Switch) + " ("
<< SwitchVarToken << ") ";
{
Scope TheScope(Out);
using PTMLKeyword = ptml::PTMLCBuilder::Keyword;
// Generate the body of the switch (except for the default)
for (const auto &[Labels, CaseNode] : Switch->cases_const_range()) {
// If we encounter the `default` case, skip it, as it is emitted later
if (Labels.empty() == true) {
continue;
}
// Generate the case label(s) (multiple case labels might share the
// same body)
for (uint64_t CaseVal : Labels) {
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Case) + " ";
if (SwitchVar) {
llvm::Type *SwitchVarT = SwitchVar->getType();
auto *IntType = cast<llvm::IntegerType>(SwitchVarT);
auto *CaseConst = llvm::ConstantInt::get(IntType, CaseVal);
// TODO: assigned the signedness based on the signedness of the
// condition
Out << B.getNumber(CaseConst->getValue());
} else {
Out << B.getNumber(CaseVal);
}
Out << ":\n";
}
{
Scope InnerScope(Out);
// Generate the case body
rc_recur emitGHASTNode(CaseNode);
}
Out << " " + B.getKeyword(PTMLKeyword::Break) + ";\n";
}
// Generate the default case if it exists
if (auto *Default = Switch->getDefault()) {
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Default) << ":\n";
{
Scope TheScope(Out);
rc_recur emitGHASTNode(Default);
}
Out << " " + B.getKeyword(PTMLKeyword::Break) + ";\n";
}
}
Out << "\n";
// If the switch needs a loop break dispatcher, reset the associated
// state variable before emitting the switch statement.
if (Switch->needsLoopBreakDispatcher()) {
revng_assert(not SwitchStateVars.empty()
and not SwitchStateVars.back().empty());
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::If) + " ("
+ SwitchStateVars.back() + ")";
{
auto Scope = B.getScope(ptml::PTMLCBuilder::Scopes::Scope)
.scope(Out, true);
auto IndentScope = Out.scope();
Out << B.getKeyword(ptml::PTMLCBuilder::Keyword::Break) + ";";
}
Out << "\n";
}
// If we're done with a switch that generates a state variable to break
// out of loops, pop it from the stack.
if (Switch->needsStateVariable()) {
revng_assert(Switch->needsLoopBreakDispatcher());
SwitchStateVars.pop_back();
}
} break;
case ASTNode::NodeKind::NK_Set: {
revng_log(VisitLog, "(NK_Set)");
const SetNode *Set = cast<SetNode>(N);
unsigned StateValue = Set->getStateVariableValue();
revng_assert(!LoopStateVar.empty());
// Print an assignment to the loop state variable. This is an artificial
// variable introduced by the GHAST to enable executing certain pieces
// of code based on which control-flow branch was taken. This, for
// example, can be used to jump to the middle of a loop
// instead of at the start, without emitting gotos.
Out << LoopStateVar << " "
<< B.getOperator(ptml::PTMLCBuilder::Operator::Assign) << " "
<< StateValue << ";\n";
} break;
}
rc_return;
}
static std::string getModelArgIdentifier(const model::Type *ModelFunctionType,
const llvm::Argument &Argument) {
const llvm::Function *LLVMFunction = Argument.getParent();
unsigned ArgNo = Argument.getArgNo();
if (auto *RFT = dyn_cast<model::RawFunctionType>(ModelFunctionType)) {
auto NumModelArguments = RFT->Arguments().size();
revng_assert(ArgNo <= NumModelArguments + 1);
revng_assert(LLVMFunction->arg_size() == NumModelArguments
or (not RFT->StackArgumentsType().empty()
and (LLVMFunction->arg_size() == NumModelArguments + 1)));
if (ArgNo < NumModelArguments) {
return std::next(RFT->Arguments().begin(), ArgNo)->name().str().str();
} else {
return "_stack_arguments";
}
} else if (auto *CFT = dyn_cast<model::CABIFunctionType>(ModelFunctionType)) {
revng_assert(LLVMFunction->arg_size() == CFT->Arguments().size());
revng_assert(ArgNo < CFT->Arguments().size());
return CFT->Arguments().at(ArgNo).name().str().str();
}
revng_abort("Unexpected function type");
return "";
}
void CCodeGenerator::emitFunction(bool NeedsLocalStateVar,
InlineableTypesMap &StackTypes) {
revng_log(Log, "========= Emitting Function " << LLVMFunction.getName());
revng_log(VisitLog, "========= Function " << LLVMFunction.getName());
LoggerIndent Indent{ VisitLog };
auto FunctionTagScope = B.getScope(ptml::PTMLCBuilder::Scopes::FunctionBody)
.scope(Out);
// Extract user comments from the model and emit them as PTML just before
// the prototype.
Out << B.getFunctionComment(ModelFunction, Model);
// Print function's prototype
printFunctionPrototype(Prototype, ModelFunction, Out, B, Model, false);
// Set up the argument identifiers to be used in the function's body.
for (const auto &Arg : LLVMFunction.args()) {
std::string ArgString = getModelArgIdentifier(&Prototype, Arg);
TokenMap[&Arg] = getArgumentLocationReference(ArgString, ModelFunction, B);
}
// Print the function body
Out << " ";
{
Scope BraceScope(Out, ptml::c::scopes::FunctionBody);
// We expect just one stack type definition.
bool IsStackDefined = false;
// Declare the local variable representing the stack frame
if (not ModelFunction.StackFrameType().empty()) {
revng_log(Log, "Stack Frame Declaration");
const auto &IsStackFrameDecl = [](const llvm::Instruction &I) {
return isStackFrameDecl(&I);
};
auto It = llvm::find_if(llvm::instructions(LLVMFunction),
IsStackFrameDecl);
if (It != llvm::instructions(LLVMFunction).end()) {
const auto *Call = &cast<llvm::CallInst>(*It);
std::string VarName = createStackFrameVarDeclName(Call);
revng_assert(not VarName.empty());
auto *TheType = ModelFunction.StackFrameType().getConst();
// This will contain the stack types that we can inline, since
// there could be a stack type that is being used somewhere else,
// so we do not want to inline it.
auto TheStackTypes = StackTypes.at(&ModelFunction);
if (TheStackTypes.contains(TheType) and !IsStackDefined) {
IsStackDefined = true;
std::map<model::QualifiedType, std::string> AdditionalTypeNames;
// For all nested types within stack definition we print forward
// declarations.
for (auto *Type : TheStackTypes) {
revng_assert(isCandidateForInline(Type));
printForwardDeclaration(*Type, Out, B);
}
printDefinition(Log,
*cast<model::StructType>(TheType),
Out,
B,
Model,
AdditionalTypeNames,
TheStackTypes,
VarName);
} else {
Out << getNamedCInstance(TypeMap.at(Call), VarName, B) << ";\n";
}
} else {
revng_log(Log,
"WARNING: function with valid stack type has no stack "
"declaration: "
<< LLVMFunction.getName());
}
}
// Emit a declaration for the loop state variable, which is used to
// redirect control flow inside loops (e.g. if we want to jump in the
// middle of a loop during a certain iteration)
if (NeedsLocalStateVar)
Out << B.tokenTag("uint64_t", ptml::c::tokens::Type) << " "
<< LoopStateVarDeclaration << ";\n";
// Recursively print the body of this function
emitGHASTNode(GHAST.getRoot());
}
Out << "\n";
}
static std::string decompileFunction(FunctionMetadataCache &Cache,
const llvm::Function &LLVMFunc,
const ASTTree &CombedAST,
const Binary &Model,
const ASTVarDeclMap &VarToDeclare,
bool NeedsLocalStateVar,
InlineableTypesMap &StackTypes) {
std::string Result;
llvm::raw_string_ostream Out(Result);
ptml::PTMLCBuilder B;
CCodeGenerator
Backend(Cache, Model, LLVMFunc, CombedAST, VarToDeclare, Out, B);
Backend.emitFunction(NeedsLocalStateVar, StackTypes);
Out.flush();
return Result;
}
static bool hasLoopDispatchers(const ASTTree &GHAST) {
return needsLoopVar(GHAST.getRoot());
}
static ASTVarDeclMap computeVariableDeclarationScope(const llvm::Function &F,
const ASTTree &GHAST) {
const ASTNode *Entry = GHAST.getRoot();
ASTVarDeclMap Result;
for (const BasicBlock &BB : F) {
for (const Instruction &I : BB) {
auto *Call = dyn_cast<llvm::CallInst>(&I);
if (not Call)
continue;
// Ignore the stack frame, which is handled separately.
if (isStackFrameDecl(Call))
continue;
// All local variable declarations should go in the entry scope for now
if (isLocalVarDecl(Call) or isCallStackArgumentDecl(Call)
or isArtificialAggregateLocalVarDecl(Call)
or isHelperAggregateLocalVarDecl(Call))
Result[Entry].insert(Call);
revng_assert(not isCallToNonIsolated(Call)
or not Call->getCalledFunction()->isTargetIntrinsic());
}
}
return Result;
}
using Container = revng::pipes::DecompileStringMap;
void decompile(FunctionMetadataCache &Cache,
llvm::Module &Module,
const model::Binary &Model,
Container &DecompiledFunctions) {
TypeInlineHelper TheTypeInlineHelper(Model);
// Get all Stack types and all the inlinable types reachable from it,
// since we want to emit forward declarations for all of them.
auto StackTypes = TheTypeInlineHelper.findStackTypesPerFunction(Model);
auto
T = llvm::make_task_on_set(llvm::make_address_range(FunctionTags::Isolated
.functions(&Module)),
"decompile");
for (llvm::Function &F : FunctionTags::Isolated.functions(&Module)) {
T.advance(&F,
llvm::Twine("decompile Function: ") + llvm::Twine(F.getName()));
if (F.empty())
continue;
llvm::Task T2(3,
llvm::Twine("decompile Function: ")
+ llvm::Twine(F.getName()));
// TODO: this will eventually become a GHASTContainer for revng pipeline
ASTTree GHAST;
// Generate the GHAST and beautify it.
{
T2.advance("restructureCFG");
restructureCFG(F, GHAST);
// TODO: beautification should be optional, but at the moment it's not
// truly so (if disabled, things crash). We should strive to make it
// optional for real.
T2.advance("beautifyAST");
beautifyAST(Model, F, GHAST);
}
T2.advance("decompileFunction");
if (Log.isEnabled()) {
std::string ASTFileName = F.getName().str()
+ "GHAST-during-c-codegen.dot";
GHAST.dumpASTOnFile(ASTFileName.c_str());
}
// Generated C code for F
auto VariablesToDeclare = computeVariableDeclarationScope(F, GHAST);
auto NeedsLoopStateVar = hasLoopDispatchers(GHAST);
std::string CCode = decompileFunction(Cache,
F,
GHAST,
Model,
VariablesToDeclare,
NeedsLoopStateVar,
StackTypes);
// Push the C code into
MetaAddress Key = getMetaAddressMetadata(&F, "revng.function.entry");
DecompiledFunctions.insert_or_assign(Key, std::move(CCode));
}
}