Files
2025-11-12 15:28:54 +02:00

1869 lines
72 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/GenericDomTree.h"
#include "revng/ABI/FunctionType/Layout.h"
#include "revng/ADT/RecursiveCoroutine.h"
#include "revng/ADT/ScopedExchange.h"
#include "revng/Clift/CliftDialect.h"
#include "revng/CliftImportModel/ImportModel.h"
#include "revng/Clifter/Clifter.h"
#include "revng/LocalVariables/LocalVariableHelpers.h"
#include "revng/Model/Binary.h"
#include "revng/Model/FunctionTags.h"
#include "revng/Model/IRHelpers.h"
#include "revng/Pipeline/Location.h"
#include "revng/Pipes/Ranks.h"
#include "revng/RestructureCFG/ScopeGraphGraphTraits.h"
#include "revng/Support/Debug.h"
#include "revng/Support/Identifier.h"
namespace clift = mlir::clift;
using namespace clift;
namespace {
static Logger BasicBlockLog{ "llvm-to-clift-basic-blocks" };
static Logger ExpressionLog{ "llvm-to-clift-expressions" };
// These helper functions are used to save insertion points *after* an
// operation, while the usual operating mode of `OpBuilder::InsertPoint` is
// *before* and operation. On save, the saved iterator is moved backwards by one
// position, conversely moved forward on restore. For the begin/end positions,
// it can be imagined that they loop around to the other. For this reason the
// end position actually signifies insertion at the start of the block.
//
// The scenario where saving *after* vs *before* an operation (or end) makes a
// difference is as follows: We emit some operation X in block B and want to
// save the position *after* X. If X is at the end B, saving the position
// normally would result in saving an end iterator for B. If some new operation
// Y is subsequently inserted at the end of B, the meaning of the previously
// saved position is changed. The iterator still points to the end of B, but now
// the preceding operation (and so the operation *after* which we insert) is no
// longer X but rather Y. For the same reason the usual mechanism does not allow
// saving the position at the beginning of a block (or *after* an imagined
// operation before the first operation in the block, if any).
[[nodiscard]] static mlir::OpBuilder::InsertPoint
saveInsertionPointAfter(const mlir::OpBuilder &Builder) {
mlir::Block *Block = Builder.getInsertionBlock();
mlir::Block::iterator Point = Builder.getInsertionPoint();
// End iterator specifies insertion at the start of Block. Non-end iterator
// specifies insertion *after* the operation referred to by the iterator.
Point = Point == Block->begin() ? Block->end() : std::prev(Point);
return mlir::OpBuilder::InsertPoint(Block, Point);
}
static void
restoreInsertionPointAfter(mlir::OpBuilder &Builder,
mlir::OpBuilder::InsertPoint InsertPoint) {
revng_assert(InsertPoint.isSet());
mlir::Block *Block = InsertPoint.getBlock();
mlir::Block::iterator Point = InsertPoint.getPoint();
// Convert an end iterator back into begin and advance non-end iterators. This
// is because the builder inserts operations *before* the specified iterator.
Point = Point == Block->end() ? Block->begin() : std::next(Point);
Builder.setInsertionPoint(Block, Point);
}
using ScopeGraphPostDomTree = llvm::PostDomTreeOnView<llvm::BasicBlock, Scope>;
class ClifterImpl final : public Clifter {
public:
// It is important only to query minimal properties about the model in the
// importer constructor to avoid all imported functions depending on those
// properties.
explicit ClifterImpl(mlir::ModuleOp Module, const model::Binary &Model) :
Context(Module.getContext()),
CurrentModule(Module),
Model(Model),
Builder(Context),
PointerSize(getPointerSize(Model.Architecture())) {
revng_assert(clift::hasModuleAttr(Module));
Builder.setInsertionPointToEnd(Module.getBody());
}
private:
/* Debug info */
mlir::Location getLocation(const llvm::DISubprogram *Subprogram) {
if (Subprogram == nullptr)
return mlir::UnknownLoc::get(Context);
auto Content = mlir::StringAttr::get(Context, Subprogram->getName());
return mlir::NameLoc::get(Content, mlir::UnknownLoc::get(Context));
}
mlir::Location getLocation(const llvm::DebugLoc &DL) {
if (not DL)
return mlir::UnknownLoc::get(Context);
const llvm::MDNode *Scope = DL->getScope();
return getLocation(llvm::dyn_cast_or_null<llvm::DISubprogram>(Scope));
}
mlir::Location getLocation(const llvm::BasicBlock *BB) {
return getLocation(&BB->front());
}
mlir::Location getLocation(const llvm::Instruction *I) {
return getLocation(I->getDebugLoc());
}
/* Type utilities */
static uint64_t getIntegerSize(unsigned IntegerWidth) {
if (IntegerWidth == 1)
IntegerWidth = 8;
revng_check(IntegerWidth % 8 == 0);
// Convert the size in bits to a size in octets:
return IntegerWidth / 8;
}
clift::ValueType
getPrimitiveType(uint64_t Size,
PrimitiveKind Kind = PrimitiveKind::GenericKind) {
return PrimitiveType::get(Context, Kind, Size);
}
clift::ValueType makePointerType(clift::ValueType ElementType) {
return PointerType::get(ElementType, PointerSize);
}
clift::ValueType getVoidType() {
if (not VoidTypeCache)
VoidTypeCache = getPrimitiveType(0, PrimitiveKind::VoidKind);
return VoidTypeCache;
}
clift::ValueType getVoidPointerType() {
if (not VoidPointerTypeCache)
VoidPointerTypeCache = makePointerType(getVoidType());
return VoidPointerTypeCache;
}
clift::ValueType getIntptrType() {
if (not IntptrTypeCache)
IntptrTypeCache = getPrimitiveType(PointerSize);
return IntptrTypeCache;
}
template<typename FunctionT>
model::UpcastableType getPrototype(const FunctionT &Function) {
if (auto &&Type = Function.Prototype())
return Type;
return Model.makeType(Model.defaultPrototype()->key());
}
/* Model type import */
clift::ValueType importModelType(const model::Type &Type) {
auto EmitError = [&]() -> mlir::InFlightDiagnostic {
return Context->getDiagEngine().emit(mlir::UnknownLoc::get(Context),
mlir::DiagnosticSeverity::Error);
};
return clift::importModelType(EmitError, *Context, Type, Model);
}
clift::ValueType importModelType(const model::TypeDefinition &Type) {
auto EmitError = [&]() -> mlir::InFlightDiagnostic {
return Context->getDiagEngine().emit(mlir::UnknownLoc::get(Context),
mlir::DiagnosticSeverity::Error);
};
return clift::importModelType(EmitError, *Context, Type, Model);
}
template<typename TypeT, typename ModelTypeT>
TypeT importModelType(const ModelTypeT &Type) {
return mlir::cast<TypeT>(importModelType(Type));
}
/* LLVM type import */
clift::ValueType
importLLVMIntegerType(const llvm::IntegerType *Type,
PrimitiveKind Kind = PrimitiveKind::GenericKind) {
return getPrimitiveType(getIntegerSize(Type->getBitWidth()), Kind);
}
clift::ValueType importLLVMPointerType(const llvm::PointerType *Type) {
revng_assert(Type->getAddressSpace() == 0);
return getVoidPointerType();
}
clift::ValueType importLLVMArrayType(const llvm::ArrayType *Type) {
auto ElementType = importLLVMType(Type->getElementType());
return ArrayType::get(ElementType, Type->getNumElements());
}
clift::ValueType importLLVMType(const llvm::Type *Type) {
if (Type->isVoidTy())
return getVoidType();
if (auto *T = llvm::dyn_cast<llvm::IntegerType>(Type))
return importLLVMIntegerType(T);
if (auto *T = llvm::dyn_cast<llvm::PointerType>(Type))
return importLLVMPointerType(T);
if (auto *T = llvm::dyn_cast<llvm::ArrayType>(Type))
return importLLVMArrayType(T);
revng_abort("Unsupported LLVM type");
}
/* LLVM expression import */
uint64_t getConstantInt(const llvm::Value *Value) {
return llvm::cast<llvm::ConstantInt>(Value)->getZExtValue();
}
// Determines if a argument is passed as a pointer to the actual value. In
// these cases an indirection must be inserted when using the argument.
static bool
isByAddressParameter(const abi::FunctionType::Layout::Argument &Parameter) {
switch (Parameter.Kind) {
case abi::FunctionType::ArgumentKind::PointerToCopy:
case abi::FunctionType::ArgumentKind::ReferenceToAggregate:
return true;
default:
return false;
}
}
template<typename EmitT>
auto getOrEmitSymbol(const llvm::GlobalObject *G, EmitT &&Emit) {
using ReturnType = std::invoke_result_t<EmitT>;
auto [Iterator, Inserted] = SymbolMapping.try_emplace(G);
if (Inserted) {
mlir::OpBuilder::InsertionGuard Guard(Builder);
Builder.setInsertionPointToEnd(CurrentModule.getBody());
Iterator->second = std::forward<EmitT>(Emit)();
}
return mlir::cast<ReturnType>(Iterator->second);
}
static std::string getHelperStructFieldName(unsigned Index) {
std::string Name;
{
llvm::raw_string_ostream Out(Name);
Out << "field_" << Index;
}
return Name;
}
// Import an LLVM type used as a helper function return type. If the type is a
// struct type, create a Clift struct type with a handle derived from the
// helper function name.
clift::ValueType importHelperReturnType(const llvm::Type *Type,
llvm::StringRef HelperName) {
auto Struct = llvm::dyn_cast<llvm::StructType>(Type);
if (not Struct)
return importLLVMType(Type);
revng_assert(Struct->isLiteral());
auto Location = pipeline::location(revng::ranks::HelperStructType,
HelperName.str());
llvm::SmallVector<FieldAttr> Fields;
Fields.reserve(Struct->getNumElements());
uint64_t Offset = 0;
for (auto [I, T] : llvm::enumerate(Struct->elements())) {
clift::ValueType FieldType = importLLVMType(T);
std::string FieldName = getHelperStructFieldName(I);
std::string FieldHandle = Location
.extend(revng::ranks::HelperStructField,
FieldName)
.toString();
Fields.push_back(FieldAttr::get(Context,
FieldHandle,
makeNameAttr<FieldAttr>(Context,
FieldHandle,
FieldName),
Offset,
FieldType));
Offset += FieldType.getByteSize();
}
auto Handle = pipeline::locationString(revng::ranks::HelperStructType,
HelperName.str());
std::string
Name = Model.Configuration().Naming().ArtificialReturnValuePrefix()
+ sanitizeIdentifier(HelperName);
auto Definition = StructAttr::get(Context,
Handle,
makeNameAttr<StructAttr>(Context,
Handle,
Name),
Offset,
Fields);
return StructType::get(Context, Definition);
}
// Import a Clift function type from an LLVM function type and a helper name
// used for deriving the type handle.
clift::FunctionType importHelperType(const llvm::FunctionType *Type,
llvm::StringRef HelperName) {
revng_assert(not Type->isVarArg());
clift::ValueType ReturnType = importHelperReturnType(Type->getReturnType(),
HelperName);
llvm::SmallVector<clift::ValueType> ParameterTypes;
ParameterTypes.reserve(Type->getNumParams());
for (const llvm::Type *T : Type->params())
ParameterTypes.push_back(importLLVMType(T));
auto Handle = pipeline::locationString(revng::ranks::HelperFunction,
HelperName.str());
return FunctionType::get(Context,
Handle,
makeNameAttr<FunctionType>(Context, Handle),
ReturnType,
ParameterTypes);
}
// Import a Clift function type from an LLVM function, using the name of the
// function for deriving the type handle.
clift::FunctionType importHelperType(const llvm::Function *F) {
return importHelperType(F->getFunctionType(), F->getName());
}
clift::FunctionOp
emitHelperFunctionDeclaration(mlir::Location Loc,
llvm::StringRef HelperName,
clift::FunctionType FunctionType) {
auto [Iterator, Inserted] = HelperMapping.try_emplace(HelperName);
if (Inserted) {
mlir::OpBuilder::InsertionGuard Guard(Builder);
Builder.setInsertionPointToEnd(CurrentModule.getBody());
auto Op = Builder.create<FunctionOp>(Loc, HelperName, FunctionType);
Op.setHandle(pipeline::locationString(revng::ranks::HelperFunction,
HelperName.str()));
Iterator->second = Op;
} else {
revng_assert(Iterator->second.getFunctionType() == FunctionType);
}
return Iterator->second;
}
clift::FunctionOp emitHelperFunctionDeclaration(const llvm::Function *F) {
return getOrEmitSymbol(F, [&]() -> clift::FunctionOp {
return emitHelperFunctionDeclaration(getLocation(F->getSubprogram()),
F->getName(),
importHelperType(F));
});
}
template<typename FunctionT, typename RankT, typename... ArgsT>
clift::FunctionOp emitModelFunctionDeclaration(const llvm::Function *F,
const FunctionT &MF,
const RankT &Rank,
ArgsT &&...Args) {
model::UpcastableType Prototype = getPrototype(MF);
auto FunctionType = importModelType<clift::FunctionType>(*Prototype);
return getOrEmitSymbol(F, [&]() -> clift::FunctionOp {
// It is important not to query any model properties in this scope, as
// doing so would break invalidation when the import of a function uses a
// declaration already emitted during the import of previous function.
auto Op = Builder.create<FunctionOp>(getLocation(F->getSubprogram()),
F->getName(),
FunctionType);
Op.setHandle(pipeline::locationString(Rank,
std::forward<ArgsT>(Args)...));
return Op;
});
}
clift::FunctionOp emitIsolatedFunctionDeclaration(const llvm::Function *F,
const model::Function *MF) {
return emitModelFunctionDeclaration(F,
*MF,
revng::ranks::Function,
MF->Entry());
}
clift::FunctionOp emitImportedFunctionDeclaration(const llvm::Function *F) {
llvm::StringRef Name = F->getName();
revng_check(Name.consume_front("dynamic_"));
auto It = Model.ImportedDynamicFunctions().find(Name.str());
revng_check(It != Model.ImportedDynamicFunctions().end());
return emitModelFunctionDeclaration(F,
*It,
revng::ranks::DynamicFunction,
Name.str());
}
clift::FunctionOp emitFunctionDeclaration(const llvm::Function *F) {
if (const model::Function *MF = llvmToModelFunction(Model, *F))
return emitIsolatedFunctionDeclaration(F, MF);
auto Tags = FunctionTags::TagsSet::from(F);
if (Tags.contains(FunctionTags::DynamicFunction))
return emitImportedFunctionDeclaration(F);
return emitHelperFunctionDeclaration(F);
}
clift::ValueType emitGlobalObject(const llvm::GlobalObject *G) {
if (const auto *F = llvm::dyn_cast<llvm::Function>(G))
return emitFunctionDeclaration(F).getFunctionType();
revng_abort("Unsupported global object kind");
}
template<typename OpT, typename... ArgsT>
mlir::Value emitExpr(mlir::Location Loc, ArgsT &&...Args) {
return Builder.create<OpT>(Loc, std::forward<ArgsT>(Args)...);
}
mlir::Value emitCast(mlir::Location Loc,
mlir::Value Value,
clift::ValueType TargetType,
CastKind Kind = CastKind::Bitcast) {
if (Value.getType() != TargetType)
Value = Builder.create<CastOp>(Loc, TargetType, Value, Kind);
return Value;
}
// Apply an implicit conversion to the requested type. The conversion is only
// applied in the case that neither the source nor target values have array
// type, and as long as both have the same size. If the cast is possible, it
// is naturally performed using the bitcast mode. If the cast is not possible,
// no cast is emitted and the original value is returned instead. In the case
// of failure to convert LLVM IR to Clift, this is a likely point of failure.
// When that happens, invalid Clift is emitted, causing subsequent failure in
// verification.
//
// During development of the LLVM IR to Clift converter, this behaviour was
// found to be more useful for debugging than simply aborting. It is generally
// easier to find the problem when full context of the surrounding Clift code
// is available.
mlir::Value emitImplicitCast(mlir::Location Loc,
mlir::Value Value,
clift::ValueType TargetType) {
auto SourceType = mlir::cast<clift::ValueType>(Value.getType());
if (SourceType == TargetType)
return Value;
auto UnderlyingSourceT = dealias(SourceType, true);
auto UnderlyingTargetT = dealias(TargetType, true);
if (UnderlyingSourceT.getByteSize() != UnderlyingTargetT.getByteSize())
return Value;
if (mlir::isa<ArrayType>(UnderlyingSourceT))
return Value;
if (mlir::isa<ArrayType>(UnderlyingTargetT))
return Value;
return emitCast(Loc, Value, TargetType);
}
// Used for emitting operations acting on integer operands (arithmetic,
// comparison, etc.) of a specific kind. Before applying the operation, the
// operands are automatically converted to the requested kind, if necessary.
// After the operation, the result is automaticlaly converted to generic kind.
mlir::Value emitIntegerOp(mlir::Location Loc,
PrimitiveKind Kind,
auto ApplyOperation,
std::same_as<mlir::Value> auto... Operands) {
auto ConvertToKind = [&](mlir::Value &Value, PrimitiveKind Kind) {
auto UnderlyingType = getUnderlyingIntegerType(Value.getType());
revng_assert(UnderlyingType);
uint64_t Size = UnderlyingType.getSize();
Value = emitCast(Loc, Value, getPrimitiveType(Size, Kind));
};
(ConvertToKind(Operands, Kind), ...);
mlir::Value Result = ApplyOperation(Operands...);
if (Kind != PrimitiveKind::GenericKind)
ConvertToKind(Result, PrimitiveKind::GenericKind);
return Result;
}
// Applies an integer cast of the operand to the specified size, using the
// specified primitive kind. Finally the result is converted to generic kind.
mlir::Value emitIntegerCast(mlir::Location Loc,
mlir::Value Operand,
uint64_t Size,
PrimitiveKind Kind) {
uint64_t SrcSize = getUnderlyingIntegerType(Operand.getType()).getSize();
CastKind Cast = CastKind::Bitcast;
if (Size > SrcSize)
Cast = CastKind::Extend;
if (Size < SrcSize)
Cast = CastKind::Truncate;
auto EmitCast = [&](mlir::Value Operand) {
return emitCast(Loc, Operand, getPrimitiveType(Size, Kind), Cast);
};
return emitIntegerOp(Loc, Kind, EmitCast, Operand);
}
FunctionOp getHelperFunction(llvm::StringRef HelperName,
clift::FunctionType FunctionType) {
return emitHelperFunctionDeclaration(mlir::UnknownLoc::get(Context),
HelperName,
FunctionType);
}
mlir::Value useGlobal(mlir::Location Loc, clift::GlobalOpInterface Global) {
return Builder.create<UseOp>(Loc, Global.getType(), Global.getName());
}
mlir::Value emitHelperCall(mlir::Location Loc,
FunctionOp Function,
llvm::ArrayRef<mlir::Value> Arguments) {
auto FunctionType = Function.getFunctionType();
llvm::SmallVector<mlir::Value> CastArgs;
CastArgs.reserve(Arguments.size());
for (auto [A, T] : llvm::zip(Arguments, FunctionType.getArgumentTypes()))
CastArgs.push_back(emitImplicitCast(Loc, A, T));
return Builder.create<CallOp>(Loc,
FunctionType.getReturnType(),
useGlobal(Loc, Function),
CastArgs);
}
mlir::Value emitHelperCall(mlir::Location Loc,
const llvm::Function *F,
llvm::ArrayRef<mlir::Value> Arguments) {
return emitHelperCall(Loc, emitHelperFunctionDeclaration(F), Arguments);
}
mlir::Value emitHelperCall(mlir::Location Loc,
llvm::StringRef HelperName,
mlir::Type ReturnType,
llvm::ArrayRef<mlir::Type> ParameterTypes,
llvm::ArrayRef<mlir::Value> Arguments) {
auto Handle = pipeline::locationString(revng::ranks::HelperFunction,
HelperName.str());
auto NameAttr = makeNameAttr<clift::FunctionType>(Context, Handle);
auto FunctionType = clift::FunctionType::get(Context,
Handle,
NameAttr,
ReturnType,
ParameterTypes);
return emitHelperCall(Loc,
getHelperFunction(HelperName, FunctionType),
Arguments);
}
RecursiveCoroutine<mlir::Value>
emitStructInitializer(const llvm::CallInst *Call) {
mlir::Location Loc = getLocation(Call);
auto UseBegin = Call->use_begin();
auto UseEnd = Call->use_end();
revng_assert(UseBegin != UseEnd);
// StructInitializer must have exactly one user of type ReturnInst.
revng_assert(llvm::isa<llvm::ReturnInst>(UseBegin->getUser()));
revng_assert(std::next(UseBegin) == UseEnd);
auto T = mlir::dyn_cast<StructType>(CurrentFunction.getReturnType());
revng_assert(Call->arg_size() == T.getFields().size());
llvm::SmallVector<mlir::Value> Initializers;
for (const auto &[A, F] : llvm::zip(Call->args(), T.getFields())) {
mlir::Value Value = rc_recur emitExpression(A, Loc);
Value = emitImplicitCast(Loc, Value, F.getType());
Initializers.push_back(Value);
}
rc_return Builder.create<AggregateOp>(Loc, T, Initializers);
}
RecursiveCoroutine<mlir::Value>
emitOpaqueExtractValue(const llvm::CallInst *Call) {
mlir::Location Loc = getLocation(Call);
mlir::Value Aggregate = rc_recur emitExpression(Call->getArgOperand(0),
Loc);
uint64_t Index = getConstantInt(Call->getArgOperand(1));
auto Struct = mlir::cast<StructType>(Aggregate.getType());
revng_assert(Index < Struct.getFields().size());
mlir::Type FieldType = Struct.getFields()[Index].getType();
mlir::Value FieldValue = Builder.create<AccessOp>(Loc,
FieldType,
Aggregate,
/*indirect=*/false,
Index);
rc_return emitImplicitCast(Loc,
FieldValue,
importLLVMType(Call->getType()));
}
RecursiveCoroutine<mlir::Value> emitHelperCall(const llvm::CallInst *Call) {
// TODO: Use getCalledFunction. Pending fix for uniqued-by-prototype issue.
// const llvm::Function *Callee = Call->getCalledFunction();
auto *Callee = llvm::dyn_cast<llvm::Function>(Call->getCalledOperand());
revng_assert(Callee != nullptr);
auto Tags = FunctionTags::TagsSet::from(Callee);
if (Tags.contains(FunctionTags::StructInitializer))
rc_return rc_recur emitStructInitializer(Call);
if (Tags.contains(FunctionTags::OpaqueExtractValue))
rc_return rc_recur emitOpaqueExtractValue(Call);
mlir::Location Loc = getLocation(Call);
llvm::SmallVector<mlir::Value> Arguments;
Arguments.reserve(Call->arg_size());
for (const llvm::Value *Argument : Call->args())
Arguments.push_back(rc_recur emitExpression(Argument, Loc));
rc_return emitHelperCall(Loc,
emitHelperFunctionDeclaration(Callee),
Arguments);
}
struct StringLiteral {
clift::ArrayType Type;
std::string Data;
};
bool detectStringLiteralImpl(const llvm::GlobalVariable *V,
StringLiteral &String) {
if (not V->isConstant())
return false;
const auto *Type = llvm::dyn_cast<llvm::ArrayType>(V->getValueType());
if (Type == nullptr)
return false;
const auto *ET = llvm::dyn_cast<llvm::IntegerType>(Type->getElementType());
if (ET == nullptr or ET->getBitWidth() != 8)
return false;
const llvm::Constant *Init = V->getInitializer();
if (Init == nullptr)
return false;
revng_assert(Init->getType() == Type);
const auto *Array = llvm::dyn_cast<llvm::ConstantDataArray>(Init);
if (Array == nullptr and not llvm::isa<llvm::ConstantAggregateZero>(Init))
return false;
revng_assert(Type->getNumElements() != 0);
unsigned Length = Type->getNumElements() - 1;
const auto GetChar = [&](unsigned Index) -> uint8_t {
uint64_t Value = Array ?
getConstantInt(Array->getAggregateElement(Index)) :
0;
revng_assert(Value < 0x100);
return Value;
};
if (GetChar(Length) != 0)
return false;
auto CharType = clift::PrimitiveType::get(Context,
PrimitiveKind::NumberKind,
1,
/*IsConst=*/true);
String.Type = clift::ArrayType::get(CharType, Type->getNumElements());
String.Data.reserve(Length);
for (unsigned I = 0; I < Length; ++I)
String.Data.push_back(GetChar(I));
return true;
}
// Returns a non-null pointer to a string literal description if the specified
// global variable represents a string literal. Successful detections are
// cached in a table.
const StringLiteral *detectStringLiteral(const llvm::GlobalObject *O) {
if (const auto *V = llvm::dyn_cast<llvm::GlobalVariable>(O)) {
auto [Iterator, Inserted] = StringMapping.try_emplace(V);
if (not Inserted or detectStringLiteralImpl(V, Iterator->second))
return &Iterator->second;
StringMapping.erase(Iterator);
}
return nullptr;
}
RecursiveCoroutine<mlir::Value>
emitExpression(const llvm::Value *V, mlir::Location SurroundingLocation) {
LoggerIndent Indent(ExpressionLog);
if (auto G = llvm::dyn_cast<llvm::GlobalObject>(V)) {
if (const StringLiteral *String = detectStringLiteral(G)) {
revng_log(ExpressionLog, "llvm::GlobalObject -> StringOp");
auto Op = Builder.create<StringOp>(SurroundingLocation,
String->Type,
std::move(String->Data));
auto Type = makePointerType(String->Type.getElementType());
rc_return emitCast(SurroundingLocation, Op, Type, CastKind::Decay);
}
if (const auto *F = llvm::dyn_cast<llvm::Function>(G)) {
revng_log(ExpressionLog, "llvm::Function -> UseOp");
auto Function = emitFunctionDeclaration(F);
rc_return Builder.create<UseOp>(SurroundingLocation,
Function.getFunctionType(),
Function.getSymNameAttr());
}
revng_abort("Unsupported global object kind");
}
if (auto It = ValueMapping.find(V); It != ValueMapping.end()) {
revng_log(ExpressionLog, "<existing value>");
mlir::Value Value = It->second.Value;
if (It->second.TakeAddressOnUse) {
Value = Builder.create<AddressofOp>(SurroundingLocation,
makePointerType(Value.getType()),
Value);
}
rc_return Value;
}
if (auto A = llvm::dyn_cast<llvm::Argument>(V)) {
revng_log(ExpressionLog, "llvm::Argument");
LoggerIndent Indent(ExpressionLog);
auto It = ArgumentMapping.find(A);
revng_assert(It != ArgumentMapping.end());
mlir::Value Value = It->second.Argument;
if (It->second.TakeAddressOnUse) {
Value = Builder.create<AddressofOp>(SurroundingLocation,
makePointerType(Value.getType()),
Value);
}
rc_return emitImplicitCast(SurroundingLocation,
Value,
It->second.CastType);
}
if (auto U = llvm::dyn_cast<llvm::UndefValue>(V)) {
revng_log(ExpressionLog, "llvm::UndefValue -> UndefOp");
mlir::Type Type = importLLVMType(U->getType());
rc_return Builder.create<UndefOp>(SurroundingLocation, Type);
}
if (auto C = llvm::dyn_cast<llvm::ConstantInt>(V)) {
revng_log(ExpressionLog, "llvm::ConstantInt -> ImmediateOp");
const llvm::IntegerType *T = llvm::cast<llvm::IntegerType>(C->getType());
rc_return Builder.create<ImmediateOp>(SurroundingLocation,
importLLVMIntegerType(T),
C->getZExtValue());
}
if (auto N = llvm::dyn_cast<llvm::ConstantPointerNull>(V)) {
revng_log(ExpressionLog, "llvm::ConstantPointerNull -> ImmediateOp");
auto Op = Builder.create<ImmediateOp>(SurroundingLocation,
getIntptrType(),
/*Value=*/0);
LoggerIndent Indent(ExpressionLog);
rc_return emitCast(SurroundingLocation, Op, getVoidPointerType());
}
if (auto E = llvm::dyn_cast<llvm::ConstantExpr>(V)) {
revng_log(ExpressionLog, "llvm::ConstantExpr");
LoggerIndent Indent(ExpressionLog);
// TODO: This could be made more efficient by implementing direct
// ConstantExpr import.
llvm::Instruction *I = E->getAsInstruction();
mlir::Value Value = emitExpression(I, SurroundingLocation);
I->deleteValue();
rc_return Value;
}
if (auto I = llvm::dyn_cast<llvm::AllocaInst>(V)) {
mlir::Location Loc = SurroundingLocation;
if (auto It = AllocaMapping.find(I); It != AllocaMapping.end()) {
revng_log(ExpressionLog, "llvm::AllocaInst");
LoggerIndent Indent(ExpressionLog);
auto Type = makePointerType(It->second.getType());
auto Value = Builder.create<AddressofOp>(Loc, Type, It->second);
rc_return emitImplicitCast(Loc, Value, importLLVMType(I->getType()));
}
revng_assert(not llvm::isa<llvm::Constant>(I->getArraySize()));
revng_abort("Non-constant alloca is not supported.");
}
if (auto I = llvm::dyn_cast<llvm::LoadInst>(V)) {
revng_log(ExpressionLog, "llvm::LoadInst");
LoggerIndent Indent(ExpressionLog);
mlir::Location Loc = getLocation(I);
revng_log(ExpressionLog, "Subexpression:");
mlir::Value Pointer = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getPointerOperand(),
Loc));
clift::ValueType ValueType = importLLVMType(V->getType());
clift::ValueType PointerType = makePointerType(ValueType);
Pointer = Builder.create<CastOp>(Loc,
PointerType,
Pointer,
CastKind::Bitcast);
revng_log(ExpressionLog, "IndirectionOp");
rc_return Builder.create<IndirectionOp>(Loc, ValueType, Pointer);
}
if (auto I = llvm::dyn_cast<llvm::StoreInst>(V)) {
revng_log(ExpressionLog, "llvm::StoreInst");
LoggerIndent Indent(ExpressionLog);
mlir::Location Loc = getLocation(I);
revng_log(ExpressionLog, "Pointer subexpression:");
mlir::Value Pointer = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getPointerOperand(),
Loc));
revng_log(ExpressionLog, "Value subexpression:");
mlir::Value Value = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getValueOperand(), Loc));
Pointer = Builder.create<CastOp>(Loc,
makePointerType(Value.getType()),
Pointer,
CastKind::Bitcast);
revng_log(ExpressionLog, "IndirectionOp");
mlir::Value Assignee = Builder.create<IndirectionOp>(Loc,
Value.getType(),
Pointer);
revng_log(ExpressionLog, "AssignOp");
rc_return Builder.create<AssignOp>(Loc, Value.getType(), Assignee, Value);
}
if (auto I = llvm::dyn_cast<llvm::BinaryOperator>(V)) {
revng_log(ExpressionLog, "llvm::BinaryOperator");
LoggerIndent Indent(ExpressionLog);
using Operators = llvm::BinaryOperator::BinaryOps;
mlir::Location Loc = getLocation(I);
revng_log(ExpressionLog, "LHS subexpression");
mlir::Value Lhs = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getOperand(0), Loc));
revng_log(ExpressionLog, "RHS subexpression");
mlir::Value Rhs = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getOperand(1), Loc));
PrimitiveKind Kind = PrimitiveKind::GenericKind;
switch (I->getOpcode()) {
case Operators::SDiv:
case Operators::SRem:
case Operators::AShr:
Kind = PrimitiveKind::SignedKind;
break;
case Operators::UDiv:
case Operators::URem:
case Operators::LShr:
Kind = PrimitiveKind::UnsignedKind;
break;
default:
break;
}
auto *IntegerType = llvm::cast<llvm::IntegerType>(V->getType());
auto Type = importLLVMIntegerType(IntegerType, Kind);
auto EmitOp = [&](mlir::Value Lhs, mlir::Value Rhs) {
switch (I->getOpcode()) {
case Operators::Add:
revng_log(ExpressionLog, "AddOp");
return emitExpr<AddOp>(Loc, Type, Lhs, Rhs);
case Operators::Sub:
revng_log(ExpressionLog, "SubOp");
return emitExpr<SubOp>(Loc, Type, Lhs, Rhs);
case Operators::Mul:
revng_log(ExpressionLog, "MulOp");
return emitExpr<MulOp>(Loc, Type, Lhs, Rhs);
case Operators::SDiv:
case Operators::UDiv:
revng_log(ExpressionLog, "DivOp");
return emitExpr<DivOp>(Loc, Type, Lhs, Rhs);
case Operators::SRem:
case Operators::URem:
revng_log(ExpressionLog, "RemOp");
return emitExpr<RemOp>(Loc, Type, Lhs, Rhs);
case Operators::Shl:
revng_log(ExpressionLog, "ShiftLeftOp");
return emitExpr<ShiftLeftOp>(Loc, Type, Lhs, Rhs);
case Operators::LShr:
case Operators::AShr:
revng_log(ExpressionLog, "ShiftRightOp");
return emitExpr<ShiftRightOp>(Loc, Type, Lhs, Rhs);
case Operators::And:
revng_log(ExpressionLog, "BitwiseAndOp");
return emitExpr<BitwiseAndOp>(Loc, Type, Lhs, Rhs);
case Operators::Or:
revng_log(ExpressionLog, "BitwiseOrOp");
return emitExpr<BitwiseOrOp>(Loc, Type, Lhs, Rhs);
case Operators::Xor:
revng_log(ExpressionLog, "BitwiseXorOp");
return emitExpr<BitwiseXorOp>(Loc, Type, Lhs, Rhs);
default:
revng_abort("Unsupported LLVM binary operator.");
}
};
rc_return emitIntegerOp(Loc, Kind, EmitOp, Lhs, Rhs);
}
if (auto I = llvm::dyn_cast<llvm::ICmpInst>(V)) {
revng_log(ExpressionLog, "llvm::ICmpInst");
LoggerIndent Indent(ExpressionLog);
using enum llvm::ICmpInst::Predicate;
PrimitiveKind Kind = PrimitiveKind::GenericKind;
switch (I->getPredicate()) {
case ICMP_SGT:
case ICMP_SGE:
case ICMP_SLT:
case ICMP_SLE:
Kind = PrimitiveKind::SignedKind;
break;
case ICMP_UGT:
case ICMP_UGE:
case ICMP_ULT:
case ICMP_ULE:
Kind = PrimitiveKind::UnsignedKind;
break;
default:
break;
}
mlir::Location Loc = getLocation(I);
revng_log(ExpressionLog, "LHS subexpression");
mlir::Value Lhs = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getOperand(0), Loc));
revng_log(ExpressionLog, "RHS subexpression");
mlir::Value Rhs = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getOperand(1), Loc));
auto *IntegerType = llvm::cast<llvm::IntegerType>(V->getType());
auto Type = importLLVMIntegerType(IntegerType, PrimitiveKind::SignedKind);
auto EmitOp = [&](mlir::Value Lhs, mlir::Value Rhs) {
switch (I->getPredicate()) {
case ICMP_EQ:
revng_log(ExpressionLog, "CmpEqOp");
return emitExpr<CmpEqOp>(Loc, Type, Lhs, Rhs);
case ICMP_NE:
revng_log(ExpressionLog, "CmpNeOp");
return emitExpr<CmpNeOp>(Loc, Type, Lhs, Rhs);
case ICMP_SGT:
case ICMP_UGT:
revng_log(ExpressionLog, "CmpGtOp");
return emitExpr<CmpGtOp>(Loc, Type, Lhs, Rhs);
case ICMP_SGE:
case ICMP_UGE:
revng_log(ExpressionLog, "CmpGeOp");
return emitExpr<CmpGeOp>(Loc, Type, Lhs, Rhs);
case ICMP_SLT:
case ICMP_ULT:
revng_log(ExpressionLog, "CmpLtOp");
return emitExpr<CmpLtOp>(Loc, Type, Lhs, Rhs);
case ICMP_SLE:
case ICMP_ULE:
revng_log(ExpressionLog, "CmpLeOp");
return emitExpr<CmpLeOp>(Loc, Type, Lhs, Rhs);
default:
revng_abort("Unsupported LLVM comparison predicate.");
}
};
// Pointer comparisons are handled separately. Non-signed comparisons are
// emitted directly as pointer comparisons. For signed comparisons, the
// pointer operands are converted to integers before the comparison.
if (llvm::isa<llvm::PointerType>(I->getOperand(0)->getType())) {
if (not I->isSigned())
rc_return EmitOp(Lhs, Rhs);
Lhs = emitCast(Loc, Lhs, getIntptrType());
Rhs = emitCast(Loc, Rhs, getIntptrType());
}
rc_return emitIntegerOp(Loc, Kind, EmitOp, Lhs, Rhs);
}
if (auto I = llvm::dyn_cast<llvm::CastInst>(V)) {
revng_log(ExpressionLog, "llvm::CastInst");
LoggerIndent Indent(ExpressionLog);
mlir::Location Loc = getLocation(I);
revng_log(ExpressionLog, "Subexpression:");
mlir::Value Operand = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getOperand(0), Loc));
auto getIntegerSize = [](const llvm::Type *Type) {
const auto *IntegerType = llvm::cast<llvm::IntegerType>(Type);
return ClifterImpl::getIntegerSize(IntegerType->getBitWidth());
};
auto EmitIntegerCast = [&](PrimitiveKind Kind) {
return emitIntegerCast(Loc,
Operand,
getIntegerSize(V->getType()),
Kind);
};
switch (I->getOpcode()) {
using Operators = llvm::CastInst::CastOps;
case Operators::Trunc:
rc_return EmitIntegerCast(PrimitiveKind::GenericKind);
case Operators::SExt:
rc_return EmitIntegerCast(PrimitiveKind::SignedKind);
case Operators::ZExt:
rc_return EmitIntegerCast(PrimitiveKind::UnsignedKind);
case Operators::PtrToInt:
Operand = emitCast(Loc, Operand, getIntptrType());
if (uint64_t S = getIntegerSize(I->getDestTy()); S > PointerSize) {
Operand = emitCast(Loc,
Operand,
getPrimitiveType(S),
CastKind::Extend);
}
rc_return Operand;
case Operators::IntToPtr:
if (getIntegerSize(I->getSrcTy()) > PointerSize) {
Operand = emitCast(Loc,
Operand,
getPrimitiveType(PointerSize),
CastKind::Truncate);
}
rc_return emitCast(Loc, Operand, getVoidPointerType());
default:
revng_abort("Unsupported LLVM cast operation.");
}
}
if (auto I = llvm::dyn_cast<llvm::CallInst>(V)) {
revng_log(ExpressionLog, "llvm::CallInst");
LoggerIndent Indent(ExpressionLog);
mlir::Location Loc = getLocation(I);
// Any call without a prototype is considered a helper function and is
// imported separately:
if (not I->hasMetadata(PrototypeMDName))
rc_return emitHelperCall(I);
const auto *ModelCallType = getCallSitePrototype(Model, I);
auto Layout = abi::FunctionType::Layout::make(*ModelCallType);
auto CallType = importModelType<clift::FunctionType>(*ModelCallType);
revng_log(ExpressionLog, "Callee subexpression:");
mlir::Value Function = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getCalledOperand(),
Loc));
clift::FunctionType
FunctionType = getFunctionOrFunctionPointerFunctionType(Function
.getType());
// If the call type does not match the function type of the callee,
// the callee must first be converted to a pointer to the appropriate
// function type:
if (CallType != FunctionType) {
// If the callee is a function and not a pointer to function, it must
// be decayed to a pointer before applying the type conversion:
if (mlir::isa<clift::FunctionType>(Function.getType())) {
Function = emitCast(Loc,
Function,
makePointerType(FunctionType),
CastKind::Decay);
}
Function = emitCast(Loc, Function, makePointerType(CallType));
}
llvm::ArrayRef LayoutArguments = getLayoutArguments(Layout);
revng_assert(I->arg_size() == LayoutArguments.size());
revng_assert(I->arg_size() == CallType.getArgumentTypes().size());
// Process each argument subexpression and apply the required conversions:
llvm::SmallVector<mlir::Value> Arguments;
for (size_t J = 0;
auto [A, T, L] :
llvm::zip(I->args(), CallType.getArgumentTypes(), LayoutArguments)) {
revng_log(ExpressionLog, "Argument " << J++ << " subexpression:");
mlir::Value Argument = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(A, Loc));
// Parameters which are passed by address in the LLVM IR are passed by
// value in Clift, and so the LLVM IR pointer value must be dereferenced
// to make the resulting Clift expression valid. In either case, an
// implicit cast is first applied if necessary.
if (isByAddressParameter(L)) {
Argument = emitImplicitCast(Loc, Argument, makePointerType(T));
Argument = Builder.create<IndirectionOp>(Loc, T, Argument);
} else {
Argument = emitImplicitCast(Loc, Argument, T);
}
Arguments.push_back(Argument);
}
revng_log(ExpressionLog, "CallOp");
rc_return Builder.create<CallOp>(Loc,
CallType.getReturnType(),
Function,
Arguments);
}
if (auto I = llvm::dyn_cast<llvm::SelectInst>(V)) {
revng_log(ExpressionLog, "llvm::SelectInst");
LoggerIndent Indent(ExpressionLog);
mlir::Location Loc = getLocation(I);
revng_log(ExpressionLog, "Condition subexpression:");
mlir::Value Condition = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getCondition(), Loc));
revng_log(ExpressionLog, "True subexpression:");
mlir::Value True = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getTrueValue(), Loc));
revng_log(ExpressionLog, "False subexpression:");
mlir::Value False = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getFalseValue(), Loc));
auto TrueType = mlir::cast<clift::ValueType>(True.getType());
auto FalseType = mlir::cast<clift::ValueType>(False.getType());
auto ResultType = TrueType.removeConst();
if (ResultType != FalseType.removeConst()) {
ResultType = importLLVMType(I->getType());
True = emitImplicitCast(Loc, True, ResultType);
False = emitImplicitCast(Loc, False, ResultType);
}
rc_return Builder.create<TernaryOp>(Loc,
ResultType,
Condition,
True,
False);
}
if (auto I = llvm::dyn_cast<llvm::GetElementPtrInst>(V)) {
auto Alloca = llvm::cast<llvm::AllocaInst>(I->getPointerOperand());
auto It = AllocaMapping.find(Alloca);
revng_assert(It != AllocaMapping.end());
auto AT = mlir::cast<clift::ArrayType>(It->second.getType());
auto PT = makePointerType(AT.getElementType());
revng_assert(I->getNumIndices() == 2);
auto IndexIterator = I->idx_begin();
revng_assert(getConstantInt(IndexIterator->get()) == 0);
uint64_t Index1 = getConstantInt((++IndexIterator)->get());
mlir::Location Loc = getLocation(I);
auto Operand = emitCast(Loc, It->second, PT, CastKind::Decay);
mlir::Value Immediate = emitExpr<ImmediateOp>(Loc,
getIntptrType(),
Index1);
rc_return emitExpr<PtrAddOp>(Loc, PT, Operand, Immediate);
}
if (auto I = llvm::dyn_cast<llvm::FreezeInst>(V))
rc_return emitExpression(I->getOperand(0), getLocation(I));
revng_abort("Unsupported LLVM instruction.");
}
mlir::Type emitExpressionTreeImpl(mlir::Block &B, auto EmitExpression) {
revng_assert(B.empty());
mlir::OpBuilder::InsertionGuard Guard(Builder);
Builder.setInsertionPointToEnd(&B);
mlir::Value Value = EmitExpression();
Builder.create<YieldOp>(Value.getLoc(), Value);
return Value.getType();
}
mlir::Type emitExpressionTreeImpl(mlir::Region &R, auto EmitExpression) {
revng_assert(R.empty());
return emitExpressionTreeImpl(R.emplaceBlock(), EmitExpression);
}
mlir::Type emitExpressionTree(mlir::Block &B,
const llvm::Value *V,
mlir::Location SurroundingLocation) {
return emitExpressionTreeImpl(B, [&]() {
return emitExpression(V, SurroundingLocation);
});
}
mlir::Type emitExpressionTree(mlir::Region &R,
const llvm::Value *V,
mlir::Location SurroundingLocation) {
return emitExpressionTreeImpl(R, [&]() {
return emitExpression(V, SurroundingLocation);
});
}
[[nodiscard]] std::pair<mlir::Block *, mlir::Type>
emitExpressionTree(const llvm::Value *V, mlir::Location SurroundingLocation) {
mlir::Block *B = new mlir::Block();
mlir::Type Type = emitExpressionTree(*B, V, SurroundingLocation);
return { B, Type };
}
/* LLVM control flow import */
template<typename OpT, typename... ArgsT>
OpT emitLocalDeclaration(mlir::Location Loc, ArgsT &&...Args) {
mlir::OpBuilder::InsertionGuard Guard(Builder);
Builder.setInsertionPointToEnd(LocalDeclarationBlock);
OpT Op = Builder.create<OpT>(Loc, std::forward<ArgsT>(Args)...);
return Op;
}
void emitAssignLabel(mlir::Value Label, mlir::Location Loc) {
Builder.create<AssignLabelOp>(Loc, Label);
}
// Determine if an edge from Pred to Succ exists in the scope graph of the
// current function.
static bool isScopeGraphEdge(const llvm::BasicBlock *Pred,
const llvm::BasicBlock *Succ) {
for (auto *BB : llvm::children<Scope<const llvm::BasicBlock *>>(Pred)) {
if (BB == Succ)
return true;
}
return false;
}
bool hasBeenEmitted(const llvm::BasicBlock *BB) {
auto It = BlockMapping.find(BB);
return It != BlockMapping.end() and It->second.InsertPoint.isSet();
}
// Emit a goto targeting the specified basic block.
// * If the basic block has already been emitted, a label assignment is
// inserted at the position matching the beginning of the basic block.
// * Otherwise, the label declaration is recorded in the basic block table
// and the matching assignment will be emitted when the basic block is
// emitted.
void emitGoto(mlir::Location Loc, const llvm::BasicBlock *BB) {
auto [Iterator, Inserted] = BlockMapping.try_emplace(BB);
if (not Iterator->second.Label) {
Iterator->second
.Label = emitLocalDeclaration<MakeLabelOp>(getLocation(BB));
}
if (not Iterator->second.HasAssignLabel
and Iterator->second.InsertPoint.isSet()) {
mlir::OpBuilder::InsertionGuard Guard(Builder);
restoreInsertionPointAfter(Builder, Iterator->second.InsertPoint);
emitAssignLabel(Iterator->second.Label, getLocation(BB));
Iterator->second.HasAssignLabel = true;
}
Builder.create<GotoOp>(Loc, Iterator->second.Label);
}
// Returns: { RequiresFullExpression, RequiresIndirection }
std::pair<bool, bool> requiresFullExpression(const llvm::CallInst *Call) {
if (Call->hasMetadata(PrototypeMDName)) {
const auto *ModelCallType = getCallSitePrototype(Model, Call);
auto Layout = abi::FunctionType::Layout::make(*ModelCallType);
namespace ReturnMethod = abi::FunctionType::ReturnMethod;
if (Layout.hasSPTAR())
return { true, true };
return { Layout.returnMethod() == ReturnMethod::RegisterSet, false };
}
return { llvm::isa<llvm::StructType>(Call->getType()), false };
}
// This function emits a single basic block as part of a larger C scope.
// Nested scopes reached through branches at the end of this basic block are
// emitted recursively using emitScope.
RecursiveCoroutine<void>
emitBasicBlock(const llvm::BasicBlock *BB,
const llvm::BasicBlock *InnerPostDom) {
revng_log(BasicBlockLog, "BB: '" << BB->getName() << "'");
LoggerIndent Indent(BasicBlockLog);
// Map BB to the MLIR block, emit label if necessary:
{
auto [Iterator, Inserted] = BlockMapping.try_emplace(BB);
// Save the current insertion point in case a label assignment must later
// be emitted at the start of this block.
revng_assert(not Iterator->second.InsertPoint.isSet());
Iterator->second.InsertPoint = saveInsertionPointAfter(Builder);
// Sometimes a goto (along a label declaration) targeting this basic block
// is emitted before the basic block itself. In these cases the label
// assignment could not be emitted along with the declaration and must be
// emitted here along with the basic block.
if (not Inserted and Iterator->second.Label) {
emitAssignLabel(Iterator->second.Label, getLocation(BB));
Iterator->second.HasAssignLabel = true;
}
}
const llvm::Instruction *Terminal = BB->getTerminator();
bool HasGotoMarker = false;
// Process each instruction contained in this basic block, until the
// terminal instruction is encountered.
for (const llvm::Instruction &I : *BB) {
if (&I == Terminal)
break;
// Alloca instructions get special handling and are emitted as local
// variables in Clift:
if (auto *Alloca = llvm::dyn_cast<llvm::AllocaInst>(&I)) {
const auto *Size = Alloca->getArraySize();
// Non-constant alloca is not supported:
revng_assert(not Size or llvm::isa<llvm::ConstantInt>(Size));
clift::ValueType Type;
if (hasStackTypeMetadata(Alloca)) {
Type = importModelType(*getStackTypeFromMetadata(Alloca, Model));
} else if (hasVariableTypeMetadata(Alloca)) {
Type = importModelType(*getVariableTypeFromMetadata(Alloca, Model));
} else {
Type = importLLVMType(Alloca->getAllocatedType());
if (Alloca->isArrayAllocation())
Type = ArrayType::get(Context, Type, getConstantInt(Size));
}
auto Op = emitLocalDeclaration<LocalVariableOp>(getLocation(Alloca),
Type);
auto [Iterator, Inserted] = AllocaMapping.try_emplace(Alloca, Op);
revng_assert(Inserted);
continue;
}
// Call instructions require special handling:
// 1. Some call instructions are only used to represent information about
// the scope-graph structure. Such calls are not emitted in Clift.
// 2. Some calls must only be evaluated once and as such are emitted as
// local variable initializers, with any further use of that call value
// during expression import being redirected to instead use the
// local variable created here.
if (const llvm::CallInst *Call = llvm::dyn_cast<llvm::CallInst>(&I)) {
if (isCallToTagged(Call, FunctionTags::GotoBlockMarker)) {
HasGotoMarker = true;
continue;
}
// Scope closer markers are just ignored. They only affect the scope
// graph structure.
if (isCallToTagged(Call, FunctionTags::ScopeCloserMarker))
continue;
// Some function calls are emitted in local variable initializers.
if (auto [FE, Indirection] = requiresFullExpression(Call); FE) {
mlir::Location Loc = getLocation(Call);
auto Op = Builder.create<ExpressionStatementOp>(Loc);
mlir::Value Local;
emitExpressionTreeImpl(Op.getExpression(), [&]() {
mlir::Value Initializer = emitExpression(Call, Loc);
mlir::Type Type = Initializer.getType();
Local = emitLocalDeclaration<LocalVariableOp>(Loc, Type);
return Builder.create<AssignOp>(Loc, Type, Local, Initializer);
});
auto [Iterator, Inserted] = ValueMapping.try_emplace(Call, Local);
revng_assert(Inserted);
Iterator->second.TakeAddressOnUse = Indirection;
continue;
}
}
// Finally, any instruction with no uses represents the root of an
// expression tree. Any such tree is emitted as an expression statement.
if (I.use_empty()) {
mlir::Location Loc = getLocation(&I);
auto Op = Builder.create<ExpressionStatementOp>(Loc);
emitExpressionTree(Op.getExpression(), &I, Loc);
}
}
// After the main basic block body is processed, the terminal instruction is
// handled:
// Scope-graph goto-markers should only be present in basic blocks
// terminating with an unconditional branch instruction.
revng_assert(llvm::isa<llvm::BranchInst>(Terminal) or not HasGotoMarker);
mlir::Location TerminalLoc = getLocation(Terminal);
if (llvm::isa<llvm::UnreachableInst>(Terminal)) {
// Do nothing.
} else if (auto *Return = llvm::dyn_cast<llvm::ReturnInst>(Terminal)) {
auto Op = Builder.create<ReturnOp>(TerminalLoc);
if (const llvm::Value *Value = Return->getReturnValue()) {
clift::FunctionType FunctionType = CurrentFunction.getFunctionType();
clift::ValueType FuncReturnType = FunctionType.getReturnType();
clift::ValueType LLVMReturnType = FuncReturnType;
// In SPTAR functions, values are returned by address. In this case
if (CurrentLayout.hasSPTAR())
LLVMReturnType = makePointerType(LLVMReturnType);
// Emit the expression tree rooted at the return instruction directly
// into the expression region of the newly created return operation:
emitExpressionTreeImpl(Op.getResult(), [&]() {
mlir::Value ReturnValue = emitExpression(Value, TerminalLoc);
// In Clift, arrays cannot be returned, so in the case that the
// returned expression has array type (knowing that an array value
// must be an lvalue), we can take its address, and read it as the
// appropriate type (type punning, violates strict aliasing).
if (auto AT = mlir::dyn_cast<ArrayType>(ReturnValue.getType())) {
ReturnValue = emitCast(TerminalLoc,
ReturnValue,
makePointerType(AT.getElementType()),
CastKind::Decay);
ReturnValue = emitCast(TerminalLoc,
ReturnValue,
makePointerType(LLVMReturnType),
CastKind::Bitcast);
ReturnValue = Builder.create<IndirectionOp>(TerminalLoc,
LLVMReturnType,
ReturnValue);
}
// Emit an implicit cast to the required return type if necessary:
ReturnValue = emitImplicitCast(TerminalLoc,
ReturnValue,
LLVMReturnType);
// In the case of SPTAR, because in the LLVM IR the return is by
// address, but in Clift the return is by value as usual, a final
// indirection is needed to convert the LLVM IR pointer to a value:
if (CurrentLayout.hasSPTAR()) {
ReturnValue = Builder.create<IndirectionOp>(TerminalLoc,
FuncReturnType,
ReturnValue);
}
return ReturnValue;
});
}
} else if (auto *Branch = llvm::dyn_cast<llvm::BranchInst>(Terminal)) {
if (Branch->isUnconditional()) {
const llvm::BasicBlock *Succ = Terminal->getSuccessor(0);
// In the presence of a scope-graph goto-marker, the unconditional
// branch is emitted in Clift as a goto. Otherwise it can be considered
// a continuation of the current scope and emitted recursively.
if (HasGotoMarker) {
revng_log(BasicBlockLog,
"Explicit goto: '" << Succ->getName() << "'");
LoggerIndent Indent(BasicBlockLog);
emitGoto(getLocation(Terminal), Succ);
} else if (Succ != InnerPostDom) {
revng_log(BasicBlockLog, "Via unconditional successor...");
rc_recur emitScope(Succ, InnerPostDom);
}
} else {
// Conditional branches may not be present in a block containing a
// scope-graph goto-marker. They are emitted as if statements in Clift.
revng_assert(not HasGotoMarker);
auto Op = Builder.create<IfOp>(TerminalLoc);
emitExpressionTree(Op.getCondition(),
Branch->getCondition(),
TerminalLoc);
mlir::OpBuilder::InsertionGuard Guard(Builder);
// Emit true branch:
revng_assert(Op.getThen().empty());
Builder.setInsertionPointToEnd(&Op.getThen().emplaceBlock());
revng_log(BasicBlockLog, "Via BranchInst[true]:");
(LoggerIndent(BasicBlockLog),
rc_recur emitScope(Branch->getSuccessor(0), InnerPostDom));
// Emit false branch:
revng_assert(Op.getElse().empty());
Builder.setInsertionPointToEnd(&Op.getElse().emplaceBlock());
revng_log(BasicBlockLog, "Via BranchInst[false]:");
(LoggerIndent(BasicBlockLog),
rc_recur emitScope(Branch->getSuccessor(1), InnerPostDom));
}
} else if (auto *Switch = llvm::dyn_cast<llvm::SwitchInst>(Terminal)) {
llvm::SmallVector<uint64_t> CaseValues;
CaseValues.reserve(Switch->getNumCases());
for (auto CH : Switch->cases())
CaseValues.push_back(CH.getCaseValue()->getZExtValue());
auto Op = Builder.create<SwitchOp>(TerminalLoc, CaseValues);
emitExpressionTree(Op.getCondition(),
Switch->getCondition(),
TerminalLoc);
mlir::OpBuilder::InsertionGuard Guard(Builder);
// Emit case blocks:
for (auto [I, CH] : llvm::enumerate(Switch->cases())) {
const llvm::BasicBlock *Succ = CH.getCaseSuccessor();
revng_assert(Op.getCaseRegion(I).empty());
Builder.setInsertionPointToEnd(&Op.getCaseRegion(I).emplaceBlock());
LoggerIndent Indent(BasicBlockLog);
if (hasBeenEmitted(Succ)) {
revng_log(BasicBlockLog,
"Implicit goto: '" << Succ->getName() << "'");
emitGoto(TerminalLoc, Succ);
} else {
revng_log(BasicBlockLog, "Via SwitchInst[" << CaseValues[I] << "]:");
rc_recur emitScope(Succ, InnerPostDom);
}
}
// Emit default block:
if (const llvm::BasicBlock *Succ = Switch->getDefaultDest();
Succ != nullptr and isScopeGraphEdge(BB, Succ)) {
revng_assert(Op.getDefaultCaseRegion().empty());
Builder
.setInsertionPointToEnd(&Op.getDefaultCaseRegion().emplaceBlock());
LoggerIndent Indent(BasicBlockLog);
if (hasBeenEmitted(Succ)) {
revng_log(BasicBlockLog,
"Implicit goto: '" << Succ->getName() << "'");
emitGoto(TerminalLoc, Succ);
} else {
revng_log(BasicBlockLog, "Via SwitchInst[default]:");
rc_recur emitScope(Succ, InnerPostDom);
}
}
} else {
revng_abort("Unsupported terminal instruction");
}
}
// This function emits a single logical C scope starting at BB and bounded by
// OuterPostDom, which is not part of the scope. Any nested scopes are emitted
// by emitBasicBlock.
//
// Putting it another way: this function goes down across a scope, while
// emitBasicBlock recurses right, into nested scopes:
//
// emitScope
// |
// +--> emitBasicBlock --> emitScope
// |
// + --> emitBasicBlock
// |
// + --> emitBasicBlock
// !
// |
// +--> emitBasicBlock
// !
//
// The subgraph of basic blocks recursively emitted by emitBasicBlock starting
// at some basic block B is bounded by the immediate post dominator of B,
// which is itself always equal to or dominated by OuterPostDom.
RecursiveCoroutine<void> emitScope(const llvm::BasicBlock *BB,
const llvm::BasicBlock *OuterPostDom) {
while (BB != OuterPostDom) {
const auto *InnerPostDom = PostDomTree[BB]->getIDom()->getBlock();
revng_assert(PostDomTree.dominates(OuterPostDom, InnerPostDom));
rc_recur emitBasicBlock(BB, InnerPostDom);
BB = InnerPostDom;
}
}
/* LLVM function import */
static llvm::ArrayRef<abi::FunctionType::Layout::Argument>
getLayoutArguments(const abi::FunctionType::Layout &Layout) {
llvm::ArrayRef LayoutArguments = Layout.Arguments;
if (Layout.hasSPTAR())
LayoutArguments = LayoutArguments.drop_front();
return LayoutArguments;
}
public:
clift::FunctionOp import(const llvm::Function *F) override {
const model::Function *MF = llvmToModelFunction(Model, *F);
revng_assert(MF != nullptr);
revng_log(BasicBlockLog, "Function: '" << F->getName() << "'");
LoggerIndent Indent(BasicBlockLog);
auto Op = emitIsolatedFunctionDeclaration(F, MF);
revng_assert(Op.getBody().empty());
mlir::Block &BodyBlock = Op.getBody().emplaceBlock();
CurrentFunction = Op;
CurrentLayout = abi::FunctionType::Layout::make(*getPrototype(*MF));
// Clear the function-specific mappings once this function is emitted.
auto MappingGuard = llvm::make_scope_exit([&]() {
ArgumentMapping.clear();
BlockMapping.clear();
AllocaMapping.clear();
ValueMapping.clear();
});
llvm::ArrayRef LayoutArguments = getLayoutArguments(CurrentLayout);
revng_assert(F->arg_size() == Op.getArgumentTypes().size());
revng_assert(F->arg_size() == LayoutArguments.size());
// Initialize the argument mapping for the function arguments. By-address
// parameters are detected using the function layout and require indirection
// before use. In Clift each argument has the type specified in the model.
// To make the uses of it as imported from the LLVM IR valid, on each use it
// must be converted to the type imported directly from the LLVM IR argument
// type. That type is imported here and stored in the argument description.
for (const auto [A, T, L] :
llvm::zip(F->args(), Op.getArgumentTypes(), LayoutArguments)) {
ArgumentMapping.try_emplace(&A,
BodyBlock.addArgument(T, Op->getLoc()),
/*TakeAddressOnUse=*/isByAddressParameter(L),
/*CastType=*/importLLVMType(A.getType()));
}
// Compute the scope graph. While the computation should not modify the
// function IR, it is not const-correct and so a const_cast must be used.
PostDomTree.recalculate(const_cast<llvm::Function &>(*F));
mlir::OpBuilder::InsertionGuard BuilderGuard(Builder);
Builder.setInsertionPointToEnd(&BodyBlock);
mlir::Block DeclarationBlock;
ScopedExchange _(LocalDeclarationBlock, &DeclarationBlock);
// Finally emit the function body starting at the entry block.
emitScope(&F->getEntryBlock(), PostDomTree.getRootNode()->getBlock());
BodyBlock.getOperations().splice(BodyBlock.getOperations().begin(),
DeclarationBlock.getOperations());
return Op;
}
private:
mlir::MLIRContext *const Context;
mlir::ModuleOp CurrentModule;
const model::Binary &Model;
mlir::OpBuilder Builder;
clift::FunctionOp CurrentFunction;
abi::FunctionType::Layout CurrentLayout;
mlir::Block *LocalDeclarationBlock = nullptr;
ScopeGraphPostDomTree PostDomTree;
clift::ValueType VoidTypeCache;
uint64_t PointerSize;
clift::ValueType VoidPointerTypeCache;
clift::ValueType IntptrTypeCache;
// While importing the LLVM IR to Clift, we import types from both the model
// and from the LLVM IR. This creates some discrepancies, particularly around
// function parameters and return types. Within function bodies we are in the
// land of LLVM IR types, while any external input (arguments to the current
// function or return values from called functions) or output (the return
// value of the current function or arguments to called functions) have types
// imported from the model. For this reason some conversions must be inserted
// whenever we cross between the two typing domains.
struct ArgumentMappingInfo {
mlir::Value Argument;
// True if the address of the value should be taken before use in Clift.
// When converting the model type of an argument to the LLVM IR typing,
// there may arise a discrepancy in representation: the model parameter may
// have type large_struct_t, while in the LLVM IR the parameter is passed by
// address. Taking the address of the argument before each use in the
// resulting Clift solves this discrepancy.
bool TakeAddressOnUse;
// While taking the address solves a discrepancy in representation, a
// discrepancy in kind may still remain: we might now have large_struct_t*,
// while the LLVM IR has an integer type. The representation is the same,
// but the import of a subsequent integer arithmetic operation on the value
// might fail. For this reason argument values are converted to the type
// imported directly from LLVM IR, thus ensuring that any subsequently
// imported expression remains valid.
mlir::Type CastType;
};
struct BlockMappingInfo {
// Point where the mapped LLVM IR basic block is emitted. If the block has
// not yet been visited, this is not set. Note that the LLVM IR basic block
// is not necessarily emitted at the *start* of any MLIR block.
mlir::OpBuilder::InsertPoint InsertPoint;
// The result value of the MakeLabelOp to be used as the target for gotos
// jumping into the mapped LLVM IR basic block.
mlir::Value Label;
// True if the AssignLabelOp has already been created.
bool HasAssignLabel = false;
};
struct ValueMappingInfo {
mlir::Value Value;
// See ArgumentMappingInfo::TakeAddressOnUse for more information. However,
// instead of arguments, this one applies to return values stored in local
// variables. For some function calls the importer creates local variables
// with the model typing, and when the LLVM IR function call returned by
// address, the address of the variable must be taken to resolve the
// discrepancy in representation.
bool TakeAddressOnUse;
ValueMappingInfo(mlir::Value Value) :
Value(Value), TakeAddressOnUse(false) {}
};
/* Per-module mappings - persisted between imported functions */
// Maps LLVM object to a Clift global op. Each used global object is only
// emitted once and hence forth the operation stored in this table is used.
llvm::DenseMap<const llvm::GlobalObject *, clift::GlobalOpInterface>
SymbolMapping;
// Maps LLVM object to a string literal description. Used for caching
// successful string literal detection results.
llvm::DenseMap<const llvm::GlobalVariable *, StringLiteral> StringMapping;
// Maps helper name to Clift function op. Each helper function declaration is
// only emitted once.
llvm::DenseMap<llvm::StringRef, clift::FunctionOp> HelperMapping;
/* Per-function mappings - cleared for each imported function */
// Maps LLVM IR function argument to a Clift argument description. Initialized
// before importing each function body. During expression import argument
// values are generated using the descriptions stored in this table.
llvm::DenseMap<const llvm::Argument *, ArgumentMappingInfo> ArgumentMapping;
// Maps LLVM IR basic block to a Clift import state for that block.
llvm::DenseMap<const llvm::BasicBlock *, BlockMappingInfo> BlockMapping;
// Maps LLVM IR alloca to a Clift local variable op result. Used during
// expression import to map uses of an alloca to the matching Clift local
// variable.
llvm::DenseMap<const llvm::AllocaInst *, mlir::Value> AllocaMapping;
// Maps an arbitrary LLVM IR value to a Clift value description. During
// expression import some values are only emitted once and any use of such a
// value is emitted using the description stored in this table.
llvm::DenseMap<const llvm::Value *, ValueMappingInfo> ValueMapping;
};
} // namespace
std::unique_ptr<Clifter> Clifter::make(mlir::ModuleOp Module,
const model::Binary &Model) {
return std::make_unique<ClifterImpl>(Module, Model);
}