mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
ea3cbe4c1b
Type inlining was a feature that allowed type definitions of structs/unions/enums to be printed in C directly inside the definition of another parent struct/union, if the inner type was only used once in the parent type. This kind of reasoning is inherently global: a type definition of the subtype can be inlined in the parent type one only if *globally* the subtype it isn't referred anywhere else. This caused issues with type inlining inside definitions of stack types in the body of functions. Indeed, for a given function, due to type inlining, it was necessary to do global reasoning about what other types could be inlined in the definition of the function's stack frame type. This, in turn, had heavy consequences on invalidation, because any change to any type (even if it wasn't referred in a given function's body) was causing invalidation of all functions' bodies. For this reason it was decided to drop the type inlining feature.
2059 lines
75 KiB
C++
2059 lines
75 KiB
C++
//
|
|
// This file is distributed under the MIT License. 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/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/ABI/ModelHelpers.h"
|
|
#include "revng/Backend/DecompileFunction.h"
|
|
#include "revng/EarlyFunctionAnalysis/ControlFlowGraphCache.h"
|
|
#include "revng/HeadersGeneration/Options.h"
|
|
#include "revng/InitModelTypes/InitModelTypes.h"
|
|
#include "revng/Model/Binary.h"
|
|
#include "revng/Model/Helpers.h"
|
|
#include "revng/Model/IRHelpers.h"
|
|
#include "revng/Model/NameBuilder.h"
|
|
#include "revng/Model/PrimitiveKind.h"
|
|
#include "revng/Model/RawFunctionDefinition.h"
|
|
#include "revng/Model/Segment.h"
|
|
#include "revng/Model/StructDefinition.h"
|
|
#include "revng/Model/TypeDefinition.h"
|
|
#include "revng/PTML/CBuilder.h"
|
|
#include "revng/PTML/Constants.h"
|
|
#include "revng/PTML/IndentedOstream.h"
|
|
#include "revng/Pipeline/Location.h"
|
|
#include "revng/Pipes/Ranks.h"
|
|
#include "revng/RestructureCFG/ASTNode.h"
|
|
#include "revng/RestructureCFG/ASTNodeUtils.h"
|
|
#include "revng/RestructureCFG/ASTTree.h"
|
|
#include "revng/RestructureCFG/BeautifyGHAST.h"
|
|
#include "revng/RestructureCFG/RestructureCFG.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/DecompilationHelpers.h"
|
|
#include "revng/Support/FunctionTags.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
#include "revng/Support/YAMLTraits.h"
|
|
#include "revng/TypeNames/LLVMTypeNames.h"
|
|
#include "revng/TypeNames/PTMLCTypeBuilder.h"
|
|
#include "revng/Yield/PTML.h"
|
|
|
|
#include "ALAPVariableDeclaration.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::CABIFunctionDefinition;
|
|
using model::RawFunctionDefinition;
|
|
|
|
using pipeline::locationString;
|
|
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::UpcastableType>;
|
|
|
|
static Logger<> Log{ "c-backend" };
|
|
static Logger<> VisitLog{ "c-backend-visit-order" };
|
|
|
|
static bool isStackFrameDecl(const llvm::Value *I) {
|
|
auto *Call = dyn_cast_or_null<llvm::CallInst>(I);
|
|
if (not Call)
|
|
return false;
|
|
|
|
auto *Callee = getCalledFunction(Call);
|
|
if (not Callee)
|
|
return false;
|
|
|
|
return Callee->getName().startswith("revng_stack_frame");
|
|
}
|
|
|
|
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)
|
|
or isCallToTagged(I, FunctionTags::Comment);
|
|
}
|
|
|
|
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::CTypeBuilder &B) {
|
|
revng_assert(Value.getBitWidth() > 64);
|
|
revng_assert(Value.getBitWidth() <= 128);
|
|
using PTMLOperator = ptml::CBuilder::Operator;
|
|
|
|
auto U128 = model::PrimitiveType::makeUnsigned(16);
|
|
std::string Cast = addAlwaysParentheses(B.getTypeName(*U128));
|
|
|
|
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).toString();
|
|
}
|
|
return addAlwaysParentheses(CompositeConstant);
|
|
}
|
|
|
|
static std::string hexLiteral(const llvm::ConstantInt *Int,
|
|
const ptml::CTypeBuilder &B) {
|
|
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);
|
|
}
|
|
|
|
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 Escaped;
|
|
llvm::raw_string_ostream EscapeCStream(Escaped);
|
|
EscapeCStream.write_escaped(std::string(&CharValue, 1));
|
|
|
|
return "'" + std::move(Escaped) + "'";
|
|
}
|
|
|
|
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::TypeDefinition &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;
|
|
|
|
/// Helper for outputting the decompiled C code
|
|
ptml::CTypeBuilder &B;
|
|
|
|
/// Name of the local variable used to break out of loops from within nested
|
|
/// switches
|
|
std::vector<std::string> SwitchStateVars;
|
|
|
|
/// \note This class handles an individual function, but still needs a
|
|
/// reference to all of ControlFlowGraphCache, so we can call
|
|
/// getCallEdge. This is not nice, since, if we look into stuff
|
|
/// concerning other functions, we can create serious invalidation
|
|
/// issues.
|
|
/// However, right now we just call getCallEdge which is safe.
|
|
/// A better approach would be to have a ControlFlowGraphCache provide
|
|
/// us with a function-specific subject that enables us to use
|
|
/// getCallEdge.
|
|
ControlFlowGraphCache &Cache;
|
|
|
|
private:
|
|
class VarNameGenerator {
|
|
private:
|
|
uint64_t CurrentVariableID = 0;
|
|
const model::NamingConfiguration &Configuration;
|
|
|
|
public:
|
|
VarNameGenerator(const model::Binary &Binary) :
|
|
Configuration(Binary.Configuration().Naming()) {}
|
|
|
|
std::string nextVarName() {
|
|
return Configuration.unnamedLocalVariablePrefix().str()
|
|
+ std::to_string(CurrentVariableID++);
|
|
}
|
|
|
|
std::string nextSwitchStateVar() {
|
|
return Configuration.unnamedBreakFromLoopVariablePrefix().str()
|
|
+ std::to_string(CurrentVariableID++);
|
|
}
|
|
};
|
|
|
|
/// 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(ControlFlowGraphCache &Cache,
|
|
const Binary &Model,
|
|
const llvm::Function &LLVMFunction,
|
|
const ASTTree &GHAST,
|
|
const ASTVarDeclMap &VarToDeclare,
|
|
ptml::CTypeBuilder &B) :
|
|
Model(Model),
|
|
LLVMFunction(LLVMFunction),
|
|
ModelFunction(*llvmToModelFunction(Model, LLVMFunction)),
|
|
Prototype(*Model.prototypeOrDefault(ModelFunction.prototype())),
|
|
GHAST(GHAST),
|
|
VariablesToDeclare(VarToDeclare),
|
|
TypeMap(initModelTypes(LLVMFunction,
|
|
&ModelFunction,
|
|
Model,
|
|
/* PointersOnly = */ false)),
|
|
B(B),
|
|
SwitchStateVars(),
|
|
Cache(Cache),
|
|
NameGenerator(Model) {
|
|
// TODO: don't use a global loop state variable
|
|
const auto &Configuration = B.NameBuilder.Configuration;
|
|
llvm::StringRef LoopStateVariable = Configuration.loopStateVariableName();
|
|
LoopStateVar = B.getVariableReferenceTag(ModelFunction, LoopStateVariable);
|
|
LoopStateVarDeclaration = B.getVariableDefinitionTag(ModelFunction,
|
|
LoopStateVariable);
|
|
|
|
if (LLVMFunction.getMetadata(ExplicitParenthesesMDName))
|
|
IsOperatorPrecedenceResolutionPassEnabled = true;
|
|
}
|
|
|
|
void emitFunction(bool NeedsLocalStateVar);
|
|
|
|
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::TypeDefinition *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;
|
|
|
|
std::string getIsolatedFunctionToken(const llvm::Function *F) 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::Type &SrcType,
|
|
const model::Type &DestType) const;
|
|
|
|
private:
|
|
std::string createStackFrameVarDeclName(const llvm::Instruction *I) {
|
|
revng_assert(isStackFrameDecl(I));
|
|
revng_assert(not TokenMap.contains(I));
|
|
|
|
const auto &Configuration = Model.Configuration().Naming();
|
|
std::string VarName = Configuration.stackFrameVariableName().str();
|
|
TokenMap[I] = B.getVariableReferenceTag(ModelFunction, VarName);
|
|
return B.getVariableDefinitionTag(ModelFunction, VarName);
|
|
}
|
|
|
|
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] = B.getVariableReferenceTag(ModelFunction, VarName);
|
|
return B.getVariableDefinitionTag(ModelFunction, VarName);
|
|
}
|
|
|
|
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::CBuilder::Operator;
|
|
return B.getOperator(PTMLOperator::PointerDereference) + addParentheses(Expr);
|
|
}
|
|
|
|
std::string CCodeGenerator::buildAddressExpr(llvm::StringRef Expr) const {
|
|
return B.getOperator(ptml::CBuilder::Operator::AddressOf)
|
|
+ addParentheses(Expr);
|
|
}
|
|
|
|
std::string CCodeGenerator::buildCastExpr(StringRef ExprToCast,
|
|
const model::Type &SrcType,
|
|
const model::Type &DestType) const {
|
|
if (SrcType == DestType)
|
|
return ExprToCast.str();
|
|
|
|
if (*SrcType.skipTypedefs() != *DestType.skipTypedefs()
|
|
and (not SrcType.isScalar() or not DestType.isScalar())) {
|
|
revng_log(Log,
|
|
"WARNING: emitting a invalid bitcast in C, using "
|
|
"__builtin_bit_cast");
|
|
return (llvm::Twine("__builtin_bit_cast(") + B.getTypeName(DestType) + ", "
|
|
+ ExprToCast + ")")
|
|
.str();
|
|
}
|
|
|
|
return addAlwaysParentheses(B.getTypeName(DestType)) + " "
|
|
+ addParentheses(ExprToCast);
|
|
}
|
|
|
|
static std::string getUndefToken(const model::Type &UndefType,
|
|
const ptml::CTypeBuilder &B) {
|
|
return B.Binary.Configuration().Naming().undefinedValuePrefix().str()
|
|
+ UndefType.toPrimitive().getCName() + "()";
|
|
}
|
|
|
|
static std::string getFormattedIntegerToken(const llvm::CallInst *Call,
|
|
const ptml::CTypeBuilder &B) {
|
|
|
|
if (isCallToTagged(Call, FunctionTags::HexInteger)) {
|
|
const auto Operand = Call->getArgOperand(0);
|
|
const auto *Value = cast<llvm::ConstantInt>(Operand);
|
|
return B.getConstantTag(hexLiteral(Value, B)).toString();
|
|
}
|
|
|
|
if (isCallToTagged(Call, FunctionTags::CharInteger)) {
|
|
const auto Operand = Call->getArgOperand(0);
|
|
const auto *Value = cast<llvm::ConstantInt>(Operand);
|
|
return B.getConstantTag(charLiteral(Value)).toString();
|
|
}
|
|
|
|
if (isCallToTagged(Call, FunctionTags::BoolInteger)) {
|
|
const auto Operand = Call->getArgOperand(0);
|
|
const auto *Value = cast<llvm::ConstantInt>(Operand);
|
|
return B.getConstantTag(boolLiteral(Value)).toString();
|
|
}
|
|
|
|
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().toString();
|
|
}
|
|
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().toString();
|
|
|
|
if (auto *Const = dyn_cast<llvm::ConstantInt>(C)) {
|
|
llvm::APInt Value = Const->getValue();
|
|
if (Value.isIntN(64))
|
|
rc_return B.getNumber(Value).toString();
|
|
else
|
|
rc_return get128BitIntegerHexConstant(Value, B);
|
|
}
|
|
|
|
if (auto *Function = dyn_cast<llvm::Function>(C)) {
|
|
const model::Function *ModelFunc = llvmToModelFunction(Model, *Function);
|
|
revng_assert(ModelFunc);
|
|
rc_return B.getReferenceTag(*ModelFunc);
|
|
}
|
|
|
|
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 model::Type &SrcType = *TypeMap.at(Operand);
|
|
const model::Type &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);
|
|
}
|
|
|
|
case Instruction::PtrToInt: {
|
|
const auto *Operand = cast<llvm::Constant>(ConstExpr->getOperand(0));
|
|
const model::Type &SrcType = *TypeMap.at(Operand);
|
|
const model::Type &DstType = *TypeMap.at(ConstExpr);
|
|
rc_return buildCastExpr(rc_recur getConstantToken(Operand),
|
|
SrcType,
|
|
DstType);
|
|
}
|
|
|
|
default:
|
|
revng_abort(dumpToString(ConstExpr).c_str());
|
|
}
|
|
}
|
|
|
|
if (isCallToTagged(C, FunctionTags::LiteralPrintDecorator))
|
|
rc_return getFormattedIntegerToken(cast<llvm::CallInst>(C), B);
|
|
|
|
std::string Error = "Cannot get token for llvm::Constant: ";
|
|
Error += dumpToString(C).c_str();
|
|
revng_abort(Error.c_str());
|
|
|
|
rc_return "";
|
|
}
|
|
|
|
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();
|
|
model::UpcastableType CurType = fromLLVMString(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
|
|
const model::Type &Base = *TypeMap.at(BaseValue)->skipTypedefs();
|
|
const model::Type &Cur = *std::as_const(CurType)->skipTypedefs();
|
|
if (Base != Cur) {
|
|
BaseValue->dump();
|
|
TypeMap.at(BaseValue)->dump();
|
|
CurType->dump();
|
|
revng_abort("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
|
|
const model::Type &Pointee = TypeMap.at(BaseValue)->getPointee();
|
|
revng_assert(Pointee == *CurType,
|
|
"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()).toString();
|
|
} 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.
|
|
CurType = model::ArrayType::make(std::move(CurType), 1);
|
|
} 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::CBuilder::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) {
|
|
const auto &Unwrapped = *std::as_const(CurType)->skipConstAndTypedefs();
|
|
if (const auto *D = llvm::dyn_cast<model::DefinedType>(&Unwrapped)) {
|
|
// 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.toString();
|
|
|
|
// Find the field name
|
|
const model::TypeDefinition &Definition = D->unwrap();
|
|
if (auto *S = dyn_cast<model::StructDefinition>(&Definition)) {
|
|
const model::StructField &Field = S->Fields().at(FieldIdx);
|
|
CurExpr += B.getReferenceTag(*S, Field);
|
|
CurType = std::move(S->Fields().at(FieldIdx).Type());
|
|
|
|
} else if (auto *U = dyn_cast<model::UnionDefinition>(&Definition)) {
|
|
const model::UnionField &Field = U->Fields().at(FieldIdx);
|
|
CurExpr += B.getReferenceTag(*U, Field);
|
|
CurType = std::move(U->Fields().at(FieldIdx).Type());
|
|
|
|
} else {
|
|
CurType->dump();
|
|
revng_abort("Unexpected ModelGEP type found: ");
|
|
}
|
|
|
|
} else if (auto *Array = llvm::dyn_cast<model::ArrayType>(&Unwrapped)) {
|
|
std::string IndexExpr;
|
|
if (auto *Const = dyn_cast<llvm::ConstantInt>(CurArg->get())) {
|
|
IndexExpr = B.getNumber(Const->getValue()).toString();
|
|
} else {
|
|
IndexExpr = rc_recur getToken(CurArg->get());
|
|
}
|
|
|
|
CurExpr += "[" + IndexExpr + "]";
|
|
CurType = std::move(Array->ElementType());
|
|
|
|
} else {
|
|
revng_abort("Integers/pointers do not have fields.");
|
|
}
|
|
|
|
// 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::CBuilder::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();
|
|
model::UpcastableType CurType = fromLLVMString(CurArg->get(), Model);
|
|
|
|
// Second argument is the base llvm::Value
|
|
++CurArg;
|
|
llvm::Value *BaseValue = CurArg->get();
|
|
std::string Token = rc_recur getToken(BaseValue);
|
|
|
|
// If it is an implicit cast, omit it.
|
|
bool IsImplicit = cast<llvm::ConstantInt>(Call->getArgOperand(2))->isOne();
|
|
if (IsImplicit)
|
|
rc_return Token;
|
|
|
|
// Emit the parenthesized cast expr, and we are done.
|
|
rc_return buildCastExpr(Token, *TypeMap.at(BaseValue), *CurType);
|
|
}
|
|
|
|
if (isCallToTagged(Call, FunctionTags::AddressOf)) {
|
|
// First operand is the type of the value being addressed (should not
|
|
// introduce casts)
|
|
const model::UpcastableType ArgType = fromLLVMString(Call->getArgOperand(0),
|
|
Model);
|
|
|
|
// Second argument is the value being addressed
|
|
llvm::Value *Arg = Call->getArgOperand(1);
|
|
revng_assert(*ArgType->skipTypedefs() == *TypeMap.at(Arg)->skipTypedefs());
|
|
|
|
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 = B.getNamedInstanceOfReturnType(Prototype, "");
|
|
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 *I = llvm::cast<llvm::ConstantInt>(Call->getArgOperand(1));
|
|
|
|
const auto *CallReturnsStruct = llvm::cast<llvm::CallInst>(AggregateOp);
|
|
const llvm::Function *Callee = getCalledFunction(CallReturnsStruct);
|
|
const auto &CalleePrototype = getCallSitePrototype(Model,
|
|
CallReturnsStruct);
|
|
|
|
std::string StructFieldRef;
|
|
if (not CalleePrototype) {
|
|
// The call returning a struct is a call to a helper function.
|
|
// It must be a direct call.
|
|
revng_assert(Callee);
|
|
StructFieldRef = getReturnStructFieldReferenceTag(Callee,
|
|
I->getZExtValue(),
|
|
B);
|
|
} else {
|
|
auto RF = llvm::cast<const model::RawFunctionDefinition>(CalleePrototype);
|
|
uint64_t Idx = I->getZExtValue();
|
|
const auto &ReturnValue = std::next(RF->ReturnValues().begin(), Idx);
|
|
StructFieldRef = B.NameBuilder.name(*RF, *ReturnValue);
|
|
}
|
|
|
|
rc_return rc_recur getToken(AggregateOp) + "." + StructFieldRef;
|
|
}
|
|
|
|
if (isCallToTagged(Call, FunctionTags::SegmentRef)) {
|
|
auto *Callee = getCalledFunction(Call);
|
|
const auto &[StartAddress,
|
|
VirtualSize] = extractSegmentKeyFromMetadata(*Callee);
|
|
model::Segment Segment = Model.Segments().at({ StartAddress, VirtualSize });
|
|
rc_return B.getReferenceTag(Segment);
|
|
}
|
|
|
|
if (isCallToTagged(Call, FunctionTags::Copy))
|
|
rc_return rc_recur getToken(Call->getArgOperand(0));
|
|
|
|
if (isCallToTagged(Call, FunctionTags::OpaqueCSVValue)) {
|
|
auto *Callee = getCalledFunction(Call);
|
|
std::string HelperRef = getHelperFunctionReferenceTag(Callee, B);
|
|
rc_return rc_recur getCallToken(Call, HelperRef, /*prototype=*/nullptr);
|
|
}
|
|
|
|
using PTMLOperator = ptml::CBuilder::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);
|
|
rc_return B.getStringLiteral(StringLiteral).toString();
|
|
}
|
|
|
|
if (isCallToTagged(Call, FunctionTags::Comment)) {
|
|
const auto *Index = llvm::cast<llvm::ConstantInt>(Call->getArgOperand(0));
|
|
const auto &Comment = ModelFunction.Comments().at(Index->getZExtValue());
|
|
|
|
std::string ShouldBeEmittedAtOp = rc_recur getToken(Call->getArgOperand(2));
|
|
llvm::StringRef ShouldBeEmittedAt = llvm::StringRef(ShouldBeEmittedAtOp);
|
|
ShouldBeEmittedAt.consume_front("\"");
|
|
ShouldBeEmittedAt.consume_back("\"");
|
|
|
|
std::string IsBeingEmittedAtOp = rc_recur getToken(Call->getArgOperand(3));
|
|
llvm::StringRef IsBeingEmittedAt = llvm::StringRef(IsBeingEmittedAtOp);
|
|
IsBeingEmittedAt.consume_front("\"");
|
|
IsBeingEmittedAt.consume_back("\"");
|
|
|
|
const auto *IsExact = llvm::cast<llvm::ConstantInt>(Call->getArgOperand(1));
|
|
if (IsExact->getZExtValue())
|
|
revng_assert(ShouldBeEmittedAt == IsBeingEmittedAt);
|
|
|
|
rc_return "\n"
|
|
+ B.getStatementComment(Comment,
|
|
pipeline::locationString(ranks::StatementComment,
|
|
ModelFunction.key(),
|
|
Index->getZExtValue()),
|
|
IsBeingEmittedAt);
|
|
}
|
|
|
|
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);
|
|
|
|
// 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);
|
|
CalleeToken = B.getReferenceTag(DynamicFunc);
|
|
} else {
|
|
// Isolated function
|
|
llvm::Function *CalledFunc = getCalledFunction(Call);
|
|
revng_assert(CalledFunc);
|
|
const model::Function *ModelFunc = llvmToModelFunction(Model,
|
|
*CalledFunc);
|
|
revng_assert(ModelFunc);
|
|
CalleeToken = B.getReferenceTag(*ModelFunc);
|
|
}
|
|
}
|
|
|
|
// Build the call expression
|
|
revng_assert(not CalleeToken.empty());
|
|
const auto *Prototype = getCallSitePrototype(Model, Call);
|
|
rc_return rc_recur getCallToken(Call, CalleeToken, Prototype);
|
|
}
|
|
|
|
RecursiveCoroutine<std::string>
|
|
CCodeGenerator::getNonIsolatedCallToken(const llvm::CallInst *Call) const {
|
|
auto *CalledFunc = getCalledFunction(Call);
|
|
revng_assert(CalledFunc and CalledFunc->hasName(),
|
|
"Special functions should all have a name");
|
|
|
|
std::string HelperRef = getHelperFunctionReferenceTag(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,
|
|
std::string &&Str,
|
|
const ptml::CTypeBuilder &B) {
|
|
if (shouldGenerateDebugInfoAsPTML(*I)) {
|
|
std::string Location = I->getDebugLoc()->getScope()->getName().str();
|
|
return B.getDebugInfoTag(std::move(Str), std::move(Location));
|
|
|
|
} else {
|
|
return std::move(Str);
|
|
}
|
|
}
|
|
|
|
/// Return the string that represents the given binary operator in C
|
|
static const std::string getBinOpString(const llvm::BinaryOperator *BinOp,
|
|
const ptml::CBuilder &B) {
|
|
const Tag Op = [&BinOp, &B]() {
|
|
bool IsBool = BinOp->getType()->isIntegerTy(1);
|
|
|
|
using PTMLOperator = ptml::CBuilder::Operator;
|
|
|
|
switch (BinOp->getOpcode()) {
|
|
case Instruction::Add:
|
|
return B.getOperator(ptml::CBuilder::Operator::Add);
|
|
case Instruction::Sub:
|
|
return B.getOperator(ptml::CBuilder::Operator::Sub);
|
|
case Instruction::Mul:
|
|
return B.getOperator(ptml::CBuilder::Operator::Mul);
|
|
case Instruction::SDiv:
|
|
case Instruction::UDiv:
|
|
return B.getOperator(ptml::CBuilder::Operator::Div);
|
|
case Instruction::SRem:
|
|
case Instruction::URem:
|
|
return B.getOperator(ptml::CBuilder::Operator::Modulo);
|
|
case Instruction::LShr:
|
|
case Instruction::AShr:
|
|
return B.getOperator(ptml::CBuilder::Operator::RShift);
|
|
case Instruction::Shl:
|
|
return B.getOperator(ptml::CBuilder::Operator::LShift);
|
|
case Instruction::And:
|
|
return IsBool ? B.getOperator(PTMLOperator::BoolAnd) :
|
|
B.getOperator(ptml::CBuilder::Operator::And);
|
|
case Instruction::Or:
|
|
return IsBool ? B.getOperator(PTMLOperator::BoolOr) :
|
|
B.getOperator(ptml::CBuilder::Operator::Or);
|
|
case Instruction::Xor:
|
|
return B.getOperator(ptml::CBuilder::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::CBuilder &B) {
|
|
using llvm::CmpInst;
|
|
const Tag Op = [&Pred, &B]() {
|
|
switch (Pred) {
|
|
case CmpInst::ICMP_EQ: ///< equal
|
|
return B.getOperator(ptml::CBuilder::Operator::CmpEq);
|
|
case CmpInst::ICMP_NE: ///< not equal
|
|
return B.getOperator(ptml::CBuilder::Operator::CmpNeq);
|
|
case CmpInst::ICMP_UGT: ///< unsigned greater than
|
|
case CmpInst::ICMP_SGT: ///< signed greater than
|
|
return B.getOperator(ptml::CBuilder::Operator::CmpGt);
|
|
case CmpInst::ICMP_UGE: ///< unsigned greater or equal
|
|
case CmpInst::ICMP_SGE: ///< signed greater or equal
|
|
return B.getOperator(ptml::CBuilder::Operator::CmpGte);
|
|
case CmpInst::ICMP_ULT: ///< unsigned less than
|
|
case CmpInst::ICMP_SLT: ///< signed less than
|
|
return B.getOperator(ptml::CBuilder::Operator::CmpLt);
|
|
case CmpInst::ICMP_ULE: ///< unsigned less or equal
|
|
case CmpInst::ICMP_SLE: ///< signed less or equal
|
|
return B.getOperator(ptml::CBuilder::Operator::CmpLte);
|
|
default:
|
|
revng_abort("Unknown comparison operator");
|
|
}
|
|
}();
|
|
return " " + Op + " ";
|
|
}
|
|
|
|
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 model::Type &OpType0 = *TypeMap.at(Op0);
|
|
const model::Type &OpType1 = *TypeMap.at(Op1);
|
|
|
|
revng_assert(OpType0.isScalar() and OpType1.isScalar());
|
|
|
|
// 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 model::Type &PointerModelType = OpType0.isPointer() ? OpType0 :
|
|
OpType1;
|
|
const model::Type &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);
|
|
}
|
|
|
|
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)) {
|
|
// Those are usually noops on the LLVM IR.
|
|
const llvm::Value *Op = I->getOperand(0);
|
|
rc_return addDebugInfo(I, rc_recur getToken(Op), 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 "";
|
|
}
|
|
|
|
case llvm::Instruction::Ret: {
|
|
|
|
std::string Result = B.getKeyword(ptml::CBuilder::Keyword::Return)
|
|
.toString();
|
|
if (auto *Ret = llvm::cast<llvm::ReturnInst>(I);
|
|
llvm::Value *ReturnedVal = Ret->getReturnValue())
|
|
Result += " " + rc_recur getToken(ReturnedVal);
|
|
|
|
rc_return addDebugInfo(I, std::move(Result), B);
|
|
}
|
|
|
|
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 Op2String = rc_recur getToken(Op2);
|
|
|
|
rc_return addDebugInfo(I,
|
|
addParentheses(Condition) + " ? "
|
|
+ addParentheses(Op1String) + " : "
|
|
+ addParentheses(Op2String),
|
|
B);
|
|
}
|
|
|
|
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::TypeDefinition *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;
|
|
}
|
|
|
|
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());
|
|
|
|
bool NextStatementDoesNotReturn = false;
|
|
|
|
for (const Instruction &I : *BB) {
|
|
revng_log(Log, "Analyzing: " << dumpToString(I));
|
|
|
|
auto *Call = dyn_cast<llvm::CallInst>(&I);
|
|
|
|
bool IsStatement = isStatement(I);
|
|
|
|
if (not IsStatement) {
|
|
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)
|
|
or isComment(&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)) {
|
|
B.append(std::string(getToken(&I)) + (not isComment(&I) ? ";\n" : ""));
|
|
|
|
if (NextStatementDoesNotReturn)
|
|
B.append("// The previous function call does not return\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
|
|
B.append(VarName + " " + B.getOperator(ptml::CBuilder::Operator::Assign)
|
|
+ " " + std::move(RHSExpression) + ";\n");
|
|
} else {
|
|
std::string Error = "Cannot emit statement: ";
|
|
Error += dumpToString(Call).c_str();
|
|
revng_abort(Error.c_str());
|
|
}
|
|
|
|
if (IsStatement)
|
|
NextStatementDoesNotReturn = false;
|
|
|
|
if (Call != nullptr and isCallToIsolatedFunction(Call)) {
|
|
const auto &[CallEdge, _] = Cache.getCallEdge(Model, Call);
|
|
if (CallEdge->hasAttribute(Model, model::FunctionAttribute::NoReturn)) {
|
|
NextStatementDoesNotReturn = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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::CBuilder::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;
|
|
}
|
|
|
|
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);
|
|
rc_return addDebugInfo(I, rc_recur getToken(Op0), B);
|
|
}
|
|
}
|
|
rc_return rc_recur getToken(Br->getCondition());
|
|
}
|
|
|
|
case NodeKind::NK_Not: {
|
|
revng_log(VisitLog, "(not)");
|
|
|
|
const NotNode *N = cast<NotNode>(E);
|
|
ExprNode *Negated = N->getNegatedNode();
|
|
rc_return B.getOperator(ptml::CBuilder::Operator::BoolNot)
|
|
+ addAlwaysParentheses(rc_recur buildGHASTCondition(Negated, EmitBB));
|
|
}
|
|
|
|
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::CBuilder::Operator;
|
|
const Tag &OpToken = E->getKind() == NodeKind::NK_And ?
|
|
B.getOperator(PTMLOperator::BoolAnd) :
|
|
B.getOperator(PTMLOperator::BoolOr);
|
|
rc_return addAlwaysParentheses(Child1Token) + " " + OpToken.toString() + " "
|
|
+ addAlwaysParentheses(Child2Token);
|
|
}
|
|
|
|
default:
|
|
revng_abort("Unknown ExprNode kind");
|
|
}
|
|
}
|
|
|
|
static std::string makeWhile(const ptml::CBuilder &B,
|
|
const std::string &CondExpr) {
|
|
revng_assert(not CondExpr.empty());
|
|
return B.getKeyword(ptml::CBuilder::Keyword::While).toString() + " ("
|
|
+ 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());
|
|
|
|
// TODO: drop this workaround when we properly support emission of
|
|
// variable declarations with inline initialization.
|
|
//
|
|
// At the moment in C we can't emit variable declarations with inline
|
|
// initialization. Not for some substantial problem, but we haven't
|
|
// implemented it yet. As a result if TypeMap.at(VarDeclCall) returns a
|
|
// const-qualified type we will end up generating C code that doesn't
|
|
// compile, when it tries to assign a value to the variable for
|
|
// initializing it separately from the declaration.
|
|
// To work around this, until we don't support emission of variable
|
|
// declarations with inline initialization, we have to strip away
|
|
// constness.
|
|
auto NonConst = model::getNonConst(*TypeMap.at(VarDeclCall));
|
|
|
|
B.append(B.getNamedCInstance(*NonConst, VarName) + ";\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 = getCallSitePrototype(Model, VarDeclCall);
|
|
revng_assert(Prototype != nullptr);
|
|
|
|
auto Named = B.getNamedInstanceOfReturnType(*Prototype, VarName);
|
|
B.append(Named + ";\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::CBuilder::Operator;
|
|
if (Break->breaksFromWithinSwitch()) {
|
|
revng_assert(not SwitchStateVars.empty()
|
|
and not SwitchStateVars.back().empty());
|
|
B.append(SwitchStateVars.back() + " "
|
|
+ B.getOperator(PTMLOperator::Assign) + " " + B.getTrueTag()
|
|
+ ";\n");
|
|
}
|
|
}
|
|
[[fallthrough]];
|
|
|
|
case ASTNode::NodeKind::NK_SwitchBreak: {
|
|
revng_log(VisitLog, "(NK_SwitchBreak)");
|
|
|
|
B.append(B.getKeyword(ptml::CBuilder::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())
|
|
B.append(B.getKeyword(ptml::CBuilder::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());
|
|
B.append(B.getKeyword(ptml::CBuilder::Keyword::If) + " (" + CondExpr
|
|
+ ") ");
|
|
{
|
|
Scope TheScope = B.getCurvedBracketScope();
|
|
// "Then" expression (always emitted)
|
|
if (nullptr == If->getThen())
|
|
B.appendLineComment("Empty");
|
|
else
|
|
rc_recur emitGHASTNode(If->getThen());
|
|
}
|
|
|
|
// "Else" expression (optional)
|
|
if (If->hasElse()) {
|
|
B.append(" " + B.getKeyword(ptml::CBuilder::Keyword::Else) + " ");
|
|
Scope TheScope = B.getCurvedBracketScope();
|
|
rc_recur emitGHASTNode(If->getElse());
|
|
}
|
|
B.append("\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()) {
|
|
B.append(B.getKeyword(ptml::CBuilder::Keyword::Do) + " ");
|
|
} else {
|
|
if (Loop->isWhileTrue()) {
|
|
CondExpr = B.getTrueTag().toString();
|
|
} else {
|
|
revng_assert(Loop->isWhile());
|
|
CondExpr = rc_recur makeLoopCondition(Loop->getRelatedCondition());
|
|
}
|
|
B.append(makeWhile(B, CondExpr) + " ");
|
|
}
|
|
|
|
{
|
|
Scope TheScope = B.getCurvedBracketScope();
|
|
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()) {
|
|
B.append(" " + makeWhile(B, CondExpr) + ";");
|
|
}
|
|
B.append("\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());
|
|
std::string NewVarName = NameGenerator.nextSwitchStateVar();
|
|
std::string SwitchStateVar = B.getVariableReferenceTag(ModelFunction,
|
|
NewVarName);
|
|
SwitchStateVars.push_back(std::move(SwitchStateVar));
|
|
using COperator = ptml::CBuilder::Operator;
|
|
B.append(B.tokenTag("bool", ptml::c::tokens::Type) + " "
|
|
+ B.getVariableDefinitionTag(ModelFunction, NewVarName) + " "
|
|
+ B.getOperator(COperator::Assign) + " " + B.getFalseTag()
|
|
+ ";\n");
|
|
}
|
|
|
|
// Generate the condition of the switch
|
|
StringToken SwitchVarToken;
|
|
model::UpcastableType 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).copy();
|
|
} 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
|
|
SwitchVarType = model::PrimitiveType::makeUnsigned(8);
|
|
}
|
|
revng_assert(not SwitchVarToken.empty());
|
|
|
|
// Generate the switch statement
|
|
B.append(B.getKeyword(ptml::CBuilder::Keyword::Switch).toString() + " ("
|
|
+ SwitchVarToken.str().str() + ") ");
|
|
{
|
|
Scope TheScope = B.getCurvedBracketScope();
|
|
using PTMLKeyword = ptml::CBuilder::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) {
|
|
B.append(B.getKeyword(ptml::CBuilder::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
|
|
B.append(B.getNumber(CaseConst->getValue()).toString());
|
|
} else {
|
|
B.append(B.getNumber(CaseVal).toString());
|
|
}
|
|
B.append(":\n");
|
|
}
|
|
|
|
{
|
|
Scope InnerScope = B.getCurvedBracketScope();
|
|
// Generate the case body
|
|
rc_recur emitGHASTNode(CaseNode);
|
|
}
|
|
B.append(" " + B.getKeyword(PTMLKeyword::Break) + ";\n");
|
|
}
|
|
|
|
// Generate the default case if it exists
|
|
if (auto *Default = Switch->getDefault()) {
|
|
B.append(B.getKeyword(ptml::CBuilder::Keyword::Default) + ":\n");
|
|
{
|
|
Scope TheScope = B.getCurvedBracketScope();
|
|
rc_recur emitGHASTNode(Default);
|
|
}
|
|
B.append(" " + B.getKeyword(PTMLKeyword::Break) + ";\n");
|
|
}
|
|
}
|
|
B.append("\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());
|
|
B.append(B.getKeyword(ptml::CBuilder::Keyword::If) + " ("
|
|
+ SwitchStateVars.back() + ")");
|
|
{
|
|
|
|
Scope ThenScope = B.getCurvedBracketScope();
|
|
B.append(B.getKeyword(ptml::CBuilder::Keyword::Break) + ";");
|
|
}
|
|
B.append("\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.
|
|
B.append(LoopStateVar + " "
|
|
+ B.getOperator(ptml::CBuilder::Operator::Assign) + " "
|
|
+ std::to_string(StateValue) + ";\n");
|
|
} break;
|
|
}
|
|
|
|
rc_return;
|
|
}
|
|
|
|
static std::string getModelArgIdentifier(const model::TypeDefinition &ModelFT,
|
|
const llvm::Argument &Argument,
|
|
const ptml::CTypeBuilder &B) {
|
|
const llvm::Function *LLVMFunction = Argument.getParent();
|
|
unsigned ArgNo = Argument.getArgNo();
|
|
|
|
if (auto *RFT = dyn_cast<model::RawFunctionDefinition>(&ModelFT)) {
|
|
auto NumModelArguments = RFT->Arguments().size();
|
|
revng_assert(ArgNo <= NumModelArguments + 1);
|
|
revng_assert(LLVMFunction->arg_size() == NumModelArguments
|
|
or (not RFT->StackArgumentsType().isEmpty()
|
|
and (LLVMFunction->arg_size() == NumModelArguments + 1)));
|
|
|
|
if (ArgNo < NumModelArguments) {
|
|
const auto &Argument = *std::next(RFT->Arguments().begin(), ArgNo);
|
|
return B.getReferenceTag(*RFT, Argument);
|
|
|
|
} else {
|
|
return B.getStackArgumentReferenceTag(*RFT);
|
|
}
|
|
|
|
} else if (auto *CFT = dyn_cast<model::CABIFunctionDefinition>(&ModelFT)) {
|
|
revng_assert(LLVMFunction->arg_size() == CFT->Arguments().size());
|
|
revng_assert(ArgNo < CFT->Arguments().size());
|
|
return B.getReferenceTag(*CFT, CFT->Arguments().at(ArgNo));
|
|
|
|
} else {
|
|
revng_abort("Unexpected function type");
|
|
}
|
|
}
|
|
|
|
void CCodeGenerator::emitFunction(bool NeedsLocalStateVar) {
|
|
revng_log(Log, "========= Emitting Function " << LLVMFunction.getName());
|
|
revng_log(VisitLog, "========= Function " << LLVMFunction.getName());
|
|
LoggerIndent Indent{ VisitLog };
|
|
|
|
auto FTagScope = B.getScopeTag(ptml::CBuilder::Scopes::Function);
|
|
|
|
// Extract user comments from the model and emit them as PTML just before
|
|
// the prototype.
|
|
B.append(B.getFunctionComment(ModelFunction));
|
|
|
|
// Print function's prototype
|
|
B.printFunctionPrototype(Prototype, ModelFunction, false);
|
|
|
|
// Set up the argument identifiers to be used in the function's body.
|
|
for (const auto &Argument : LLVMFunction.args())
|
|
TokenMap[&Argument] = getModelArgIdentifier(Prototype, Argument, B);
|
|
|
|
// Print the function body
|
|
B.append(" ");
|
|
{
|
|
Scope Body = B.getCurvedBracketScope(ptml::c::scopes::FunctionBody.str());
|
|
|
|
// We expect just one stack type definition.
|
|
bool IsStackDefined = false;
|
|
|
|
// Declare the local variable representing the stack frame
|
|
if (not ModelFunction.StackFrameType().isEmpty()) {
|
|
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());
|
|
|
|
revng_assert(not IsStackDefined, "Multiple stack variables?");
|
|
const model::StructDefinition &Struct = *ModelFunction.stackFrameType();
|
|
// In the artifacts generated by this LLVM-based backend we've given up
|
|
// the requirement of emitting syntactically valid C code. Hence, we can
|
|
// always emit the stack frame type definition inside the body of the
|
|
// function itself, because we don't care anymore if it clashes with a
|
|
// global definition that we have emitted in a header outside the
|
|
// function's body.
|
|
if (B.Configuration.EnableStackFrameInlining) {
|
|
B.printDefinition(Struct, " " + std::move(VarName));
|
|
} else {
|
|
auto Named = B.getNamedCInstance(*ModelFunction.StackFrameType(),
|
|
std::move(VarName));
|
|
B.append(Named + ";\n");
|
|
}
|
|
IsStackDefined = true;
|
|
|
|
} 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)
|
|
B.append(B.tokenTag("uint64_t", ptml::c::tokens::Type) + " "
|
|
+ LoopStateVarDeclaration + ";\n");
|
|
|
|
// Recursively print the body of this function
|
|
emitGHASTNode(GHAST.getRoot());
|
|
}
|
|
|
|
B.append("\n");
|
|
}
|
|
|
|
static std::string decompileFunction(ControlFlowGraphCache &Cache,
|
|
const llvm::Function &LLVMFunc,
|
|
const ASTTree &CombedAST,
|
|
const Binary &Model,
|
|
const ASTVarDeclMap &VarToDeclare,
|
|
bool NeedsLocalStateVar,
|
|
ptml::CTypeBuilder &B) {
|
|
std::string Result;
|
|
|
|
llvm::raw_string_ostream Out(Result);
|
|
B.setOutputStream(Out);
|
|
|
|
CCodeGenerator Backend(Cache, Model, LLVMFunc, CombedAST, VarToDeclare, B);
|
|
Backend.emitFunction(NeedsLocalStateVar);
|
|
Out.flush();
|
|
|
|
return Result;
|
|
}
|
|
|
|
static bool hasLoopDispatchers(const ASTTree &GHAST) {
|
|
return needsLoopVar(GHAST.getRoot());
|
|
}
|
|
|
|
static ASTVarDeclMap computeVariableDeclarationScope(const llvm::Function &F,
|
|
const ASTTree &GHAST) {
|
|
PendingVariableListType PendingVariables;
|
|
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)) {
|
|
PendingVariables.push_back(Call);
|
|
}
|
|
|
|
revng_assert(not isCallToNonIsolated(Call)
|
|
or not getCalledFunction(Call)->isTargetIntrinsic());
|
|
}
|
|
}
|
|
|
|
return computeVarDeclMap(GHAST, PendingVariables);
|
|
}
|
|
|
|
std::string decompile(ControlFlowGraphCache &Cache,
|
|
llvm::Function &F,
|
|
const model::Binary &Model,
|
|
ptml::CTypeBuilder &B) {
|
|
using namespace llvm;
|
|
Task T2(3, Twine("decompile Function: ") + 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()) {
|
|
GHAST.dumpASTOnFile(F.getName().str(),
|
|
"ast-backend",
|
|
"AST-during-c-codegen.dot");
|
|
}
|
|
|
|
// Generated C code for F
|
|
auto VariablesToDeclare = computeVariableDeclarationScope(F, GHAST);
|
|
auto NeedsLoopStateVar = hasLoopDispatchers(GHAST);
|
|
return decompileFunction(Cache,
|
|
F,
|
|
GHAST,
|
|
Model,
|
|
VariablesToDeclare,
|
|
NeedsLoopStateVar,
|
|
B);
|
|
}
|