// // 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/IR/DataLayout.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/InstIterator.h" #include "llvm/Support/Casting.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/CliftAttributes.h" #include "revng/Clift/CliftDialect.h" #include "revng/Clift/CliftTypes.h" #include "revng/CliftImportModel/ImportModel.h" #include "revng/Clifter/Clifter.h" #include "revng/LocalVariables/LocalVariableHelpers.h" #include "revng/Model/Architecture.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/IRHelpers.h" #include "revng/Support/Identifier.h" 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; 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; const llvm::DataLayout *DataLayout; /* 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; 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), DataLayout(nullptr) { 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; } IntegerType getIntegerType(uint64_t Size, IntegerKind Kind = IntegerKind::Generic) const { return IntegerType::get(Context, Kind, Size); } uint64_t getModelPointerSize() const { return model::Architecture::getPointerSize(Model.Architecture()); } mlir::Type getModelPointerType(mlir::Type ElementType) const { return PointerType::get(ElementType, getModelPointerSize()); } uint64_t getPointerSize() const { revng_assert(DataLayout->getPointerSizeInBits() % 8 == 0); return DataLayout->getPointerSize(); } mlir::Type getPointerType(mlir::Type ElementType) const { return PointerType::get(ElementType, getPointerSize()); } mlir::Type getVoidType() const { return VoidType::get(Context); } mlir::Type getVoidPointerType() const { return getPointerType(getVoidType()); } mlir::Type getIntptrType() { return getIntegerType(getPointerSize()); } clift::ValueType getConstCharType() { return clift::IntegerType::get(Context, IntegerKind::Number, 1, /*IsConst=*/true); } clift::ArrayType getConstCharArrayType(uint64_t NumElements) { return clift::ArrayType::get(getConstCharType(), NumElements); } clift::ValueType getCharType() { return clift::IntegerType::get(Context, IntegerKind::Number, 1, /*IsConst=*/false); } clift::ArrayType getCharArrayType(uint64_t NumElements) { return clift::ArrayType::get(getCharType(), NumElements); } template 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 TypeT importType(const ModelTypeT &Type) { return mlir::cast(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(); } std::string getOpaqueTypeHandle(uint64_t ByteSize) { return pipeline::locationString(revng::ranks::OpaqueType, ByteSize); } clift::StructType importOpaqueStruct(uint64_t NumBytes) { std::string Handle = getOpaqueTypeHandle(NumBytes); auto NameAttr = makeNameAttr(Context, Handle); auto CommentAttr = makeCommentAttr(Context, Handle); auto Attrs = llvm::ArrayRef{}; auto Def = clift::StructAttr::get(Context, Handle, NameAttr, CommentAttr, NumBytes, {}, Attrs); return clift::StructType::get(Def); } clift::StructType importOpaqueStruct(const llvm::ArrayType *Array) { const auto *ElementType = Array->getElementType(); revng_assert(ElementType->isIntegerTy()); revng_assert(ElementType->getIntegerBitWidth() == 8); uint64_t NumBytes = Array->getNumElements() * (ElementType->getIntegerBitWidth() / 8); return importOpaqueStruct(NumBytes); } mlir::Type 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 importOpaqueStruct(T); if (auto *S = llvm::dyn_cast(Type)) { const auto *StructLayout = DataLayout->getStructLayout(S); uint64_t StructByteSize = StructLayout->getSizeInBytes(); uint64_t PreviousOffsetInBits = 0ULL; for (auto [Index, FieldType] : llvm::enumerate(S->elements())) { revng_assert(PreviousOffsetInBits % 8 == 0); uint64_t OffsetInBits = StructLayout->getElementOffsetInBits(Index); revng_assert(OffsetInBits % 8 == 0); uint64_t FieldSizeInBits = DataLayout->getTypeSizeInBits(FieldType); revng_assert(FieldSizeInBits % 8 == 0, ("StructType with a field whose size is not a multiple of " " 8 bits. Field index: " + std::to_string(Index) + ". FieldSizeInBits: " + std::to_string(FieldSizeInBits)) .c_str()); PreviousOffsetInBits = OffsetInBits + FieldSizeInBits; } revng_assert(PreviousOffsetInBits % 8 == 0); uint64_t Alignment = StructLayout->getAlignment().value(); uint64_t ByteSize = PreviousOffsetInBits / 8; uint64_t NumUnalignedBytes = ByteSize % Alignment; uint64_t AlignedByteSize = NumUnalignedBytes ? (ByteSize - NumUnalignedBytes + Alignment) : ByteSize; revng_assert(AlignedByteSize == StructByteSize); revng_assert(StructByteSize); return importOpaqueStruct(StructByteSize); } revng_abort("Unsupported LLVM type"); } //===------------------------ LLVM global import ------------------------===// uint64_t getConstantInt(const llvm::Value *Value) { return llvm::cast(Value)->getZExtValue(); } 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. mlir::Type importHelperReturnType(const llvm::Function *HelperFunction) { const llvm::FunctionType *HelperType = HelperFunction->getFunctionType(); const llvm::Type *ReturnType = HelperType->getReturnType(); auto Struct = llvm::dyn_cast(ReturnType); if (not Struct) return importLLVMType(ReturnType); revng_assert(Struct->isLiteral()); llvm::StringRef HelperName = HelperFunction->getName(); auto Location = pipeline::location(revng::ranks::HelperStructType, HelperName.str()); llvm::SmallVector Fields; Fields.reserve(Struct->getNumElements()); // For this artificial struct we have to use the byte size and the field // offsets provided by the LLVM DataLayout, because that's how LLVM reasons // about this struct anyway, and that's how all the GEPs that index into an // alloca with this StructType will reason about. Using a size and offsets // will break semantics, because the semantic used by all of LLVM's analysis // and transformations is the semantic dictated by the DataLayout. const auto &DataLayout = HelperFunction->getParent()->getDataLayout(); const auto *StructLayout = DataLayout.getStructLayout(Struct); uint64_t StructByteSize = StructLayout->getSizeInBytes(); uint64_t PreviousOffsetInBits = 0ULL; for (auto [Index, T] : llvm::enumerate(Struct->elements())) { mlir::Type FieldType = importLLVMType(T); revng_assert(PreviousOffsetInBits % 8 == 0); uint64_t FieldOffsetInBits = StructLayout->getElementOffsetInBits(Index); revng_assert(FieldOffsetInBits % 8 == 0); std::string FieldName = getHelperStructFieldName(Index); std::string FieldHandle = Location .extend(revng::ranks::HelperStructField, FieldName) .toString(); Fields.push_back(FieldAttr::get(Context, FieldHandle, makeNameAttr(Context, FieldHandle, FieldName), makeCommentAttr(Context, FieldHandle), FieldOffsetInBits / 8, FieldType)); uint64_t FieldSizeInBits = DataLayout.getTypeSizeInBits(T); revng_assert(FieldSizeInBits % 8 == 0, (HelperFunction->getName().str() + " returns a StructType with a field whose size " "is not a multiple of 8 bits. Field index: " + std::to_string(Index) + ". FieldSizeInBits: " + std::to_string(FieldSizeInBits)) .c_str()); PreviousOffsetInBits = FieldOffsetInBits + FieldSizeInBits; } revng_assert(PreviousOffsetInBits % 8 == 0); uint64_t Alignment = StructLayout->getAlignment().value(); uint64_t ByteSize = PreviousOffsetInBits / 8; uint64_t NumUnalignedBytes = ByteSize % Alignment; uint64_t AlignedByteSize = NumUnalignedBytes ? (ByteSize - NumUnalignedBytes + Alignment) : ByteSize; revng_assert(AlignedByteSize == StructByteSize); revng_assert(StructByteSize); 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), makeCommentAttr(Context, Handle), StructByteSize, Fields, {}); return StructType::get(Context, Definition); } // 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 *HelperFunction) { const llvm::FunctionType *HelperType = HelperFunction->getFunctionType(); revng_assert(not HelperType->isVarArg()); mlir::Type ReturnType = importHelperReturnType(HelperFunction); llvm::SmallVector ParameterTypes; ParameterTypes.reserve(HelperType->getNumParams()); for (const llvm::Type *T : HelperType->params()) ParameterTypes.push_back(importLLVMType(T)); auto Handle = pipeline::locationString(revng::ranks::HelperFunction, HelperFunction->getName().str()); return FunctionType::get(Context, Handle, makeNameAttr(Context, Handle), makeCommentAttr(Context, Handle), ReturnType, ParameterTypes, {}); } 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)); }); } FunctionOp getHelperFunction(llvm::StringRef HelperName, clift::FunctionType FunctionType) { return emitHelperFunctionDeclaration(mlir::UnknownLoc::get(Context), HelperName, FunctionType); } template clift::FunctionOp emitModelFunctionDeclaration(const llvm::Function *F, const FunctionT &MF, const RankT &Rank, ArgsT &&...Args) { model::UpcastableType Prototype = getPrototype(MF); auto FunctionType = importType(*Prototype); auto Handle = pipeline::locationString(Rank, std::forward(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(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(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; String.Type = getConstCharArrayType(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; } 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; }; 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 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 LLVM CallInst that returns SPTAR to the Clift LocalVariableOp that // holds the return value. // All LLVM uses of the CallInst use it as a pointer, as if it was pointing to // the storage holding the return value. This is a hack, and should be avoided // in the future. Until then, for the generation of uses of the SPTAR call in // Clift, the address of the LocalVariablOp must be taken, to preserve // semantic. llvm::DenseMap SPTARCallVariableMapping; 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 NullReset = ScopedExchange(C.DataLayout, &F->getParent()->getDataLayout()); 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 mlir::Value emitExpr(mlir::Location Loc, ArgsT &&...Args) { return Builder.create(Loc, std::forward(Args)...); } template mlir::Value emitCast(mlir::Location Loc, mlir::Value Value, mlir::Type TargetType) { if (Value.getType() != TargetType) { OpT X = Builder.create(Loc, TargetType, Value); if constexpr (std::is_same_v) { auto ValueSize = unwrapped_cast(Value.getType()) .getObjectSize(); auto TargetSize = unwrapped_cast(TargetType).getObjectSize(); revng_assert(ValueSize == TargetSize); } return X; } 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 emitImplicitBitcast(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(SourceType); if (not SourceT) 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, IntegerKind Kind, auto ApplyOperation, std::same_as auto... Operands) { auto ConvertToKind = [&](mlir::Value &Value, IntegerKind Kind) { auto UnderlyingType = getUnderlyingIntegerType(Value.getType()); revng_assert(UnderlyingType); uint64_t Size = UnderlyingType.getSize(); Value = emitCast(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(Loc, Operand, NewType); if (Size < SrcSize) return emitCast(Loc, Operand, NewType); return emitCast(Loc, Operand, NewType); }; return emitIntegerOp(Loc, Kind, EmitCast, Operand); } 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(emitImplicitBitcast(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, C.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 CommentAttr = makeCommentAttr(Context, Handle); // TODO: should we add something explicitly identifying this as a helper? llvm::ArrayRef Attributes = {}; auto FunctionType = clift::FunctionType::get(Context, Handle, NameAttr, CommentAttr, ReturnType, ParameterTypes, Attributes); return emitHelperCall(Loc, C.getHelperFunction(HelperName, FunctionType), Arguments); } RecursiveCoroutine 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(UseBegin->getUser())); revng_assert(std::next(UseBegin) == UseEnd); auto T = mlir::dyn_cast(Function.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 = emitImplicitBitcast(Loc, Value, F.getType()); Initializers.push_back(Value); } rc_return Builder.create(Loc, T, Initializers); } 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); mlir::Location Loc = C.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, C.emitHelperFunctionDeclaration(Callee), Arguments); } RecursiveCoroutine emitExpression(const llvm::Value *V, mlir::Location SurroundingLocation) { LoggerIndent Indent(ExpressionLog); if (auto G = llvm::dyn_cast(V)) { if (const StringLiteral *String = C.detectStringLiteral(G)) { revng_log(ExpressionLog, "llvm::GlobalObject -> StringOp"); auto Op = Builder.create(SurroundingLocation, String->Type, std::move(String->Data)); auto Type = C.getPointerType(String->Type.getElementType()); rc_return emitCast(SurroundingLocation, Op, Type); } if (const auto *F = llvm::dyn_cast(G)) { revng_log(ExpressionLog, "llvm::Function -> UseOp"); auto Function = C.emitFunctionDeclaration(F); rc_return Builder.create(SurroundingLocation, Function.getFunctionType(), Function.getSymNameAttr()); } revng_abort("Unsupported global object kind"); } 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; bool TakeAddressOnUse = It->second.TakeAddressOnUse; mlir::Type ArgumentUseType = It->second.CastType; if (TakeAddressOnUse) { uint64_t UseSize = unwrapped_cast(ArgumentUseType) .getObjectSize(); revng_assert(C.getModelPointerSize() == UseSize); Value = Builder .create(SurroundingLocation, C.getModelPointerType(Value.getType()), Value); } rc_return emitImplicitBitcast(SurroundingLocation, Value, It->second.CastType); } if (auto U = llvm::dyn_cast(V)) { revng_log(ExpressionLog, "llvm::UndefValue -> UndefOp"); mlir::Type Type = C.importLLVMType(U->getType()); rc_return Builder.create(SurroundingLocation, Type); } if (auto I = llvm::dyn_cast(V)) { revng_log(ExpressionLog, "llvm::ConstantInt -> ImmediateOp"); const llvm::IntegerType *T = llvm::cast(I->getType()); rc_return Builder.create(SurroundingLocation, C.importLLVMIntegerType(T), I->getZExtValue()); } if (auto N = llvm::dyn_cast(V)) { revng_log(ExpressionLog, "llvm::ConstantPointerNull -> ImmediateOp"); auto Op = Builder.create(SurroundingLocation, C.getIntptrType(), /*Value=*/0); LoggerIndent Indent(ExpressionLog); rc_return emitCast(SurroundingLocation, Op, C.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; revng_log(ExpressionLog, "llvm::AllocaInst"); LoggerIndent Indent(ExpressionLog); auto It = AllocaMapping.find(I); revng_assert(It != AllocaMapping.end()); auto Type = C.getPointerType(It->second.getType()); auto Value = Builder.create(Loc, Type, It->second); rc_return emitImplicitBitcast(Loc, Value, C.importLLVMType(I->getType())); } if (auto I = llvm::dyn_cast(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.getPointerType(ValueType); Pointer = Builder.create(Loc, PointerType, Pointer); 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 = 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(Loc, C.getPointerType(Value.getType()), Pointer); 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 = 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(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(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; 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(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(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, C.getIntptrType()); Rhs = emitCast(Loc, Rhs, C.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 = 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(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(Loc, Operand, C.getIntptrType()); if (uint64_t S = GetIntegerSize(I->getDestTy()); S != C.getPointerSize()) Operand = emitIntegerCast(Loc, Operand, S); rc_return Operand; case Operators::IntToPtr: if (uint64_t S = GetIntegerSize(I->getSrcTy()); S != C.getPointerSize()) Operand = emitIntegerCast(Loc, Operand, C.getPointerSize()); rc_return emitCast(Loc, Operand, C.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 = C.getLocation(I); if (auto It = SPTARCallVariableMapping.find(I); It != SPTARCallVariableMapping.end()) { revng_log(ExpressionLog, ""); mlir::Value Value = It->second; Value = Builder.create(SurroundingLocation, C.getPointerType(Value.getType()), Value); rc_return Value; } // 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(*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, C.getPointerType(FunctionType)); } Function = emitCast(Loc, Function, C.getPointerType(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 = emitImplicitBitcast(Loc, Argument, C.getModelPointerType(T)); Argument = Builder.create(Loc, T, Argument); } else { Argument = emitImplicitBitcast(Loc, Argument, T); } Arguments.push_back(Argument); } revng_log(ExpressionLog, "CallOp"); mlir::Value Result = Builder.create(Loc, CallType.getReturnType(), Function, Arguments); if (Layout.returnMethod() == abi::FunctionType::ReturnMethod::Scalar) { Result = emitImplicitBitcast(SurroundingLocation, Result, C.importLLVMType(I->getType())); } rc_return Result; } if (auto I = llvm::dyn_cast(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 = emitImplicitBitcast(Loc, True, ResultType); False = emitImplicitBitcast(Loc, False, ResultType); } rc_return Builder.create(Loc, ResultType, Condition, True, False); } if (auto *GEP = llvm::dyn_cast(V)) { const llvm::Value *GEPPointerOp = GEP->getPointerOperand(); mlir::Location Loc = C.getLocation(GEP); if (GEP->getNumIndices() == 1 and GEP->getSourceElementType()->isIntegerTy(8)) { auto UnsignedCharType = C.getIntegerType(1, IntegerKind::Unsigned); auto PointerType = C.getPointerType(UnsignedCharType); llvm::Value *GEPIndex = GEP->idx_begin()->get(); mlir::Value Index = rc_recur emitExpression(GEPIndex, Loc); auto IndexType = cast(Index.getType()); auto IntptrType = cast(C.getIntptrType()); auto Cmp = IntptrType.getObjectSize() <=> IndexType.getObjectSize(); if (Cmp < 0) Index = emitCast(Loc, Index, C.getIntptrType()); else if (Cmp > 0) Index = emitCast(Loc, Index, C.getIntptrType()); mlir::Value Pointer = emitCast(Loc, rc_recur emitExpression(GEPPointerOp, Loc), PointerType); rc_return emitExpr(Loc, PointerType, Pointer, Index); } } if (auto I = llvm::dyn_cast(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(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) { auto Label = emitLocalDeclaration(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(Loc, Iterator->second.Label); } bool isCallToSPTAR(const llvm::CallInst *Call) { if (not Call->hasMetadata(PrototypeMDName)) return false; const auto *ModelCallType = getCallSitePrototype(C.Model, Call); auto Layout = abi::FunctionType::Layout::make(*ModelCallType); namespace ReturnMethod = abi::FunctionType::ReturnMethod; return Layout.hasSPTAR(); } // 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, 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 *A = llvm::dyn_cast(&I)) { const auto *NumElements = A->getArraySize(); // Non-constant alloca is not supported: revng_assert(not NumElements or llvm::isa(NumElements)); std::optional Handle; mlir::Type Type; if (hasStackFrameMetadata(A)) { auto StackType = ModelFunction.stackFrameType(); revng_assert(StackType); Type = cast(C.importType(*StackType)); Handle = pipeline::locationString(revng::ranks::StackFrameVariable, ModelFunction.Entry()); } else { revng_assert(*A->getAllocationSizeInBits(*C.DataLayout) % 8 == 0); llvm::Type *Allocated = A->getAllocatedType(); revng_assert(Allocated->isSized()); if (not Allocated->isArrayTy() and not A->isArrayAllocation()) { Type = C.importLLVMType(Allocated); } else { uint64_t FieldBitSize = C.DataLayout->getTypeSizeInBits(Allocated); revng_assert(FieldBitSize % 8 == 0); uint64_t ElementSizeInBytes = FieldBitSize / 8; const auto *NumElements = cast(A->getArraySize()); uint64_t NBytes = NumElements->getZExtValue() * ElementSizeInBytes; auto *ByteType = llvm::IntegerType::get(A->getContext(), 8); auto *ByteArrayType = llvm::ArrayType::get(ByteType, NBytes); Type = C.importLLVMType(ByteArrayType); } } mlir::Location Loc = C.getLocation(A); auto Op = emitLocalDeclaration(Loc, 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(A, Op); revng_assert(Inserted); // Create a clift.require operation to indicate the position dominating // all uses of this local variable. This prevents the local variable // from being later moved past this point during scope tightening. Builder.create(Loc, Op); 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 (isCallToSPTAR(Call)) { mlir::Location Loc = C.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 [_, Inserted] = SPTARCallVariableMapping.try_emplace(Call, Local); revng_assert(Inserted); 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(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 = C.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 = 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.getPointerType(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, C.getPointerType(AT.getElementType())); ReturnValue = // emitCast(TerminalLoc, ReturnValue, C.getPointerType(LLVMReturnType)); ReturnValue = Builder.create(TerminalLoc, LLVMReturnType, ReturnValue); } if (FunctionLayout.hasSPTAR()) { // TODO: This may happen when the pointer size of the Model doesn't // match the pointer size on LLVM IR, due to mismatching DataLayout. // SPTAR functions return model-pointer-sized integers, with the // semantic is to actually return the pointee by copy. // Given that the return value is model-pointer-sized, it's size // doesn't necessarily match the pointer size in LLVM's DataLayout. // Until we don't solve the broader issue of LLVM's DataLayout // mismatching the pointer size of the input binary and the Model, // we'll have to deal with this corner case. // Another option would be to change SPTAR function to return // llvm-pointer-sized integers or even LLVM's pointers, instead of // model-pointer-sized integers. uint64_t LLVMPointerSize = unwrapped_cast(LLVMReturnType) .getObjectSize(); uint64_t ModelPointerSize = C.getModelPointerSize(); uint64_t OperandSize = unwrapped_cast(ReturnValue .getType()) .getObjectSize(); revng_assert(ModelPointerSize == OperandSize); if (ModelPointerSize != LLVMPointerSize) ReturnValue = emitIntegerCast(TerminalLoc, ReturnValue, LLVMPointerSize); } // Emit an implicit cast to the required return type if necessary: ReturnValue = emitImplicitBitcast(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(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(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(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; } void initializeArgumentMapping(const llvm::Function *F) { 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, getBodyBlock().addArgument(T, Function->getLoc()), /*TakeAddressOnUse=*/isByAddressParameter(L), /*CastType=*/C.importLLVMType(A.getType())); } } mlir::Block &getBodyBlock() { revng_assert(Function); revng_assert(Function.getBody().hasOneBlock()); return Function.getBody().getBlocks().front(); } clift::FunctionOp import(const llvm::Function *F) { revng_assert(Function.getBody().empty()); mlir::Block &BodyBlock = Function.getBody().emplaceBlock(); Builder.setInsertionPointToEnd(&BodyBlock); initializeArgumentMapping(F); // 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)); // 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::make(mlir::ModuleOp Module, const model::Binary &Model) { return std::make_unique(Module, Model); }