Files
revng-revng/lib/Clifter/Clifter.cpp
Lauri Vasama d35e54627c Add Clift ValueType interface
Value types is the set of non-array object types. These types are
assignable, can be used as function parameters, and as return types
(along with void), and in other scenarios where the type must have usual
value-like behaviour with no weirdness (such as array decay).
2026-04-08 12:28:45 +03:00

1940 lines
74 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/ArrayRef.h"
#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{ "clifter-basic-blocks" };
static Logger ExpressionLog{ "clifter-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 {
class FunctionClifter;
struct StringLiteral {
clift::ArrayType Type;
std::string Data;
};
mlir::MLIRContext *const Context;
mlir::ModuleOp CurrentModule;
const model::Binary &Model;
mlir::OpBuilder Builder;
mlir::Type VoidTypeCache;
uint64_t PointerSize;
mlir::Type VoidPointerTypeCache;
mlir::Type IntptrTypeCache;
/* 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;
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;
}
IntegerType getIntegerType(uint64_t Size,
IntegerKind Kind = IntegerKind::Generic) {
return IntegerType::get(Context, Kind, Size);
}
mlir::Type makePointerType(mlir::Type ElementType) {
return PointerType::get(ElementType, PointerSize);
}
mlir::Type getVoidType() {
if (not VoidTypeCache)
VoidTypeCache = VoidType::get(Context);
return VoidTypeCache;
}
mlir::Type getVoidPointerType() {
if (not VoidPointerTypeCache)
VoidPointerTypeCache = makePointerType(getVoidType());
return VoidPointerTypeCache;
}
mlir::Type getIntptrType() {
if (not IntptrTypeCache)
IntptrTypeCache = getIntegerType(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 */
mlir::Type importType(const model::Type &Type) {
auto EmitError = [&]() -> mlir::InFlightDiagnostic {
return Context->getDiagEngine().emit(mlir::UnknownLoc::get(Context),
mlir::DiagnosticSeverity::Error);
};
return clift::importType(EmitError, *Context, Type, Model);
}
mlir::Type importType(const model::TypeDefinition &Type) {
auto EmitError = [&]() -> mlir::InFlightDiagnostic {
return Context->getDiagEngine().emit(mlir::UnknownLoc::get(Context),
mlir::DiagnosticSeverity::Error);
};
return clift::importType(EmitError, *Context, Type, Model);
}
template<typename TypeT, typename ModelTypeT>
TypeT importType(const ModelTypeT &Type) {
return mlir::cast<TypeT>(importType(Type));
}
/* LLVM type import */
IntegerType importLLVMIntegerType(const llvm::IntegerType *Type,
IntegerKind Kind = IntegerKind::Generic) {
return getIntegerType(getIntegerSize(Type->getBitWidth()), Kind);
}
mlir::Type importLLVMPointerType(const llvm::PointerType *Type) {
revng_assert(Type->getAddressSpace() == 0);
return getVoidPointerType();
}
mlir::Type importLLVMArrayType(const llvm::ArrayType *Type) {
auto ElementType = importLLVMType(Type->getElementType());
return ArrayType::get(ElementType, Type->getNumElements());
}
mlir::Type 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 global import */
uint64_t getConstantInt(const llvm::Value *Value) {
return llvm::cast<llvm::ConstantInt>(Value)->getZExtValue();
}
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.
mlir::Type 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())) {
mlir::Type 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),
makeCommentAttr<FieldAttr>(Context,
FieldHandle),
Offset,
FieldType));
Offset += getObjectSize(FieldType);
}
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),
makeCommentAttr<StructAttr>(Context,
Handle),
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());
mlir::Type ReturnType = importHelperReturnType(Type->getReturnType(),
HelperName);
llvm::SmallVector<mlir::Type> 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),
makeCommentAttr<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));
});
}
FunctionOp getHelperFunction(llvm::StringRef HelperName,
clift::FunctionType FunctionType) {
return emitHelperFunctionDeclaration(mlir::UnknownLoc::get(Context),
HelperName,
FunctionType);
}
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 = importType<clift::FunctionType>(*Prototype);
auto Handle = pipeline::locationString(Rank, std::forward<ArgsT>(Args)...);
// NOTE: `unwrap()` explicitly marks the entire container to have been read
// exactly (so *any* change to it triggers invalidation).
auto Attributes = MF.Attributes().unwrap();
// NOTE: no matter what, do not capture `MF`!
auto ImportImpl = [F, FunctionType, Handle, Attributes, this]() // format
-> clift::FunctionOp {
// WARNING: DO NOT QUERY MODEL PROPERTIES WITHIN THIS SCOPE.
//
// It is very important not to query any tracked model properties within
// this scope, as doing so would break invalidation when the import of
// a function uses a declaration already emitted during the import of
// a previous function.
//
// To be more precise, this problem arises when:
//
// 1. The Clifter imports the definition of function F1, where F1 uses
// some other function G. At this point a declaration of G is created
// in this scope.
//
// 2. The Clifter imports the definition of function F2, where F2 also
// uses G. At this point the declaration of G has already been created
// and this scope is not entered a second time.
//
// If model properties were queried within this scope, those accesses
// would not be tracked during the import of the definition of F2, and
// so changes to G would fail to correctly invalidate F2.
return importFunctionDeclaration(CurrentModule,
getLocation(F->getSubprogram()),
F->getName(),
Handle,
FunctionType,
Attributes);
};
return getOrEmitSymbol(F, ImportImpl);
}
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();
bool HasPrefix = Name.consume_front("dynamic_");
revng_check(HasPrefix);
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);
}
mlir::Type 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");
}
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::IntegerType::get(Context,
IntegerKind::Number,
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;
}
public:
clift::FunctionOp import(const llvm::Function *F) override;
};
class ClifterImpl::FunctionClifter {
// 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) {}
};
ClifterImpl &C;
mlir::MLIRContext *const Context;
mlir::OpBuilder Builder;
clift::FunctionOp Function;
const model::Function &ModelFunction;
abi::FunctionType::Layout FunctionLayout;
ScopeGraphPostDomTree PostDomTree;
uint64_t NextLocalVariableIndex = 0;
uint64_t NextGotoLabelIndex = 0;
mlir::Block LocalDeclarationBlock;
// 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;
public:
static clift::FunctionOp import(ClifterImpl &C, const llvm::Function *F) {
const model::Function *MF = llvmToModelFunction(C.Model, *F);
revng_assert(MF != nullptr);
revng_log(BasicBlockLog, "Function: '" << F->getName() << "'");
LoggerIndent Indent(BasicBlockLog);
auto Function = C.emitIsolatedFunctionDeclaration(F, MF);
return FunctionClifter(C, Function, *MF).import(F);
}
private:
explicit FunctionClifter(ClifterImpl &Clifter,
clift::FunctionOp Function,
const model::Function &ModelFunction) :
C(Clifter),
Context(Clifter.Context),
Builder(Clifter.Context),
Function(Function),
ModelFunction(ModelFunction),
FunctionLayout(makeLayout(Clifter, ModelFunction)) {}
static abi::FunctionType::Layout makeLayout(ClifterImpl &Clifter,
const model::Function &Model) {
return abi::FunctionType::Layout::make(*Clifter.getPrototype(Model));
}
/* LLVM expression import */
// 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 OpT, typename... ArgsT>
mlir::Value emitExpr(mlir::Location Loc, ArgsT &&...Args) {
return Builder.create<OpT>(Loc, std::forward<ArgsT>(Args)...);
}
template<typename OpT>
mlir::Value
emitCast(mlir::Location Loc, mlir::Value Value, mlir::Type TargetType) {
if (Value.getType() != TargetType)
Value = Builder.create<OpT>(Loc, TargetType, Value);
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,
mlir::Type TargetType) {
mlir::Type SourceType = Value.getType();
if (SourceType == TargetType)
return Value;
auto SourceT = clift::unwrapped_dyn_cast<ValueType>(SourceType);
if (not SourceT)
return Value;
auto TargetT = clift::unwrapped_dyn_cast<ValueType>(TargetType);
if (not TargetT)
return Value;
return emitCast<BitCastOp>(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,
IntegerKind Kind,
auto ApplyOperation,
std::same_as<mlir::Value> auto... Operands) {
auto ConvertToKind = [&](mlir::Value &Value, IntegerKind Kind) {
auto UnderlyingType = getUnderlyingIntegerType(Value.getType());
revng_assert(UnderlyingType);
uint64_t Size = UnderlyingType.getSize();
Value = emitCast<BitCastOp>(Loc, Value, C.getIntegerType(Size, Kind));
};
(ConvertToKind(Operands, Kind), ...);
mlir::Value Result = ApplyOperation(Operands...);
if (Kind != IntegerKind::Generic)
ConvertToKind(Result, IntegerKind::Generic);
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,
IntegerKind Kind = IntegerKind::Generic) {
uint64_t SrcSize = getUnderlyingIntegerType(Operand.getType()).getSize();
auto EmitCast = [&](mlir::Value Operand) {
auto NewType = C.getIntegerType(Size, Kind);
if (Size > SrcSize)
return emitCast<ExtendOp>(Loc, Operand, NewType);
if (Size < SrcSize)
return emitCast<TruncateOp>(Loc, Operand, NewType);
return emitCast<BitCastOp>(Loc, Operand, NewType);
};
return emitIntegerOp(Loc, Kind, EmitCast, Operand);
}
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, C.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 CommentAttr = makeCommentAttr<clift::FunctionType>(Context, Handle);
// TODO: should we add something explicitly identifying this as a helper?
llvm::ArrayRef<mlir::clift::CAttributeAttr> Attributes = {};
auto FunctionType = clift::FunctionType::get(Context,
Handle,
NameAttr,
CommentAttr,
ReturnType,
ParameterTypes,
Attributes);
return emitHelperCall(Loc,
C.getHelperFunction(HelperName, FunctionType),
Arguments);
}
RecursiveCoroutine<mlir::Value>
emitStructInitializer(const llvm::CallInst *Call) {
mlir::Location Loc = C.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>(Function.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 = C.getLocation(Call);
mlir::Value Aggregate = rc_recur emitExpression(Call->getArgOperand(0),
Loc);
uint64_t Index = C.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,
C.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 = C.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,
C.emitHelperFunctionDeclaration(Callee),
Arguments);
}
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 = C.detectStringLiteral(G)) {
revng_log(ExpressionLog, "llvm::GlobalObject -> StringOp");
auto Op = Builder.create<StringOp>(SurroundingLocation,
String->Type,
std::move(String->Data));
auto Type = C.makePointerType(String->Type.getElementType());
rc_return emitCast<DecayOp>(SurroundingLocation, Op, Type);
}
if (const auto *F = llvm::dyn_cast<llvm::Function>(G)) {
revng_log(ExpressionLog, "llvm::Function -> UseOp");
auto Function = C.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,
C.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,
C.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 = C.importLLVMType(U->getType());
rc_return Builder.create<UndefOp>(SurroundingLocation, Type);
}
if (auto I = llvm::dyn_cast<llvm::ConstantInt>(V)) {
revng_log(ExpressionLog, "llvm::ConstantInt -> ImmediateOp");
const llvm::IntegerType *T = llvm::cast<llvm::IntegerType>(I->getType());
rc_return Builder.create<ImmediateOp>(SurroundingLocation,
C.importLLVMIntegerType(T),
I->getZExtValue());
}
if (auto N = llvm::dyn_cast<llvm::ConstantPointerNull>(V)) {
revng_log(ExpressionLog, "llvm::ConstantPointerNull -> ImmediateOp");
auto Op = Builder.create<ImmediateOp>(SurroundingLocation,
C.getIntptrType(),
/*Value=*/0);
LoggerIndent Indent(ExpressionLog);
rc_return emitCast<BitCastOp>(SurroundingLocation,
Op,
C.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 = C.makePointerType(It->second.getType());
auto Value = Builder.create<AddressofOp>(Loc, Type, It->second);
rc_return emitImplicitCast(Loc, Value, C.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 = C.getLocation(I);
revng_log(ExpressionLog, "Subexpression:");
mlir::Value Pointer = (LoggerIndent(ExpressionLog),
rc_recur emitExpression(I->getPointerOperand(),
Loc));
mlir::Type ValueType = C.importLLVMType(V->getType());
mlir::Type PointerType = C.makePointerType(ValueType);
Pointer = Builder.create<BitCastOp>(Loc, PointerType, Pointer);
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 = C.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<BitCastOp>(Loc,
C.makePointerType(Value.getType()),
Pointer);
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 = C.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));
IntegerKind Kind = IntegerKind::Generic;
switch (I->getOpcode()) {
case Operators::SDiv:
case Operators::SRem:
case Operators::AShr:
Kind = IntegerKind::Signed;
break;
case Operators::UDiv:
case Operators::URem:
case Operators::LShr:
Kind = IntegerKind::Unsigned;
break;
default:
break;
}
auto *IntegerType = llvm::cast<llvm::IntegerType>(V->getType());
auto Type = C.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;
IntegerKind Kind = IntegerKind::Generic;
switch (I->getPredicate()) {
case ICMP_SGT:
case ICMP_SGE:
case ICMP_SLT:
case ICMP_SLE:
Kind = IntegerKind::Signed;
break;
case ICMP_UGT:
case ICMP_UGE:
case ICMP_ULT:
case ICMP_ULE:
Kind = IntegerKind::Unsigned;
break;
default:
break;
}
mlir::Location Loc = C.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 = C.importLLVMIntegerType(IntegerType, IntegerKind::Signed);
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<BitCastOp>(Loc, Lhs, C.getIntptrType());
Rhs = emitCast<BitCastOp>(Loc, Rhs, C.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 = C.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 = [&](IntegerKind Kind) {
return emitIntegerCast(Loc,
Operand,
GetIntegerSize(V->getType()),
Kind);
};
switch (I->getOpcode()) {
using Operators = llvm::CastInst::CastOps;
case Operators::SExt:
rc_return EmitIntegerCast(IntegerKind::Signed);
case Operators::ZExt:
case Operators::Trunc:
rc_return EmitIntegerCast(IntegerKind::Generic);
case Operators::PtrToInt:
Operand = emitCast<BitCastOp>(Loc, Operand, C.getIntptrType());
if (uint64_t S = GetIntegerSize(I->getDestTy()); S != C.PointerSize)
Operand = emitIntegerCast(Loc, Operand, S);
rc_return Operand;
case Operators::IntToPtr:
if (uint64_t S = GetIntegerSize(I->getSrcTy()); S != C.PointerSize)
Operand = emitIntegerCast(Loc, Operand, C.PointerSize);
rc_return emitCast<BitCastOp>(Loc, Operand, C.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 = C.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(C.Model, I);
auto Layout = abi::FunctionType::Layout::make(*ModelCallType);
auto CallType = C.importType<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<DecayOp>(Loc,
Function,
C.makePointerType(FunctionType));
}
Function = emitCast<BitCastOp>(Loc,
Function,
C.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, C.makePointerType(T));
Argument = Builder.create<IndirectionOp>(Loc, T, Argument);
} else {
Argument = emitImplicitCast(Loc, Argument, T);
}
Arguments.push_back(Argument);
}
revng_log(ExpressionLog, "CallOp");
mlir::Value Result = Builder.create<CallOp>(Loc,
CallType.getReturnType(),
Function,
Arguments);
if (Layout.returnMethod() == abi::FunctionType::ReturnMethod::Scalar) {
Result = emitImplicitCast(SurroundingLocation,
Result,
C.importLLVMType(I->getType()));
}
rc_return Result;
}
if (auto I = llvm::dyn_cast<llvm::SelectInst>(V)) {
revng_log(ExpressionLog, "llvm::SelectInst");
LoggerIndent Indent(ExpressionLog);
mlir::Location Loc = C.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));
mlir::Type ResultType = removeConst(True.getType());
if (ResultType != removeConst(False.getType())) {
ResultType = C.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 = C.makePointerType(AT.getElementType());
revng_assert(I->getNumIndices() == 2);
auto IndexIterator = I->idx_begin();
revng_assert(C.getConstantInt(IndexIterator->get()) == 0);
uint64_t Index1 = C.getConstantInt((++IndexIterator)->get());
mlir::Location Loc = C.getLocation(I);
auto Operand = emitCast<DecayOp>(Loc, It->second, PT);
mlir::Value Immediate = emitExpr<ImmediateOp>(Loc,
C.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), C.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) {
auto Label = emitLocalDeclaration<MakeLabelOp>(C.getLocation(BB));
Label.setHandle(pipeline::locationString(revng::ranks::GotoLabel,
ModelFunction.Entry(),
NextGotoLabelIndex++));
Iterator->second.Label = Label;
}
if (not Iterator->second.HasAssignLabel
and Iterator->second.InsertPoint.isSet()) {
mlir::OpBuilder::InsertionGuard Guard(Builder);
restoreInsertionPointAfter(Builder, Iterator->second.InsertPoint);
emitAssignLabel(Iterator->second.Label, C.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(C.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, C.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));
std::optional<std::string> Handle;
mlir::Type Type;
if (hasStackTypeMetadata(Alloca)) {
Type = C.importType(*getStackTypeFromMetadata(Alloca, C.Model));
Handle = pipeline::locationString(revng::ranks::StackFrameVariable,
ModelFunction.Entry());
} else if (hasVariableTypeMetadata(Alloca)) {
Type = C.importType(*getVariableTypeFromMetadata(Alloca, C.Model));
} else {
Type = C.importLLVMType(Alloca->getAllocatedType());
if (Alloca->isArrayAllocation())
Type = ArrayType::get(Context, Type, C.getConstantInt(Size));
}
auto Op = emitLocalDeclaration<LocalVariableOp>(C.getLocation(Alloca),
Type);
if (not Handle) {
// For any local variables without a more specific handle (e.g. stack
// frame variable), a generic handle with increasing indices is used.
Handle = pipeline::locationString(revng::ranks::LocalVariable,
ModelFunction.Entry(),
NextLocalVariableIndex++);
}
Op.setHandle(*Handle);
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 = C.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 = C.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 = C.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 = Function.getFunctionType();
mlir::Type FuncReturnType = FunctionType.getReturnType();
mlir::Type LLVMReturnType = FuncReturnType;
// In SPTAR functions, values are returned by address. In this case
if (FunctionLayout.hasSPTAR())
LLVMReturnType = C.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<DecayOp>(TerminalLoc,
ReturnValue,
C.makePointerType(AT.getElementType()));
ReturnValue = //
emitCast<BitCastOp>(TerminalLoc,
ReturnValue,
C.makePointerType(LLVMReturnType));
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 (FunctionLayout.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(C.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;
}
clift::FunctionOp import(const llvm::Function *F) {
revng_assert(Function.getBody().empty());
mlir::Block &BodyBlock = Function.getBody().emplaceBlock();
Builder.setInsertionPointToEnd(&BodyBlock);
llvm::ArrayRef LayoutArguments = getLayoutArguments(FunctionLayout);
revng_assert(F->arg_size() == Function.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(), Function.getArgumentTypes(), LayoutArguments)) {
ArgumentMapping.try_emplace(&A,
BodyBlock.addArgument(T, Function->getLoc()),
/*TakeAddressOnUse=*/isByAddressParameter(L),
/*CastType=*/C.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));
// Finally emit the function body starting at the entry block.
emitScope(&F->getEntryBlock(), PostDomTree.getRootNode()->getBlock());
BodyBlock.getOperations().splice(BodyBlock.getOperations().begin(),
LocalDeclarationBlock.getOperations());
return Function;
}
};
clift::FunctionOp ClifterImpl::import(const llvm::Function *F) {
return FunctionClifter::import(*this, F);
}
} // namespace
std::unique_ptr<Clifter> Clifter::make(mlir::ModuleOp Module,
const model::Binary &Model) {
return std::make_unique<ClifterImpl>(Module, Model);
}