// // 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; 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(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 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 TypeT importModelType(const ModelTypeT &Type) { return mlir::cast(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(Type)) return importLLVMIntegerType(T); if (auto *T = llvm::dyn_cast(Type)) return importLLVMPointerType(T); if (auto *T = llvm::dyn_cast(Type)) return importLLVMArrayType(T); revng_abort("Unsupported LLVM type"); } /* LLVM expression import */ uint64_t getConstantInt(const llvm::Value *Value) { return llvm::cast(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 auto getOrEmitSymbol(const llvm::GlobalObject *G, EmitT &&Emit) { using ReturnType = std::invoke_result_t; auto [Iterator, Inserted] = SymbolMapping.try_emplace(G); if (Inserted) { mlir::OpBuilder::InsertionGuard Guard(Builder); Builder.setInsertionPointToEnd(CurrentModule.getBody()); Iterator->second = std::forward(Emit)(); } return mlir::cast(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(Type); if (not Struct) return importLLVMType(Type); revng_assert(Struct->isLiteral()); auto Location = pipeline::location(revng::ranks::HelperStructType, HelperName.str()); llvm::SmallVector 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(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(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 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(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(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 clift::FunctionOp emitModelFunctionDeclaration(const llvm::Function *F, const FunctionT &MF, const RankT &Rank, ArgsT &&...Args) { model::UpcastableType Prototype = getPrototype(MF); auto FunctionType = importModelType(*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(getLocation(F->getSubprogram()), F->getName(), FunctionType); Op.setHandle(pipeline::locationString(Rank, std::forward(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(G)) return emitFunctionDeclaration(F).getFunctionType(); revng_abort("Unsupported global object kind"); } template mlir::Value emitExpr(mlir::Location Loc, ArgsT &&...Args) { return Builder.create(Loc, std::forward(Args)...); } mlir::Value emitCast(mlir::Location Loc, mlir::Value Value, clift::ValueType TargetType, CastKind Kind = CastKind::Bitcast) { if (Value.getType() != TargetType) Value = Builder.create(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(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(UnderlyingSourceT)) return Value; if (mlir::isa(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 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(Loc, Global.getType(), Global.getName()); } mlir::Value emitHelperCall(mlir::Location Loc, FunctionOp Function, llvm::ArrayRef Arguments) { auto FunctionType = Function.getFunctionType(); llvm::SmallVector CastArgs; CastArgs.reserve(Arguments.size()); for (auto [A, T] : llvm::zip(Arguments, FunctionType.getArgumentTypes())) CastArgs.push_back(emitImplicitCast(Loc, A, T)); return Builder.create(Loc, FunctionType.getReturnType(), useGlobal(Loc, Function), CastArgs); } mlir::Value emitHelperCall(mlir::Location Loc, const llvm::Function *F, llvm::ArrayRef Arguments) { return emitHelperCall(Loc, emitHelperFunctionDeclaration(F), Arguments); } mlir::Value emitHelperCall(mlir::Location Loc, llvm::StringRef HelperName, mlir::Type ReturnType, llvm::ArrayRef ParameterTypes, llvm::ArrayRef Arguments) { auto Handle = pipeline::locationString(revng::ranks::HelperFunction, HelperName.str()); auto NameAttr = makeNameAttr(Context, Handle); auto FunctionType = clift::FunctionType::get(Context, Handle, NameAttr, ReturnType, ParameterTypes); return emitHelperCall(Loc, getHelperFunction(HelperName, FunctionType), Arguments); } RecursiveCoroutine 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(UseBegin->getUser())); revng_assert(std::next(UseBegin) == UseEnd); auto T = mlir::dyn_cast(CurrentFunction.getReturnType()); revng_assert(Call->arg_size() == T.getFields().size()); llvm::SmallVector 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(Loc, T, Initializers); } RecursiveCoroutine 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(Aggregate.getType()); revng_assert(Index < Struct.getFields().size()); mlir::Type FieldType = Struct.getFields()[Index].getType(); mlir::Value FieldValue = Builder.create(Loc, FieldType, Aggregate, /*indirect=*/false, Index); rc_return emitImplicitCast(Loc, FieldValue, importLLVMType(Call->getType())); } RecursiveCoroutine 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(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 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(V->getValueType()); if (Type == nullptr) return false; const auto *ET = llvm::dyn_cast(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(Init); if (Array == nullptr and not llvm::isa(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(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 emitExpression(const llvm::Value *V, mlir::Location SurroundingLocation) { LoggerIndent Indent(ExpressionLog); if (auto G = llvm::dyn_cast(V)) { if (const StringLiteral *String = detectStringLiteral(G)) { revng_log(ExpressionLog, "llvm::GlobalObject -> StringOp"); auto Op = Builder.create(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(G)) { revng_log(ExpressionLog, "llvm::Function -> UseOp"); auto Function = emitFunctionDeclaration(F); rc_return Builder.create(SurroundingLocation, Function.getFunctionType(), Function.getSymNameAttr()); } revng_abort("Unsupported global object kind"); } if (auto It = ValueMapping.find(V); It != ValueMapping.end()) { revng_log(ExpressionLog, ""); mlir::Value Value = It->second.Value; if (It->second.TakeAddressOnUse) { Value = Builder.create(SurroundingLocation, makePointerType(Value.getType()), Value); } rc_return Value; } if (auto A = llvm::dyn_cast(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(SurroundingLocation, makePointerType(Value.getType()), Value); } rc_return emitImplicitCast(SurroundingLocation, Value, It->second.CastType); } if (auto U = llvm::dyn_cast(V)) { revng_log(ExpressionLog, "llvm::UndefValue -> UndefOp"); mlir::Type Type = importLLVMType(U->getType()); rc_return Builder.create(SurroundingLocation, Type); } if (auto C = llvm::dyn_cast(V)) { revng_log(ExpressionLog, "llvm::ConstantInt -> ImmediateOp"); const llvm::IntegerType *T = llvm::cast(C->getType()); rc_return Builder.create(SurroundingLocation, importLLVMIntegerType(T), C->getZExtValue()); } if (auto N = llvm::dyn_cast(V)) { revng_log(ExpressionLog, "llvm::ConstantPointerNull -> ImmediateOp"); auto Op = Builder.create(SurroundingLocation, getIntptrType(), /*Value=*/0); LoggerIndent Indent(ExpressionLog); rc_return emitCast(SurroundingLocation, Op, getVoidPointerType()); } if (auto E = llvm::dyn_cast(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(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(Loc, Type, It->second); rc_return emitImplicitCast(Loc, Value, importLLVMType(I->getType())); } revng_assert(not llvm::isa(I->getArraySize())); revng_abort("Non-constant alloca is not supported."); } if (auto I = llvm::dyn_cast(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(Loc, PointerType, Pointer, CastKind::Bitcast); revng_log(ExpressionLog, "IndirectionOp"); rc_return Builder.create(Loc, ValueType, Pointer); } if (auto I = llvm::dyn_cast(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(Loc, makePointerType(Value.getType()), Pointer, CastKind::Bitcast); revng_log(ExpressionLog, "IndirectionOp"); mlir::Value Assignee = Builder.create(Loc, Value.getType(), Pointer); revng_log(ExpressionLog, "AssignOp"); rc_return Builder.create(Loc, Value.getType(), Assignee, Value); } if (auto I = llvm::dyn_cast(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(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(Loc, Type, Lhs, Rhs); case Operators::Sub: revng_log(ExpressionLog, "SubOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::Mul: revng_log(ExpressionLog, "MulOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::SDiv: case Operators::UDiv: revng_log(ExpressionLog, "DivOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::SRem: case Operators::URem: revng_log(ExpressionLog, "RemOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::Shl: revng_log(ExpressionLog, "ShiftLeftOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::LShr: case Operators::AShr: revng_log(ExpressionLog, "ShiftRightOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::And: revng_log(ExpressionLog, "BitwiseAndOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::Or: revng_log(ExpressionLog, "BitwiseOrOp"); return emitExpr(Loc, Type, Lhs, Rhs); case Operators::Xor: revng_log(ExpressionLog, "BitwiseXorOp"); return emitExpr(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(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(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(Loc, Type, Lhs, Rhs); case ICMP_NE: revng_log(ExpressionLog, "CmpNeOp"); return emitExpr(Loc, Type, Lhs, Rhs); case ICMP_SGT: case ICMP_UGT: revng_log(ExpressionLog, "CmpGtOp"); return emitExpr(Loc, Type, Lhs, Rhs); case ICMP_SGE: case ICMP_UGE: revng_log(ExpressionLog, "CmpGeOp"); return emitExpr(Loc, Type, Lhs, Rhs); case ICMP_SLT: case ICMP_ULT: revng_log(ExpressionLog, "CmpLtOp"); return emitExpr(Loc, Type, Lhs, Rhs); case ICMP_SLE: case ICMP_ULE: revng_log(ExpressionLog, "CmpLeOp"); return emitExpr(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(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(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(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(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(*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(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 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(Loc, T, Argument); } else { Argument = emitImplicitCast(Loc, Argument, T); } Arguments.push_back(Argument); } revng_log(ExpressionLog, "CallOp"); rc_return Builder.create(Loc, CallType.getReturnType(), Function, Arguments); } if (auto I = llvm::dyn_cast(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(True.getType()); auto FalseType = mlir::cast(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(Loc, ResultType, Condition, True, False); } if (auto I = llvm::dyn_cast(V)) { auto Alloca = llvm::cast(I->getPointerOperand()); auto It = AllocaMapping.find(Alloca); revng_assert(It != AllocaMapping.end()); auto AT = mlir::cast(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(Loc, getIntptrType(), Index1); rc_return emitExpr(Loc, PT, Operand, Immediate); } if (auto I = llvm::dyn_cast(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(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 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 OpT emitLocalDeclaration(mlir::Location Loc, ArgsT &&...Args) { mlir::OpBuilder::InsertionGuard Guard(Builder); Builder.setInsertionPointToEnd(LocalDeclarationBlock); OpT Op = Builder.create(Loc, std::forward(Args)...); return Op; } void emitAssignLabel(mlir::Value Label, mlir::Location Loc) { Builder.create(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>(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(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(Loc, Iterator->second.Label); } // Returns: { RequiresFullExpression, RequiresIndirection } std::pair 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(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 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(&I)) { const auto *Size = Alloca->getArraySize(); // Non-constant alloca is not supported: revng_assert(not Size or llvm::isa(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(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(&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(Loc); mlir::Value Local; emitExpressionTreeImpl(Op.getExpression(), [&]() { mlir::Value Initializer = emitExpression(Call, Loc); mlir::Type Type = Initializer.getType(); Local = emitLocalDeclaration(Loc, Type); return Builder.create(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(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(Terminal) or not HasGotoMarker); mlir::Location TerminalLoc = getLocation(Terminal); if (llvm::isa(Terminal)) { // Do nothing. } else if (auto *Return = llvm::dyn_cast(Terminal)) { auto Op = Builder.create(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(ReturnValue.getType())) { ReturnValue = emitCast(TerminalLoc, ReturnValue, makePointerType(AT.getElementType()), CastKind::Decay); ReturnValue = emitCast(TerminalLoc, ReturnValue, makePointerType(LLVMReturnType), CastKind::Bitcast); ReturnValue = Builder.create(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(TerminalLoc, FuncReturnType, ReturnValue); } return ReturnValue; }); } } else if (auto *Branch = llvm::dyn_cast(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(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(Terminal)) { llvm::SmallVector CaseValues; CaseValues.reserve(Switch->getNumCases()); for (auto CH : Switch->cases()) CaseValues.push_back(CH.getCaseValue()->getZExtValue()); auto Op = Builder.create(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 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 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(*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 SymbolMapping; // Maps LLVM object to a string literal description. Used for caching // successful string literal detection results. llvm::DenseMap StringMapping; // Maps helper name to Clift function op. Each helper function declaration is // only emitted once. llvm::DenseMap 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 ArgumentMapping; // Maps LLVM IR basic block to a Clift import state for that block. llvm::DenseMap 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 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 ValueMapping; }; } // namespace std::unique_ptr Clifter::make(mlir::ModuleOp Module, const model::Binary &Model) { return std::make_unique(Module, Model); }