diff --git a/CMakeLists.txt b/CMakeLists.txt index f739e7998..5404af14c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,8 +219,36 @@ foreach( list(APPEND HELPER_MODULE_LIST "${CMAKE_BINARY_DIR}/share/revng/${OUTPUT}") endforeach() - list(APPEND HELPER_MODULE_LIST - "${CMAKE_INSTALL_PREFIX}/lib/libtinycode-helpers-${ARCH}.bc") + # Annotate libtcg-helpers-*.bc + + set(LIBTCG_HELPERS + "${CMAKE_INSTALL_PREFIX}/share/libtcg/libtcg-helpers-${ARCH}.bc") + set(LIBTCG_HELPERS_ANNOTATED + "${CMAKE_BINARY_DIR}/share/revng/libtcg-helpers-annotated-${ARCH}.bc") + add_custom_command( + OUTPUT "${LIBTCG_HELPERS_ANNOTATED}" + DEPENDS "${LIBTCG_HELPERS}" revng-all-binaries + COMMAND + ./bin/revng opt -sroa -instsimplify -cpu-loop-exit + -analyze-helper-arguments -fix-helpers + -fix-helpers-architecture="${ARCH}" "${LIBTCG_HELPERS}" -o + "${LIBTCG_HELPERS_ANNOTATED}") + add_custom_target("annotated-libtcg-helpers-${ARCH}" ALL + DEPENDS "${LIBTCG_HELPERS_ANNOTATED}") + + # Drop all the functions *not* tagged with revng_inline + set(LIBTCG_HELPERS_ANNOTATED_SLIM + "${CMAKE_BINARY_DIR}/share/revng/libtcg-helpers-annotated-slim-${ARCH}.bc" + ) + add_custom_command( + OUTPUT "${LIBTCG_HELPERS_ANNOTATED_SLIM}" + DEPENDS "${LIBTCG_HELPERS_ANNOTATED}" revng-all-binaries + COMMAND ./bin/revng opt -slim-down-helpers-module -globaldce + "${LIBTCG_HELPERS_ANNOTATED}" -o "${LIBTCG_HELPERS_ANNOTATED_SLIM}") + add_custom_target("annotated-libtcg-helpers-slim-${ARCH}" ALL + DEPENDS "${LIBTCG_HELPERS_ANNOTATED_SLIM}") + list(APPEND HELPER_MODULE_LIST "${LIBTCG_HELPERS_ANNOTATED_SLIM}") + endforeach() # Produce well-known-models diff --git a/include/revng/BasicAnalyses/GeneratedCodeBasicInfo.h b/include/revng/BasicAnalyses/GeneratedCodeBasicInfo.h index 7f3027f6c..33bf98a0e 100644 --- a/include/revng/BasicAnalyses/GeneratedCodeBasicInfo.h +++ b/include/revng/BasicAnalyses/GeneratedCodeBasicInfo.h @@ -163,9 +163,7 @@ public: const ProgramCounterHandler *programCounterHandler() { if (not PCH) { llvm::Module *M = RootFunction->getParent(); - using namespace model::Architecture; - auto Architecture = toLLVMArchitecture(Binary->Architecture()); - PCH = ProgramCounterHandler::fromModule(Architecture, M); + PCH = ProgramCounterHandler::fromModule(Binary->Architecture(), M); } return PCH.get(); @@ -300,9 +298,7 @@ public: } MetaAddress fromPC(uint64_t PC) const { - using namespace model::Architecture; - auto Architecture = toLLVMArchitecture(Binary->Architecture()); - return MetaAddress::fromPC(Architecture, PC); + return MetaAddress::fromPC(Binary->Architecture(), PC); } llvm::Function *root() { diff --git a/include/revng/HelperArgumentsAnalysis/Annotation.h b/include/revng/HelperArgumentsAnalysis/Annotation.h new file mode 100644 index 000000000..12d81771a --- /dev/null +++ b/include/revng/HelperArgumentsAnalysis/Annotation.h @@ -0,0 +1,51 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include + +#include "llvm/ADT/DenseSet.h" + +namespace llvm { +class MDNode; +class LLVMContext; +class User; +class Instruction; +} // namespace llvm + +namespace aua { + +using OffsetAndSize = std::pair; + +class Annotation { +private: + static constexpr const char *MetadataKind = "revng.csua"; + +public: + using OffsetAndSizeSet = llvm::DenseSet; + +public: + bool Escapes = false; + OffsetAndSizeSet Reads; + OffsetAndSizeSet Writes; + +public: + ~Annotation() {} + bool operator==(const Annotation &) const = default; + +public: + llvm::MDNode &serializeToMetadata(llvm::LLVMContext &Context) const; + + void serialize(llvm::User &ToAnnotate); + +public: + static bool isAnnotated(llvm::Instruction &I); + static std::optional deserialize(llvm::User &ToAnnotate); + static Annotation deserializeFromMetadata(llvm::LLVMContext &Context, + llvm::MDNode &MD); +}; + +} // namespace aua diff --git a/include/revng/HelperArgumentsAnalysis/CPULoopExitPass.h b/include/revng/HelperArgumentsAnalysis/CPULoopExitPass.h new file mode 100644 index 000000000..4d4d40df1 --- /dev/null +++ b/include/revng/HelperArgumentsAnalysis/CPULoopExitPass.h @@ -0,0 +1,16 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/Pass.h" + +class CPULoopExitPass : public llvm::ModulePass { +public: + static char ID; + + CPULoopExitPass() : llvm::ModulePass(ID) {} + + bool runOnModule(llvm::Module &M) override; +}; diff --git a/include/revng/Lift/CPUStateAccessAnalysisPass.h b/include/revng/Lift/CPUStateAccessAnalysisPass.h deleted file mode 100644 index 4bcf2f294..000000000 --- a/include/revng/Lift/CPUStateAccessAnalysisPass.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include - -#include "llvm/Pass.h" -#include "llvm/Support/raw_ostream.h" - -#include "revng/Lift/CSVOffsets.h" -#include "revng/Support/Assert.h" - -namespace llvm { -class Instruction; -} - -class VariableManager; - -/// LLVM pass to analyze the access patterns to the CPU State Variable -class CPUStateAccessAnalysisPass : public llvm::ModulePass { -public: - using AccessOffsetMap = std::map; - -private: - const bool Lazy; - VariableManager *Variables = nullptr; - -public: - static char ID; - -public: - CPUStateAccessAnalysisPass() : - llvm::ModulePass(ID), Lazy(false), Variables(nullptr){}; - - CPUStateAccessAnalysisPass(VariableManager *VM, bool IsLazy = false) : - llvm::ModulePass(ID), Lazy(IsLazy), Variables(VM){}; - -public: - virtual bool runOnModule(llvm::Module &TheModule) override; -}; diff --git a/include/revng/Lift/IRAnnotators.h b/include/revng/Lift/IRAnnotators.h index e22b22834..f63508ace 100644 --- a/include/revng/Lift/IRAnnotators.h +++ b/include/revng/Lift/IRAnnotators.h @@ -15,5 +15,3 @@ void createSelfReferencingDebugInfo(llvm::Module *M, llvm::StringRef SourcePath, llvm::AssemblyAnnotationWriter *InnerAAW); void createPTCDebugInfo(llvm::Module *M, llvm::StringRef SourcePath); -void createOriginalAssemblyDebugInfo(llvm::Module *M, - llvm::StringRef SourcePath); diff --git a/include/revng/Lift/LibTcg.h b/include/revng/Lift/LibTcg.h new file mode 100644 index 000000000..8d9b77249 --- /dev/null +++ b/include/revng/Lift/LibTcg.h @@ -0,0 +1,82 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "qemu/libtcg/libtcg.h" + +#include "revng/Model/Architecture.h" + +class LibTcg { +public: + class TranslationBlock { + private: + LibTcgInterface &Interface; + LibTcgContext &Context; + LibTcgTranslationBlock Block; + + public: + TranslationBlock(LibTcgInterface &Interface, + LibTcgContext &Context, + LibTcgTranslationBlock Block) : + Interface(Interface), Context(Context), Block(Block) {} + + ~TranslationBlock() { + Interface.translation_block_destroy(&Context, Block); + } + + public: + LibTcgTranslationBlock &operator*() { return Block; } + const LibTcgTranslationBlock &operator*() const { return Block; } + LibTcgTranslationBlock *operator->() { return &Block; } + const LibTcgTranslationBlock *operator->() const { return &Block; } + }; + +private: + void *LibraryHandle = nullptr; + LibTcgInterface Interface; + LibTcgContext *Context = nullptr; + LibTcgArchInfo ArchInfo; + std::map GlobalNames; + +public: + ~LibTcg(); + +public: + static LibTcg get(model::Architecture::Values Architecture); + +public: + const LibTcgArchInfo &archInfo() const { return ArchInfo; } + + uint8_t *envPointer() { return Interface.env_ptr(Context); } + + TranslationBlock translateBlock(const unsigned char *Buffer, + size_t Size, + uint64_t VirtualAddress, + uint32_t TranslateFlags) { + return TranslationBlock(Interface, + *Context, + Interface.translate_block(Context, + Buffer, + Size, + VirtualAddress, + TranslateFlags)); + } + + void dumpInstructionToBuffer(LibTcgInstruction *Instruction, + char *Buffer, + size_t Size) { + Interface.dump_instruction_to_buffer(Instruction, Buffer, Size); + } + + const char *instructionName(LibTcgOpcode Opcode) { + return Interface.get_instruction_name(Opcode); + } + + LibTcgHelperInfo helperInfo(LibTcgInstruction *InstructionInfo) { + return Interface.get_helper_info(InstructionInfo); + } + + const auto &globalNames() const { return GlobalNames; } +}; diff --git a/include/revng/Lift/PTCDump.h b/include/revng/Lift/PTCDump.h deleted file mode 100644 index d8d558090..000000000 --- a/include/revng/Lift/PTCDump.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include - -#include "revng/Support/MetaAddress.h" - -#include "ptc.h" - -/// Write to a stream the string representation of the PTC instruction with the -/// specified index within the instruction list. -/// -/// \param Result the output stream. -/// \param Instructions the instruction list. -/// \param Index the index of the target instruction in the instruction list. -/// -/// \return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise. -int dumpInstruction(std::ostream &Result, - PTCInstructionList *Instructions, - unsigned Index); - -/// Write to a stream all the instructions in an instruction list. -/// -/// \param Result the output stream. -/// \param Instructions the instruction list. -/// -/// \return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise. -int dumpTranslation(MetaAddress VirtualAddress, - std::ostream &Result, - PTCInstructionList *Instructions); - -/// Write to a stream the disasembled version of the instruction at the -/// specified program counter. -/// -/// \param Result the output stream -/// \param PC the program counter in the current context. -/// \param MaxSize the maximum number of bytes to disassemble. -/// \param InstructionCount the maximum number of instructions to disassemble. -void disassemble(std::ostream &Result, - MetaAddress PC, - uint32_t MaxBytes = 4096, - uint32_t InstructionCount = 4096); diff --git a/include/revng/Lift/VariableManager.h b/include/revng/Lift/VariableManager.h index 61450c6d1..18472c936 100644 --- a/include/revng/Lift/VariableManager.h +++ b/include/revng/Lift/VariableManager.h @@ -10,8 +10,6 @@ #include "llvm/Pass.h" -#include "revng/Lift/CPUStateAccessAnalysisPass.h" -#include "revng/Lift/PTCDump.h" #include "revng/Support/CommandLine.h" #include "revng/Support/IRBuilder.h" #include "revng/Support/IRHelpers.h" @@ -26,36 +24,50 @@ class StructType; class Value; } // namespace llvm -class VariableManager; +struct LibTcgInstructionList; +struct LibTcgInstruction; +struct LibTcgArgument; +struct LibTcgTemp; -// TODO: rename -extern llvm::cl::opt External; +// TODO: this class is used by CodeGenerator and by fixHelpers. +// The latter only needs the part of this class that's related to the +// CPU state. +// There's an opportunity to split off this class in a class that only +// manages the CPU state and another that handles temporaries and the +// like. We should also likely have some RAII object that saves us from +// having to manually clean up temporary state by calling +// newTranslationBlock. /// Maintain the list of variables required by PTC /// /// It can be queried for a variable, which, if not already existing, will be /// created on the fly. class VariableManager { +public: + using GlobalsMap = std::map; + public: VariableManager(llvm::Module &M, bool TargetIsLittleEndian, - llvm::StructType *CPUStruct, - unsigned EnvOffset); + unsigned LibTcgEnvOffset, + uint8_t *LibTcgEnvPtr, + const std::map &GlobalNames); void setAllocaInsertPoint(llvm::Instruction *I) { AllocaBuilder.SetInsertPoint(I); } - llvm::Instruction *load(revng::IRBuilder &Builder, unsigned TemporaryId) { - using namespace llvm; - - auto &&[IsNew, V] = getOrCreate(TemporaryId, true); + llvm::Value *load(revng::IRBuilder &Builder, LibTcgArgument *Arg) { + auto [IsNew, V] = getOrCreate(Arg, true); if (V == nullptr) return nullptr; + if (llvm::isa(V)) + return V; + if (IsNew) { - auto *Undef = UndefValue::get(getVariableType(V)); + auto *Undef = llvm::UndefValue::get(getVariableType(V)); Builder.CreateStore(Undef, V); } @@ -70,38 +82,36 @@ public: /// \param TemporaryId the PTC temporary identifier. /// /// \return a `Value` wrapping the requested global or local variable. - llvm::Value *getOrCreate(unsigned TemporaryId) { - return getOrCreate(TemporaryId, false).second; + llvm::Value *getOrCreate(LibTcgArgument *Arg) { + return getOrCreate(Arg, false).second; } /// Return the global variable corresponding to \p Offset in the CPU state. /// /// \param Offset the offset in the CPU state (the `env` PTC variable). - /// \param Name an optional name to force for the associate global variable. /// /// \return a pair composed by the request global variable and the offset in /// it corresponding to \p Offset. For instance, if you're accessing /// the third byte of a 32-bit integer it will 2. - std::pair - getByEnvOffset(intptr_t Offset, std::string Name = "") { - return getByCPUStateOffsetInternal(EnvOffset + Offset, Name); + std::pair getByEnvOffset(intptr_t Offset) { + return getByCPUStateOffsetWithRemainder(LibTcgEnvOffset + Offset); } - /// Notify VariableManager to reset all the "function"-specific information + /// Notify VariableManager to reset all the Translation Block (TB) specific + /// information /// - /// Informs the VariableManager that a new function has begun, so it can - /// discard function- and basic block-level variables. + /// Note: A TB refers to a set of instructions that could be translated by + /// QEMU in one shot, and might encompass multiple LLVM basic blocks. /// - /// Note: by "function" here we mean a function in PTC terms, i.e. a run of - /// code translated in a single shot by the TCG. Do not confuse this - /// function concept with other meanings. - /// - /// \param Instructions the new PTCInstructionList to use from now on. - void newFunction(PTCInstructionList *Instructions); + void newTranslationBlock() { + TBTemporaries.clear(); + newExtendedBasicBlock(); + } - /// Informs the VariableManager that a new basic block has begun, so it can - /// discard basic block-level variables. - void newBasicBlock() { Temporaries.clear(); } + /// Informs the VariableManager that a new Extended Basic Block (EBB) has + /// begun. An EBB is a single entry, multiple exit region that fallst through + /// conditional branches. + void newExtendedBasicBlock() { EBBTemporaries.clear(); } /// Returns true if the given variable is the env variable bool isEnv(llvm::Value *TheValue); @@ -114,49 +124,54 @@ public: ModuleLayout = NewLayout; } - std::vector locals() { - std::vector Locals; - for (auto Pair : LocalTemporaries) - Locals.push_back(Pair.second); - return Locals; + std::vector getLiveVariables() { + std::vector LiveVariables; + for (auto Pair : TBTemporaries) + LiveVariables.push_back(Pair.second); + for (auto Pair : EBBTemporaries) + LiveVariables.push_back(Pair.second); + return LiveVariables; } llvm::Value *loadFromEnvOffset(revng::IRBuilder &Builder, unsigned LoadSize, unsigned Offset) { - return loadFromCPUStateOffset(Builder, LoadSize, EnvOffset + Offset); + return loadFromCPUStateOffset(Builder, LoadSize, LibTcgEnvOffset + Offset); } std::optional storeToEnvOffset(revng::IRBuilder &Builder, unsigned StoreSize, unsigned Offset, llvm::Value *ToStore) { - unsigned ActualOffset = EnvOffset + Offset; + unsigned ActualOffset = LibTcgEnvOffset + Offset; return storeToCPUStateOffset(Builder, StoreSize, ActualOffset, ToStore); } - bool memcpyAtEnvOffset(revng::IRBuilder &Builder, - llvm::CallInst *CallMemcpy, - unsigned Offset, - bool EnvIsSrc); + /// Handle memcpy, memmove and memset + void memOpAtEnvOffset(revng::IRBuilder &Builder, + llvm::CallInst *Call, + unsigned Offset, + bool EnvIsSrc); + + void memOpAtCPUStateOffset(revng::IRBuilder &Builder, + llvm::CallInst *Call, + unsigned Offset, + bool EnvIsSrc) { + memOpAtEnvOffset(Builder, Call, Offset - LibTcgEnvOffset, EnvIsSrc); + } /// Perform finalization steps on variables void finalize(); void rebuildCSVList(); - /// Gets the CPUStateType - llvm::StructType *getCPUStateType() const { return CPUStateType; } - - bool hasEnv() const { return Env != nullptr; } - llvm::Value *cpuStateToEnv(llvm::Value *CPUState, llvm::Instruction *InsertBefore) const; private: - std::pair getOrCreate(unsigned TemporaryId, - bool Reading); + std::pair getOrCreate(LibTcgArgument *Arg, bool Reading); +public: llvm::Value *loadFromCPUStateOffset(revng::IRBuilder &Builder, unsigned LoadSize, unsigned Offset); @@ -167,27 +182,34 @@ private: unsigned Offset, llvm::Value *ToStore); - llvm::GlobalVariable *getByCPUStateOffset(intptr_t Offset, - std::string Name = ""); + llvm::GlobalVariable *getByCPUStateOffset(intptr_t Offset); std::pair - getByCPUStateOffsetInternal(intptr_t Offset, std::string Name = ""); + getByCPUStateOffsetWithRemainder(intptr_t Offset); + + std::optional> + getGlobalByCPUStateOffset(intptr_t Offset) const; private: llvm::Module &TheModule; revng::NonDebugInfoCheckingIRBuilder AllocaBuilder; - using TemporariesMap = std::map; - using GlobalsMap = std::map; GlobalsMap CPUStateGlobals; - GlobalsMap OtherGlobals; - TemporariesMap Temporaries; - TemporariesMap LocalTemporaries; - PTCInstructionList *Instructions = nullptr; - llvm::StructType *CPUStateType; + // QEMU terminology + // - Translation Block (TB): All instructions that could be translated in one + // shot, might encompass multiple LLVM basic blocks. + // - Extended Basic Block (EBB): Single entry, multiple exit region that falls + // through conditional branches, smaller than a TB. + using TemporariesMap = std::map; + TemporariesMap TBTemporaries; + TemporariesMap EBBTemporaries; + + llvm::StructType *ArchCPUStruct; const llvm::DataLayout *ModuleLayout; - unsigned EnvOffset; + unsigned LibTcgEnvOffset; + uint8_t *LibTcgEnvPtr; llvm::GlobalVariable *Env; bool TargetIsLittleEndian; + const std::map &GlobalNames; }; diff --git a/include/revng/Model/Architecture.h b/include/revng/Model/Architecture.h index 1f3725994..04206170f 100644 --- a/include/revng/Model/Architecture.h +++ b/include/revng/Model/Architecture.h @@ -295,21 +295,7 @@ inline llvm::StringRef getJumpAssembly(Values V) { } } -inline llvm::StringRef getPCCSVName(Values V) { - switch (V) { - case model::Architecture::x86_64: - case model::Architecture::systemz: - case model::Architecture::x86: - case model::Architecture::arm: - case model::Architecture::aarch64: - case model::Architecture::mips: - case model::Architecture::mipsel: - return "pc"; - - default: - revng_abort(); - } -} +llvm::StringRef getPCCSVName(Values V); inline llvm::StringRef getQEMUName(Values V) { switch (V) { @@ -332,18 +318,40 @@ inline llvm::StringRef getQEMUName(Values V) { } } +inline Values fromQEMUName(llvm::StringRef Name) { + if (Name == "x86_64") + return model::Architecture::x86_64; + else if (Name == "s390x") + return model::Architecture::systemz; + else if (Name == "i386") + return model::Architecture::x86; + else if (Name == "arm") + return model::Architecture::arm; + else if (Name == "aarch64") + return model::Architecture::aarch64; + else if (Name == "mips") + return model::Architecture::mips; + else if (Name == "mipsel") + return model::Architecture::mipsel; + else + revng_abort(); +} + inline unsigned getMinimalFinalStackOffset(Values V) { switch (V) { case model::Architecture::x86_64: return 8; + case model::Architecture::x86: return 4; + case model::Architecture::systemz: case model::Architecture::arm: case model::Architecture::aarch64: case model::Architecture::mips: case model::Architecture::mipsel: return 0; + default: revng_abort(); } diff --git a/include/revng/Model/FunctionTags.h b/include/revng/Model/FunctionTags.h index 603beab52..5eb08acad 100644 --- a/include/revng/Model/FunctionTags.h +++ b/include/revng/Model/FunctionTags.h @@ -125,27 +125,10 @@ inline bool isCallToHelper(const llvm::Instruction *I) { return getCallToHelper(I) != nullptr; } -inline std::optional -getCSVUsedByHelperCallIfAvailable(llvm::Instruction *Call) { - revng_assert(isCallToHelper(Call)); - - const llvm::Module *M = getModule(Call); - const auto LoadMDKind = M->getMDKindID("revng.csvaccess.offsets.load"); - const auto StoreMDKind = M->getMDKindID("revng.csvaccess.offsets.store"); - - if (Call->getMetadata(LoadMDKind) == nullptr - and Call->getMetadata(StoreMDKind) == nullptr) { - return {}; - } - - CSVsUsage Result; - Result.Read = extractCSVs(Call, LoadMDKind); - Result.Written = extractCSVs(Call, StoreMDKind); - return Result; -} +std::optional tryGetCSVUsedByHelperCall(llvm::Instruction *Call); inline CSVsUsage getCSVUsedByHelperCall(llvm::Instruction *Call) { - return getCSVUsedByHelperCallIfAvailable(Call).value(); + return tryGetCSVUsedByHelperCall(Call).value(); } /// Checks if \p I is a marker diff --git a/include/revng/Model/Importer/Binary/BinaryImporterHelper.h b/include/revng/Model/Importer/Binary/BinaryImporterHelper.h index 5854f0091..c3a21be95 100644 --- a/include/revng/Model/Importer/Binary/BinaryImporterHelper.h +++ b/include/revng/Model/Importer/Binary/BinaryImporterHelper.h @@ -38,14 +38,13 @@ public: MetaAddress fromPC(uint64_t PC) const { using namespace model::Architecture; revng_assert(Binary.Architecture() != Invalid); - return MetaAddress::fromPC(toLLVMArchitecture(Binary.Architecture()), PC); + return MetaAddress::fromPC(Binary.Architecture(), PC); } MetaAddress fromGeneric(uint64_t Address) const { using namespace model::Architecture; revng_assert(Binary.Architecture() != Invalid); - return MetaAddress::fromGeneric(toLLVMArchitecture(Binary.Architecture()), - Address); + return MetaAddress::fromGeneric(Binary.Architecture(), Address); } public: diff --git a/include/revng/Model/ProgramCounterHandler.h b/include/revng/Model/ProgramCounterHandler.h index 26d7ce2ff..6b87ac7f2 100644 --- a/include/revng/Model/ProgramCounterHandler.h +++ b/include/revng/Model/ProgramCounterHandler.h @@ -7,6 +7,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/IR/Value.h" +#include "revng/Model/Architecture.h" #include "revng/Support/BlockType.h" #include "revng/Support/IRHelpers.h" @@ -42,8 +43,8 @@ namespace revng::detail { using namespace llvm; -using CSVFactory = std::function; +using CSVFactory = std::function< + GlobalVariable *(PCAffectingCSV::Values CSVID)>; }; // namespace revng::detail @@ -51,7 +52,6 @@ using CSVFactory = revng::detail::CSVFactory; class ProgramCounterHandler { protected: - static constexpr const char *AddressName = "pc"; static constexpr const char *AddressSpaceName = "pc_address_space"; static constexpr const char *EpochName = "pc_epoch"; static constexpr const char *TypeName = "pc_type"; @@ -77,12 +77,12 @@ public: public: static std::unique_ptr - create(llvm::Triple::ArchType Architecture, + create(model::Architecture::Values Architecture, llvm::Module *M, const CSVFactory &Factory); static std::unique_ptr - fromModule(llvm::Triple::ArchType Architecture, llvm::Module *M); + fromModule(model::Architecture::Values Architecture, llvm::Module *M); public: std::array pcCSVs() const { @@ -149,6 +149,8 @@ public: return false; } + bool isPCAffectingHelper(llvm::Instruction *I) const; + /// \return an empty optional if the PC has not changed on at least one path, /// an invalid MetaAddress in case there isn't a single next PC, or, /// finally, a valid MetaAddress representing the only possible next @@ -180,7 +182,7 @@ public: virtual std::array dissectJumpablePC(revng::IRBuilder &Builder, llvm::Value *ToDissect, - llvm::Triple::ArchType Arch) const = 0; + model::Architecture::Values Arch) const = 0; virtual void deserializePCFromSignalContext(revng::IRBuilder &Builder, @@ -235,8 +237,6 @@ public: protected: void createMissingVariables(llvm::Module *M) { - if (AddressCSV == nullptr) - AddressCSV = createAddress(M); if (EpochCSV == nullptr) EpochCSV = createEpoch(M); if (AddressSpaceCSV == nullptr) @@ -258,8 +258,8 @@ protected: return Builder.CreateAnd(V, Mask); } -public: - void setMissingVariables(llvm::Module *M) { +protected: + void setMissingVariables(llvm::Module *M, llvm::StringRef AddressName) { AddressCSV = M->getGlobalVariable(AddressName, true); EpochCSV = M->getGlobalVariable(EpochName, true); AddressSpaceCSV = M->getGlobalVariable(AddressSpaceName, true); @@ -270,12 +270,6 @@ public: } private: - bool isPCAffectingHelper(llvm::Instruction *I) const; - - static llvm::GlobalVariable *createAddress(llvm::Module *M) { - return createVariable(M, AddressName, sizeof(MetaAddress::Address)); - } - static llvm::GlobalVariable *createEpoch(llvm::Module *M) { return createVariable(M, EpochName, sizeof(MetaAddress::Epoch)); } diff --git a/include/revng/Model/Register.h b/include/revng/Model/Register.h index 74e555fef..88dab8113 100644 --- a/include/revng/Model/Register.h +++ b/include/revng/Model/Register.h @@ -470,59 +470,10 @@ inline std::optional getMContextIndex(Values V) { revng_abort("Not supported for this architecture"); } -inline llvm::StringRef getCSVName(Values V) { - // TODO: handle xmm0_x86 - switch (V) { - case st0_x86: - return "state_0x83c0"; - case xmm0_x86_64: - return "state_0x8558"; - case xmm1_x86_64: - return "state_0x8598"; - case xmm2_x86_64: - return "state_0x85d8"; - case xmm3_x86_64: - return "state_0x8618"; - case xmm4_x86_64: - return "state_0x8658"; - case xmm5_x86_64: - return "state_0x8698"; - case xmm6_x86_64: - return "state_0x86d8"; - case xmm7_x86_64: - return "state_0x8718"; - default: - return model::Register::getRegisterName(V); - } -} +std::string getCSVName(Values V); -inline Values fromCSVName(llvm::StringRef Name, - model::Architecture::Values Architecture) { - if (Architecture == model::Architecture::x86_64) { - // TODO: handle xmm0_x86 - if (Name == "state_0x83c0") { - return st0_x86; - } else if (Name == "state_0x8558") { - return xmm0_x86_64; - } else if (Name == "state_0x8598") { - return xmm1_x86_64; - } else if (Name == "state_0x85d8") { - return xmm2_x86_64; - } else if (Name == "state_0x8618") { - return xmm3_x86_64; - } else if (Name == "state_0x8658") { - return xmm4_x86_64; - } else if (Name == "state_0x8698") { - return xmm5_x86_64; - } else if (Name == "state_0x86d8") { - return xmm6_x86_64; - } else if (Name == "state_0x8718") { - return xmm7_x86_64; - } - } - - return model::Register::fromRegisterName(Name, Architecture); -} +Values fromCSVName(llvm::StringRef Name, + model::Architecture::Values Architecture); constexpr inline model::PrimitiveKind::Values primitiveKind(Values V) { switch (V) { diff --git a/include/revng/Recompile/OriginalAssemblyAnnotationWriter.h b/include/revng/Recompile/OriginalAssemblyAnnotationWriter.h index 44ff29532..d69d0ee6c 100644 --- a/include/revng/Recompile/OriginalAssemblyAnnotationWriter.h +++ b/include/revng/Recompile/OriginalAssemblyAnnotationWriter.h @@ -11,7 +11,6 @@ class OriginalAssemblyAnnotationWriter : public llvm::AssemblyAnnotationWriter { public: OriginalAssemblyAnnotationWriter(llvm::LLVMContext &Context) : - OriginalInstrMDKind(Context.getMDKindID("oi")), PTCInstrMDKind(Context.getMDKindID("pi")) {} ~OriginalAssemblyAnnotationWriter() override = default; @@ -21,6 +20,5 @@ public: llvm::formatted_raw_ostream &Output) override; private: - unsigned OriginalInstrMDKind; unsigned PTCInstrMDKind; }; diff --git a/include/revng/Support/BasicBlockID.h b/include/revng/Support/BasicBlockID.h index ef286006d..f7df730cf 100644 --- a/include/revng/Support/BasicBlockID.h +++ b/include/revng/Support/BasicBlockID.h @@ -38,7 +38,8 @@ public: public: static BasicBlockID fromString(llvm::StringRef Text); - std::string toString(std::optional Arch = {}) const; + std::string toString(model::Architecture::Values Architecture = + model::Architecture::Invalid) const; static BasicBlockID fromValue(llvm::Value *V); llvm::Constant *toValue(llvm::Module *M) const; diff --git a/include/revng/Support/FastValuePrinter.h b/include/revng/Support/FastValuePrinter.h new file mode 100644 index 000000000..f70566084 --- /dev/null +++ b/include/revng/Support/FastValuePrinter.h @@ -0,0 +1,29 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include + +#include "llvm/IR/ModuleSlotTracker.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/raw_ostream.h" + +class FastValuePrinter { +private: + llvm::ModuleSlotTracker MST; + +public: + FastValuePrinter(const llvm::Module &M) : MST(&M, false) {} + +public: + std::string toString(const llvm::Value &V, bool PrintAsOperand = false) { + std::string Result; + { + llvm::raw_string_ostream Stream(Result); + V.print(Stream, MST, true); + } + return Result; + } +}; diff --git a/include/revng/Support/IRHelpers.h b/include/revng/Support/IRHelpers.h index d4c1ffb0c..671e5fff2 100644 --- a/include/revng/Support/IRHelpers.h +++ b/include/revng/Support/IRHelpers.h @@ -930,26 +930,6 @@ inline const llvm::CallInst *getCallTo(const llvm::Instruction *I, return nullptr; } -inline std::vector extractCSVs(llvm::Instruction *Call, - unsigned MDKindID) { - using namespace llvm; - - std::vector Result; - auto *Tuple = cast_or_null(Call->getMetadata(MDKindID)); - if (Tuple == nullptr) - return Result; - - QuickMetadata QMD(getContext(Call)); - - auto OperandsRange = QMD.extract(Tuple, 1)->operands(); - for (const MDOperand &Operand : OperandsRange) { - auto *CSV = QMD.extract(Operand.get()); - Result.push_back(cast(CSV)); - } - - return Result; -} - inline bool CompareByName(const llvm::GlobalVariable *LHS, const llvm::GlobalVariable *RHS) { revng_assert(LHS->hasName() and RHS->hasName()); @@ -1553,12 +1533,16 @@ public: void sortModule(llvm::Module &M); +std::unique_ptr parseIR(llvm::LLVMContext &Context, + llvm::StringRef Path); + /// \p FinalLinkage final linkage for all the globals. Use std::nullopt to /// preserve the original one. void linkModules(std::unique_ptr &&Source, llvm::Module &Destination, std::optional FinalLinkage); + inline void linkModules(std::unique_ptr &&Source, llvm::Module &Destination) { - return linkModules(std::move(Source), Destination, std::nullopt); + linkModules(std::move(Source), Destination, std::nullopt); } diff --git a/include/revng/Support/MetaAddress.h b/include/revng/Support/MetaAddress.h index 9d3436a6e..8ef0bb906 100644 --- a/include/revng/Support/MetaAddress.h +++ b/include/revng/Support/MetaAddress.h @@ -4,10 +4,12 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include + #include "llvm/ADT/StringRef.h" -#include "llvm/ADT/Triple.h" #include "revng/ADT/KeyedObjectContainer.h" +#include "revng/Model/Architecture.h" #include "revng/Support/Debug.h" #include "revng/Support/IntegerSerialization.h" #include "revng/Support/OverflowSafeInt.h" @@ -162,27 +164,27 @@ inline llvm::StringRef consumeFromString(llvm::StringRef String) { return String; } -inline constexpr const std::optional arch(Values V) { +inline constexpr model::Architecture::Values arch(Values V) { switch (V) { case Code_x86: - return { llvm::Triple::x86 }; + return model::Architecture::x86; case Code_x86_64: - return { llvm::Triple::x86_64 }; + return model::Architecture::x86_64; case Code_mips: - return { llvm::Triple::mips }; + return model::Architecture::mips; case Code_mipsel: - return { llvm::Triple::mipsel }; + return model::Architecture::mipsel; case Code_arm: case Code_arm_thumb: - return { llvm::Triple::arm }; + return model::Architecture::arm; case Code_aarch64: - return { llvm::Triple::aarch64 }; + return model::Architecture::aarch64; case Code_systemz: - return { llvm::Triple::systemz }; + return model::Architecture::systemz; case Invalid: case Generic32: case Generic64: - return {}; + return model::Architecture::Invalid; case Count: default: revng_abort(); @@ -190,16 +192,17 @@ inline constexpr const std::optional arch(Values V) { } /// Returns Generic32 or Generic64 depending on the size of addresses in \p Arch -inline constexpr Values genericFromArch(llvm::Triple::ArchType Arch) { - switch (Arch) { - case llvm::Triple::x86: - case llvm::Triple::arm: - case llvm::Triple::mips: - case llvm::Triple::mipsel: +inline constexpr Values +genericFromArch(model::Architecture::Values Architecture) { + switch (Architecture) { + case model::Architecture::x86: + case model::Architecture::arm: + case model::Architecture::mips: + case model::Architecture::mipsel: return Generic32; - case llvm::Triple::x86_64: - case llvm::Triple::aarch64: - case llvm::Triple::systemz: + case model::Architecture::x86_64: + case model::Architecture::aarch64: + case model::Architecture::systemz: return Generic64; default: revng_abort("Unsupported architecture"); @@ -235,21 +238,21 @@ inline constexpr Values toGeneric(Values Type) { } /// Get the default type for code of the given architecture -inline constexpr Values defaultCodeFromArch(llvm::Triple::ArchType Arch) { +inline constexpr Values defaultCodeFromArch(model::Architecture::Values Arch) { switch (Arch) { - case llvm::Triple::x86: + case model::Architecture::x86: return Code_x86; - case llvm::Triple::arm: + case model::Architecture::arm: return Code_arm; - case llvm::Triple::mips: + case model::Architecture::mips: return Code_mips; - case llvm::Triple::mipsel: + case model::Architecture::mipsel: return Code_mipsel; - case llvm::Triple::x86_64: + case model::Architecture::x86_64: return Code_x86_64; - case llvm::Triple::aarch64: + case model::Architecture::aarch64: return Code_aarch64; - case llvm::Triple::systemz: + case model::Architecture::systemz: return Code_systemz; default: revng_abort("Unsupported architecture"); @@ -340,21 +343,21 @@ inline constexpr bool isCode(Values Type) { } /// Does \p Type represent an address pointing to \p Arch code? -inline constexpr bool isCode(Values Type, llvm::Triple::ArchType Arch) { +inline constexpr bool isCode(Values Type, model::Architecture::Values Arch) { switch (Arch) { - case llvm::Triple::x86: + case model::Architecture::x86: return Type == Code_x86; - case llvm::Triple::arm: + case model::Architecture::arm: return Type == Code_arm or Type == Code_arm_thumb; - case llvm::Triple::mips: + case model::Architecture::mips: return Type == Code_mips; - case llvm::Triple::mipsel: + case model::Architecture::mipsel: return Type == Code_mipsel; - case llvm::Triple::x86_64: + case model::Architecture::x86_64: return Type == Code_x86_64; - case llvm::Triple::aarch64: + case model::Architecture::aarch64: return Type == Code_aarch64; - case llvm::Triple::systemz: + case model::Architecture::systemz: return Type == Code_systemz; default: revng_abort("Unsupported architecture"); @@ -401,7 +404,6 @@ inline constexpr bool isDefaultCode(Values Type) { case Generic64: case Code_arm_thumb: return false; - case Count: default: revng_abort("Unknown MetaAddressType value"); @@ -468,7 +470,7 @@ public: public: class Features { public: - llvm::Triple::ArchType Architecture = llvm::Triple::UnknownArch; + model::Architecture::Values Architecture = model::Architecture::Invalid; uint32_t Epoch = 0; uint16_t AddressSpace = 0; @@ -514,7 +516,7 @@ public: static constexpr MetaAddress invalid() { return MetaAddress(); } /// Create a MetaAddress from a pointer to \p Arch code - static constexpr MetaAddress fromPC(llvm::Triple::ArchType Arch, + static constexpr MetaAddress fromPC(model::Architecture::Values Arch, uint64_t PC, uint32_t Epoch = 0, uint16_t AddressSpace = 0) { @@ -528,7 +530,7 @@ public: // A code MetaAddress pointing at 0 should always be valid revng_assert(Result.isValid()); - if (Arch == llvm::Triple::arm and (PC & 1) == 1) { + if (Arch == model::Architecture::arm and (PC & 1) == 1) { // A pointer to ARM code with the LSB turned on is Thumb code // Override the type @@ -544,7 +546,7 @@ public: } static MetaAddress fromPC(MetaAddress Base, uint64_t Address) { - return fromPC(*Base.arch(), Address, Base.epoch(), Base.addressSpace()); + return fromPC(Base.arch(), Address, Base.epoch(), Base.addressSpace()); } static MetaAddress fromPC(uint64_t Address, const Features &Features) { @@ -555,7 +557,7 @@ public: } /// Create a generic MetaAddress for architecture \p Arch - static constexpr MetaAddress fromGeneric(llvm::Triple::ArchType Arch, + static constexpr MetaAddress fromGeneric(model::Architecture::Values Arch, uint64_t Address, uint32_t Epoch = 0, uint16_t AddressSpace = 0) { @@ -610,16 +612,16 @@ public: return Result; } - constexpr MetaAddress toPC(llvm::Triple::ArchType Arch) const { + constexpr MetaAddress toPC(model::Architecture::Values Arch) const { return fromPC(Arch, Address, Epoch, AddressSpace); } Features features() const { - return Features(*MetaAddressType::arch(type()), Epoch, AddressSpace); + return Features(MetaAddressType::arch(type()), Epoch, AddressSpace); } public: - constexpr auto operator<=>(const MetaAddress &Other) const { + constexpr std::strong_ordering operator<=>(const MetaAddress &Other) const { return tie() <=> Other.tie(); } constexpr bool operator==(const MetaAddress &Other) const { @@ -837,7 +839,7 @@ public: } constexpr bool isValid() const { return not isInvalid(); } constexpr bool isCode() const { return MetaAddressType::isCode(type()); } - constexpr bool isCode(llvm::Triple::ArchType Arch) const { + constexpr bool isCode(model::Architecture::Values Arch) const { return MetaAddressType::isCode(type(), Arch); } constexpr bool isGeneric() const { @@ -850,7 +852,7 @@ public: return MetaAddressType::alignment(type()); } - std::optional arch() const { + model::Architecture::Values arch() const { return MetaAddressType::arch(type()); } @@ -980,9 +982,10 @@ public: /// \param Arch specifying the "expected" architecture omits it from /// the serialized string. But it also leads to inability /// to deserialize it! So only use if you know what you're doing. - std::string toString(std::optional Arch = {}) const; - std::string - toIdentifier(std::optional Arch = {}) const; + std::string toString(model::Architecture::Values Arch = + model::Architecture::Invalid) const; + std::string toIdentifier(model::Architecture::Values Arch = + model::Architecture::Invalid) const; static MetaAddress fromString(llvm::StringRef Text); private: @@ -1013,10 +1016,10 @@ struct KeyedObjectTraits : public IdentityKeyedObjectTraits {}; inline llvm::hash_code hash_value(const MetaAddress &Address) { - return hash_combine(Address.arch(), - Address.address(), - Address.epoch(), - Address.addressSpace()); + return llvm::hash_combine(Address.arch(), + Address.address(), + Address.epoch(), + Address.addressSpace()); } namespace std { diff --git a/include/revng/ValueMaterializer/AdvancedValueInfo.h b/include/revng/ValueMaterializer/AdvancedValueInfo.h index a03f888bd..b4fb08689 100644 --- a/include/revng/ValueMaterializer/AdvancedValueInfo.h +++ b/include/revng/ValueMaterializer/AdvancedValueInfo.h @@ -10,6 +10,7 @@ #include "revng/MFP/Graph.h" #include "revng/MFP/MFP.h" #include "revng/ValueMaterializer/ControlFlowEdgesGraph.h" +#include "revng/ValueMaterializer/DataFlowRangeAnalysis.h" namespace llvm { class Instruction; @@ -29,6 +30,7 @@ public: private: llvm::LazyValueInfo &LVI; + DataFlowRangeAnalysis &DFRA; const llvm::DominatorTree &DT; llvm::Instruction *Context; llvm::SmallPtrSetImpl &Instructions; @@ -36,11 +38,13 @@ private: public: AdvancedValueInfoMFI(llvm::LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, const llvm::DominatorTree &DT, llvm::Instruction *Context, InstructionsSet &Instructions, bool ZeroExtendConstraints) : LVI(LVI), + DFRA(DFRA), DT(DT), Context(Context), Instructions(Instructions), @@ -71,9 +75,14 @@ runAVI(const DataFlowGraph &DFG, llvm::Instruction *Context, const llvm::DominatorTree &DT, llvm::LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, bool ZeroExtendConstraints); template<> void MFP::dump(llvm::raw_ostream &Stream, unsigned Indent, const std::map &Element); + +template<> +void MFP::dumpLabel(llvm::raw_ostream &Stream, + const ControlFlowEdgesGraph::Node *const &Label); diff --git a/include/revng/ValueMaterializer/DataFlowRangeAnalysis.h b/include/revng/ValueMaterializer/DataFlowRangeAnalysis.h new file mode 100644 index 000000000..c630cbc1e --- /dev/null +++ b/include/revng/ValueMaterializer/DataFlowRangeAnalysis.h @@ -0,0 +1,32 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include + +#include "llvm/IR/ModuleSlotTracker.h" + +#include "revng/ADT/ConstantRangeSet.h" + +namespace llvm { +class Value; +class Module; +} // namespace llvm + +class DataFlowRangeAnalysis { +public: + using CacheEntry = std::pair; + +private: + std::map Cache; + llvm::ModuleSlotTracker MST; + +public: + DataFlowRangeAnalysis(llvm::Module &M) : MST(&M, false) {} + +public: + std::optional visit(llvm::Value &I, llvm::Value &Variable); +}; diff --git a/include/revng/ValueMaterializer/ValueMaterializer.h b/include/revng/ValueMaterializer/ValueMaterializer.h index 0d52442db..82d501081 100644 --- a/include/revng/ValueMaterializer/ValueMaterializer.h +++ b/include/revng/ValueMaterializer/ValueMaterializer.h @@ -22,6 +22,8 @@ class LazyValueInfo; class DominatorTree; } // namespace llvm +class DataFlowRangeAnalysis; + namespace Oracle { enum Values { None, @@ -45,6 +47,7 @@ private: llvm::Value *V; MemoryOracle &MO; llvm::LazyValueInfo &LVI; + DataFlowRangeAnalysis &DFRA; const llvm::DominatorTree &DT; DataFlowGraph::Limits TheLimits; Oracle::Values Oracle; @@ -65,6 +68,7 @@ private: llvm::Value *V, MemoryOracle &MO, llvm::LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, const llvm::DominatorTree &DT, DataFlowGraph::Limits TheLimits, Oracle::Values Oracle) : @@ -72,6 +76,7 @@ private: V(V), MO(MO), LVI(LVI), + DFRA(DFRA), DT(DT), TheLimits(TheLimits), Oracle(Oracle) {} @@ -81,10 +86,11 @@ public: llvm::Value *V, MemoryOracle &MO, llvm::LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, const llvm::DominatorTree &DT, DataFlowGraph::Limits TheLimits, Oracle::Values Oracle) { - ValueMaterializer Result(Context, V, MO, LVI, DT, TheLimits, Oracle); + ValueMaterializer Result(Context, V, MO, LVI, DFRA, DT, TheLimits, Oracle); Result.run(); return Result; } diff --git a/lib/ABI/ModelHelpers.cpp b/lib/ABI/ModelHelpers.cpp index 686bb947a..52a196a84 100644 --- a/lib/ABI/ModelHelpers.cpp +++ b/lib/ABI/ModelHelpers.cpp @@ -338,6 +338,21 @@ getStrongModelInfo(const llvm::Instruction *Inst, const model::Binary &Model) { revng_assert(not ParentFunc()->StackFrameType().isEmpty()); rc_return{ ParentFunc()->StackFrameType() }; + } else if (FTags.contains(FunctionTags::QEMU) + and Call->getType()->isStructTy()) { + auto *ReturnedStruct = cast(Call->getType()); + revng_assert(llvm::all_of(ReturnedStruct->elements(), + [](llvm::Type *T) { + return isa(T); + })); + + llvm::SmallVector Result; + for (llvm::Type *ElementType : ReturnedStruct->elements()) { + auto ByteSize = ElementType->getIntegerBitWidth() / 8; + Result.push_back(model::PrimitiveType::makeGeneric(ByteSize)); + } + + rc_return Result; } else { revng_assert(not FuncName.startswith("revng_call_stack_arguments")); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e80609866..b608d3512 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory(FunctionCallIdentification) add_subdirectory(FunctionIsolation) add_subdirectory(GraphLayout) add_subdirectory(HeadersGeneration) +add_subdirectory(HelperArgumentsAnalysis) add_subdirectory(ImportFromC) add_subdirectory(InitModelTypes) add_subdirectory(LocalVariables) diff --git a/lib/Canonicalize/SimplifySwitchPass.cpp b/lib/Canonicalize/SimplifySwitchPass.cpp index c42b08375..3a83ce84d 100644 --- a/lib/Canonicalize/SimplifySwitchPass.cpp +++ b/lib/Canonicalize/SimplifySwitchPass.cpp @@ -18,12 +18,14 @@ #include "llvm/Passes/PassBuilder.h" #include "revng/Lift/LoadBinaryPass.h" +#include "revng/Model/Architecture.h" #include "revng/Pipeline/ExecutionContext.h" #include "revng/Pipeline/RegisterPipe.h" #include "revng/Pipes/FileContainer.h" #include "revng/Pipes/FunctionPass.h" #include "revng/Pipes/Kinds.h" #include "revng/Support/IRHelpers.h" +#include "revng/ValueMaterializer/DataFlowRangeAnalysis.h" #include "revng/ValueMaterializer/ValueMaterializer.h" using namespace llvm; @@ -42,8 +44,7 @@ public: BinaryView(BinaryView), Architecture(Architecture) {} MaterializedValue load(uint64_t LoadAddress, unsigned LoadSize) final { - auto Address = MetaAddress::fromGeneric(toLLVMArchitecture(Architecture), - LoadAddress); + auto Address = MetaAddress::fromGeneric(Architecture, LoadAddress); return BinaryView.load(Address, LoadSize, model::Architecture::isLittleEndian(Architecture)); @@ -92,6 +93,7 @@ findStartNode(const DataFlowGraph &DFG) { static bool handleSwitch(SwitchInst *Switch, LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, DominatorTree &DT, RawBinaryMemoryOracle &MO) { // Skip empty switches. @@ -100,14 +102,16 @@ static bool handleSwitch(SwitchInst *Switch, Value *Condition = Switch->getCondition(); DataFlowGraph::Limits Limits(1000 /*MaxPhiLike*/, 1 /*MaxLoad*/); - ::ValueMaterializer - Results = ::ValueMaterializer::getValuesFor(Switch, - Condition, - MO, - LVI, - DT, - Limits, - Oracle::AdvancedValueInfo); + + auto AVIOracle = Oracle::AdvancedValueInfo; + ::ValueMaterializer Results = ::ValueMaterializer::getValuesFor(Switch, + Condition, + MO, + LVI, + DFRA, + DT, + Limits, + AVIOracle); // We can not do too much without any values materialized. if (not Results.values()) return false; @@ -174,6 +178,7 @@ static bool handleSwitch(SwitchInst *Switch, static bool simplifySwitch(Function &F, LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, DominatorTree &DT, RawBinaryMemoryOracle &MO) { bool Result = false; @@ -183,7 +188,7 @@ static bool simplifySwitch(Function &F, if (not Switch) continue; - if (handleSwitch(Switch, LVI, DT, MO)) { + if (handleSwitch(Switch, LVI, DFRA, DT, MO)) { Switch->eraseFromParent(); Result |= true; } @@ -196,12 +201,13 @@ static bool simplifySwitch(Function &F, struct SimplifySwitchPassImpl : public pipeline::FunctionPassImpl { private: const model::Binary &Binary; + DataFlowRangeAnalysis DFRA; public: SimplifySwitchPassImpl(llvm::ModulePass &Pass, const model::Binary &Binary, llvm::Module &M) : - pipeline::FunctionPassImpl(Pass), Binary(Binary) {} + pipeline::FunctionPassImpl(Pass), Binary(Binary), DFRA(M) {} bool runOnFunction(const model::Function &ModelFunction, llvm::Function &Function) override; @@ -216,7 +222,7 @@ bool SimplifySwitchPassImpl::runOnFunction(const model::Function &ModelFunction, auto &DT = getAnalysis(Function).getDomTree(); RawBinaryView &BinaryView = getAnalysis().get(); RawBinaryMemoryOracle MO(BinaryView, Binary.Architecture()); - return simplifySwitch(Function, LVI, DT, MO); + return simplifySwitch(Function, LVI, DFRA, DT, MO); } void SimplifySwitchPassImpl::getAnalysisUsage(AnalysisUsage &AU) { diff --git a/lib/EarlyFunctionAnalysis/CFGAnalyzer.cpp b/lib/EarlyFunctionAnalysis/CFGAnalyzer.cpp index f0221ab97..a34fe0f12 100644 --- a/lib/EarlyFunctionAnalysis/CFGAnalyzer.cpp +++ b/lib/EarlyFunctionAnalysis/CFGAnalyzer.cpp @@ -39,6 +39,7 @@ #include "revng/Model/FunctionTags.h" #include "revng/Support/BasicBlockID.h" #include "revng/Support/Generator.h" +#include "revng/Support/MetaAddress.h" #include "revng/Support/TemporaryLLVMOption.h" // This name is not present after `lift`. @@ -397,10 +398,9 @@ CFGAnalyzer::State CFGAnalyzer::loadState(revng::IRBuilder &Builder) const { } // Load the PC - auto LLVMArchitecture = toLLVMArchitecture(Binary->Architecture()); auto DissectedPC = PCH->dissectJumpablePC(Builder, ReturnAddress, - LLVMArchitecture); + Binary->Architecture()); Value *IntegerPC = MetaAddress::composeIntegerPC(Builder, DissectedPC[0], DissectedPC[1], diff --git a/lib/EarlyFunctionAnalysis/DetectABI.cpp b/lib/EarlyFunctionAnalysis/DetectABI.cpp index 97404670e..7e5ede15a 100644 --- a/lib/EarlyFunctionAnalysis/DetectABI.cpp +++ b/lib/EarlyFunctionAnalysis/DetectABI.cpp @@ -120,9 +120,10 @@ static pipeline::RegisterAnalysis A1; namespace efa { static model::Architecture::Values getCodeArchitecture(const MetaAddress &MA) { - const auto MaybeArch = MetaAddressType::arch(MA.type()); - revng_assert(MaybeArch && "The architecture is available for code addresses"); - return model::Architecture::fromLLVMArchitecture(*MaybeArch); + const auto Architecture = MetaAddressType::arch(MA.type()); + revng_assert(Architecture != model::Architecture::Invalid, + "The architecture is available for code addresses"); + return Architecture; } static bool isWritingToMemory(llvm::Instruction &I) { @@ -598,7 +599,7 @@ void DetectABI::applyABIDeductions() { abi::Definition::RegisterSet RValues; model::Architecture::Values Architecture = Binary->Architecture(); for (const auto &Register : model::Architecture::registers(Architecture)) { - llvm::StringRef Name = model::Register::getCSVName(Register); + auto Name = model::Register::getCSVName(Register); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) { if (Summary.ABIResults.ArgumentsRegisters.contains(CSV)) Arguments.emplace(Register); @@ -626,7 +627,7 @@ void DetectABI::applyABIDeductions() { efa::CSVSet ResultingArguments; efa::CSVSet ResultingReturnValues; for (const auto &Register : model::Architecture::registers(Architecture)) { - llvm::StringRef Name = model::Register::getCSVName(Register); + auto Name = model::Register::getCSVName(Register); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) { if (Arguments.contains(Register)) ResultingArguments.insert(CSV); @@ -900,7 +901,7 @@ static void combineCrossCallSites(auto &CallSite, auto &Callee) { bool DetectABI::getRegisterState(model::Register::Values RegisterValue, const CSVSet &ABIRegisterMap) { - llvm::StringRef Name = model::Register::getCSVName(RegisterValue); + auto Name = model::Register::getCSVName(RegisterValue); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) { return ABIRegisterMap.contains(CSV); } diff --git a/lib/EarlyFunctionAnalysis/FunctionSummaryOracle.cpp b/lib/EarlyFunctionAnalysis/FunctionSummaryOracle.cpp index cd21ba541..1ac368e0f 100644 --- a/lib/EarlyFunctionAnalysis/FunctionSummaryOracle.cpp +++ b/lib/EarlyFunctionAnalysis/FunctionSummaryOracle.cpp @@ -52,13 +52,13 @@ PrototypeImporter::prototype(const AttributesSet &Attributes, auto &&[ArgumentRegisters, ReturnValueRegisters] = abi::FunctionType::usedRegisters(*Prototype); for (Register ArgumentRegister : ArgumentRegisters) { - llvm::StringRef Name = model::Register::getCSVName(ArgumentRegister); + auto Name = model::Register::getCSVName(ArgumentRegister); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) Summary.ABIResults.ArgumentsRegisters.insert(CSV); } for (Register ReturnValueRegister : ReturnValueRegisters) { - llvm::StringRef Name = model::Register::getCSVName(ReturnValueRegister); + auto Name = model::Register::getCSVName(ReturnValueRegister); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) Summary.ABIResults.ReturnValuesRegisters.insert(CSV); } diff --git a/lib/HelperArgumentsAnalysis/AnalyzeHelperArguments.cpp b/lib/HelperArgumentsAnalysis/AnalyzeHelperArguments.cpp new file mode 100644 index 000000000..d1256aebf --- /dev/null +++ b/lib/HelperArgumentsAnalysis/AnalyzeHelperArguments.cpp @@ -0,0 +1,268 @@ +/// \file AnalyzeHelperArguments.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/ADT/DenseMap.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/IR/Argument.h" +#include "llvm/IR/Attributes.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/GlobalValue.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/Casting.h" + +#include "AnnotationWriter.h" +#include "ArgumentUsageAnalysis.h" +#include "CPUStateUsage.h" +#include "Context.h" +#include "Function.h" +#include "Value.h" +#include "llvm-c/Types.h" + +using namespace llvm; + +static cl::opt ModuleDumpPath("analyze-helper-arguments-output", + cl::desc("path where to save the " + "module annotated with the " + "results of the Analyze " + "Helper Arguments Analysis" + "results"), + cl::init("")); + +// TODO: use ConstantExpr to perform the constant folding instead of inventing +// our own arithmetic framework + +static aua::StructPointers collectOffsetTypes(Module &Module, + StructType &ArchCPU) { + LLVMContext &Context = Module.getContext(); + aua::StructPointers Result(Module, ArchCPU); + + for (Function &F : Module) { + for (Instruction &I : llvm::instructions(F)) { + auto *Dbg = dyn_cast(&I); + if (Dbg == nullptr) + continue; + + auto *MAV0 = dyn_cast(Dbg->getArgOperand(0)); + auto *MAV1 = cast(Dbg->getArgOperand(1))->getMetadata(); + if (MAV0 == nullptr or MAV1 == nullptr + or not isa(MAV0->getMetadata()) + or not isa(MAV1)) + continue; + + auto *Value = cast(MAV0->getMetadata())->getValue(); + // TODO: go beyond arguments? + auto *Argument = dyn_cast_or_null(Value); + auto *Type = cast(MAV1)->getType(); + if (Argument == nullptr or Type == nullptr) + continue; + + revng_log(Log, + "Considering argument " << Argument->getArgNo() << " of " + << F.getName().str()); + LoggerIndent<> Indent(Log); + + auto *DIArgumentType = dyn_cast_or_null(Type); + if (DIArgumentType == nullptr + or DIArgumentType->getTag() != dwarf::DW_TAG_pointer_type) { + revng_log(Log, "Not a pointer"); + continue; + } + + auto *BaseType = DIArgumentType->getBaseType(); + + // Skip typedefs + auto *DerivedType = dyn_cast_or_null(BaseType); + while (DerivedType != nullptr + and DerivedType->getTag() == llvm::dwarf::DW_TAG_typedef) { + BaseType = DerivedType->getBaseType(); + DerivedType = dyn_cast_or_null(BaseType); + } + + if (BaseType == nullptr or BaseType->getRawName() == nullptr) { + revng_log(Log, "Pointee has no name"); + continue; + } + + for (const char *Prefix : std::vector{ "struct.", "union." }) { + auto *Struct = StructType::getTypeByName(Context, + (Prefix + BaseType->getName()) + .str()); + + if (Struct != nullptr) { + revng_log(Log, "Registering as a pointer to " << Struct->getName()); + Result.registerPointer(*Value, *Struct); + } + } + } + } + + Result.propagateFromActualArguments(); + + if (Log.isEnabled()) { + Result.dump(Log); + Log << DoLog; + } + + return Result; +} + +class AnalyzeHelperArguments : public ModulePass { +public: + static char ID; + +public: + AnalyzeHelperArguments() : ModulePass(ID) {} + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesAll(); + } + + bool runOnModule(Module &M) override { + Task T(6, "Analyze helper arguments"); + + T.advance("Prepare module"); + LLVMContext &LLVMContext = M.getContext(); + auto &DL = M.getDataLayout(); + + // NOTE: patch these things on the QEMU side wherever possible! + std::array NoOpFunctionNames = { + // Ignore debugging primitives + "printf", + + // Ignore assertion-like + "g_assertion_message_expr", + }; + std::vector NoOpFunctions; + for (auto &&Name : NoOpFunctionNames) + if (Function *F = M.getFunction(Name)) + NoOpFunctions.push_back(F); + + // NOTE: patch these things on the QEMU side wherever possible! + std::array AbortFunctionNames = { + // glib features we don't support + "g_hash_table_lookup", + "g_string_free", + }; + std::vector AbortFunctions; + for (auto &&Name : AbortFunctionNames) + if (Function *F = M.getFunction(Name)) + AbortFunctions.push_back(F); + + for (Function &F : M) { + if (F.getSection() == "revng_noop") { + NoOpFunctions.push_back(&F); + } else if (F.getSection() == "revng_abort") { + AbortFunctions.push_back(&F); + } + } + + for (Function *F : NoOpFunctions) { + auto *ReturnType = F->getFunctionType()->getReturnType(); + revng_assert(ReturnType->isVoidTy() or ReturnType->isPointerTy() + or ReturnType->isIntegerTy()); + + // Purge the function body + { + for (BasicBlock &BB : *F) + BB.dropAllReferences(); + while (not F->empty()) + F->begin()->eraseFromParent(); + } + + auto *Block = BasicBlock::Create(LLVMContext, "", F); + if (ReturnType->isVoidTy()) { + ReturnInst::Create(LLVMContext, Block); + } else if (ReturnType->isPointerTy()) { + auto *Null = ConstantPointerNull::get(cast(ReturnType)); + ReturnInst::Create(LLVMContext, Null, Block); + } else { + auto *Zero = ConstantInt::get(ReturnType, 0); + ReturnInst::Create(LLVMContext, Zero, Block); + } + } + + for (Function *F : AbortFunctions) { + { + for (BasicBlock &BB : *F) + BB.dropAllReferences(); + while (not F->empty()) + F->begin()->eraseFromParent(); + } + + F->setDoesNotReturn(); + auto *Block = BasicBlock::Create(LLVMContext, "", F); + new UnreachableInst(LLVMContext, Block); + } + + // Remove noreturn from cpu_loop_exit and its call sites + for (auto &F : M) { + if (F.getName().starts_with("cpu_loop_exit")) { + F.removeFnAttr(Attribute::NoReturn); + + for (CallBase *Call : callers(&F)) + Call->removeFnAttr(Attribute::NoReturn); + } + } + + aua::Context Context; + + T.advance("Run argument usage analysis"); + aua::ArgumentUsageAnalysis AUA(Context, M); + AUA.run(); + + T.advance("Collect offsets types"); + auto *CPUStruct = StructType::getTypeByName(LLVMContext, "struct.ArchCPU"); + aua::StructPointers OffsetTypes = collectOffsetTypes(M, *CPUStruct); + + T.advance("Analyzing helpers"); + aua::CPUStateUsageAnalysis CSUA(Context, + AUA, + DL, + *CPUStruct, + std::move(OffsetTypes)); + + SmallVector HelperDefinitions; + for (Function &F : M) + if (not F.isDeclaration() and F.getName().starts_with("helper_")) + HelperDefinitions.push_back(&F); + + { + Task T(HelperDefinitions.size(), "Analyzing helpers"); + for (Function *F : HelperDefinitions) { + T.advance(F->getName()); + revng_assert(not F->isVarArg()); + CSUA.analyze(*F); + } + } + + T.advance("Adding annotations"); + CSUA.annotate(M); + + T.advance("Dumping module"); + if (ModuleDumpPath.getNumOccurrences() != 0) { + aua::AnnotationWriter Annotator = aua::AnnotationWriter(AUA, CSUA); + std::error_code EC; + raw_fd_ostream Stream(ModuleDumpPath.getValue(), EC); + revng_assert(not EC); + M.print(Stream, &Annotator); + } + + return false; + } +}; + +char AnalyzeHelperArguments::ID = 0; + +using Register = RegisterPass; +static Register X("analyze-helper-arguments", + "Analyze usage of arguments of helper functions"); diff --git a/lib/HelperArgumentsAnalysis/Annotation.cpp b/lib/HelperArgumentsAnalysis/Annotation.cpp new file mode 100644 index 000000000..217781bec --- /dev/null +++ b/lib/HelperArgumentsAnalysis/Annotation.cpp @@ -0,0 +1,85 @@ +/// \file Annotation.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include + +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Function.h" + +#include "revng/HelperArgumentsAnalysis/Annotation.h" +#include "revng/Support/IRHelpers.h" + +using namespace llvm; + +namespace aua { + +llvm::MDNode & +Annotation::serializeToMetadata(llvm::LLVMContext &Context) const { + QuickMetadata QMD(Context); + + auto ToTuple = [&QMD](const OffsetAndSizeSet &Set) { + SmallVector Entries; + for (auto &[Offset, Size] : Set) + Entries.push_back(QMD.tuple({ QMD.get(Offset), QMD.get(Size) })); + return QMD.tuple(Entries); + }; + + return *QMD.tuple({ QMD.get(Escapes), ToTuple(Reads), ToTuple(Writes) }); +} + +void Annotation::serialize(llvm::User &ToAnnotate) { + auto &MDAnnotation = serializeToMetadata(getContext(&ToAnnotate)); + if (auto *F = dyn_cast(&ToAnnotate)) { + F->setMetadata(MetadataKind, &MDAnnotation); + } else if (auto *I = dyn_cast(&ToAnnotate)) { + I->setMetadata(MetadataKind, &MDAnnotation); + } +} + +Annotation Annotation::deserializeFromMetadata(llvm::LLVMContext &Context, + llvm::MDNode &MD) { + using llvm::MDTuple; + Annotation Result; + QuickMetadata QMD(Context); + + auto FromTuple = [&QMD](const MDTuple &Tuple) -> OffsetAndSizeSet { + OffsetAndSizeSet Result; + for (llvm::Metadata *MD : Tuple.operands()) { + Result.insert({ QMD.extract(MD, 0), + QMD.extract(MD, 1) }); + } + return Result; + }; + + Result.Escapes = QMD.extract(&MD, 0); + Result.Reads = FromTuple(*QMD.extract(&MD, 1)); + Result.Writes = FromTuple(*QMD.extract(&MD, 2)); + + return Result; +} + +bool Annotation::isAnnotated(llvm::Instruction &I) { + return I.getMetadata(MetadataKind) != nullptr; +} + +std::optional Annotation::deserialize(llvm::User &ToAnnotate) { + + MDNode *MD = nullptr; + if (auto *F = dyn_cast(&ToAnnotate)) { + MD = F->getMetadata(MetadataKind); + } else if (auto *I = dyn_cast(&ToAnnotate)) { + MD = I->getMetadata(MetadataKind); + } else { + revng_abort(); + } + + if (MD == nullptr) + return std::nullopt; + + return deserializeFromMetadata(getContext(&ToAnnotate), *MD); +} + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/AnnotationWriter.h b/lib/HelperArgumentsAnalysis/AnnotationWriter.h new file mode 100644 index 000000000..e665bd34c --- /dev/null +++ b/lib/HelperArgumentsAnalysis/AnnotationWriter.h @@ -0,0 +1,75 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/IR/AssemblyAnnotationWriter.h" +#include "llvm/Support/FormattedStream.h" + +#include "ArgumentUsageAnalysis.h" +#include "CPUStateUsage.h" + +namespace aua { + +class AnnotationWriter : public llvm::AssemblyAnnotationWriter { +private: + ArgumentUsageAnalysis &AUA; + CPUStateUsageAnalysis &CSUA; + const Function *CurrentAUA = nullptr; + +public: + AnnotationWriter(ArgumentUsageAnalysis &AUA, CPUStateUsageAnalysis &CSUA) : + AUA(AUA), CSUA(CSUA) {} + ~AnnotationWriter() override = default; + +public: + virtual void emitFunctionAnnot(const llvm::Function *F, + llvm::formatted_raw_ostream &Output) override { + auto It = AUA.find(F); + if (It == AUA.end()) { + CurrentAUA = nullptr; + } else { + const Function &Results = It->second; + CurrentAUA = &Results; + + if (auto *Results = CSUA.get(*const_cast(F))) + Results->dump(Output, "; "); + + Results.dump(Output, "; "); + } + } + + virtual void + emitInstructionAnnot(const llvm::Instruction *I, + llvm::formatted_raw_ostream &Output) override { + if (CurrentAUA != nullptr) { + if (const Value *V = CurrentAUA->tryGet(*I)) { + Output << " ; " << V->toString() << "\n"; + } + + if (CSUA.isEscaping(*I)) + Output << " ; CPU state escapes!\n"; + + for (unsigned J = 0; J < I->getNumOperands(); ++J) { + const auto &Accesses = CSUA.getOffsets(I->getOperandUse(J)); + if (Accesses.size() > 0) { + Output << " ; Offsets for operand " << J << ": {"; + for (const auto &[Offset, Size] : Accesses) + Output << " i" << (Size * 8) << " @ " << Offset; + Output << " }\n"; + } + } + + if (auto *Call = dyn_cast(I)) { + for (const auto &Call : CurrentAUA->calls()) { + if (&Call.callInstruction() == I) { + Call.dump(Output, " ; ", true); + } + } + } + } + } +}; + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/ArgumentUsageAnalysis.cpp b/lib/HelperArgumentsAnalysis/ArgumentUsageAnalysis.cpp new file mode 100644 index 000000000..35dde1ed7 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/ArgumentUsageAnalysis.cpp @@ -0,0 +1,423 @@ +/// \file ArgumentUsageAnalysis.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Intrinsics.h" + +#include "ArgumentUsageAnalysis.h" + +static bool isVarArg(const llvm::Function &F) { + if (not F.isVarArg()) + return false; + + // Check if the function uses llvm.va_start + for (const llvm::Instruction &I : llvm::instructions(F)) + if (auto *Call = dyn_cast(&I)) + if (Call->getIntrinsicID() == llvm::Intrinsic::vastart) + return true; + + return false; +} + +namespace aua { + +void ArgumentUsageAnalysis::run() { + revng_log(Log, "Running ArgumentUsageAnalysis"); + LoggerIndent<> Indent(Log); + + // Analyze functions in post order + llvm::CallGraph CG(M); + for (llvm::CallGraphNode *Node : llvm::post_order(&CG)) { + llvm::Function *F = Node->getFunction(); + if (F != nullptr and not F->isDeclaration() and not isVarArg(*F)) + analyzeFunction(*F); + } +} + +void ArgumentUsageAnalysis::analyzeFunction(llvm::Function &F) { + revng_log(Log, "Analyzing " << F.getName()); + LoggerIndent<> Indent(Log); + + auto [It, New] = Results.insert({ &F, Function(TheContext) }); + revng_assert(New); + Function &FunctionResults = It->second; + + // Initialize arguments + for (auto &&[Index, Argument] : llvm::enumerate(F.args())) + FunctionResults.set(Argument, TheContext.getArgument(Index)); + + // Initialize the analysis with a conservative approach by associating each + // instruction to FunctionOf the arguments that might affect it. + taintAnalysis(FunctionResults, F); + + // At this point the results are correct and we could stop. + + // Do a single sweep of results refinement with more sophisticated + // expressions + { + revng_log(Log, "Running analyzeInstruction"); + LoggerIndent<> Indent(Log); + + // We do one sweep in reverse-post order. This means that, before we visit + // an instruction all of its operands will have been visited already. The + // only exception are "recursive" phis. For those, we accept to employ + // correct but suboptimal results compute by tainAnalysis. + llvm::ReversePostOrderTraversal RPOT(&F); + for (const llvm::BasicBlock *BB : RPOT) + for (const llvm::Instruction &I : *BB) + if (not I.isDebugOrPseudoInst()) + if (const Value *V = analyzeInstruction(FunctionResults, I)) + FunctionResults.set(I, *V); + } + + { + // Do a final pass to register memory accesses (load, store, memcpy), + // function calls and escaped arguments + revng_log(Log, "Running registerFunctionResults"); + LoggerIndent<> Indent(Log); + for (llvm::BasicBlock &BB : F) + for (llvm::Instruction &I : BB) + if (not I.isDebugOrPseudoInst()) + registerFunctionResults(FunctionResults, I); + + // Handle functions returning void or not returning at all + if (FunctionResults.returnValue() == nullptr) + FunctionResults.registerReturnValue(TheContext, TheContext.getUnknown()); + } +} + +void ArgumentUsageAnalysis::taintAnalysis(Function &FunctionResults, + const llvm::Function &F) { + revng_log(Log, "Running taint analysis"); + LoggerIndent<> Indent(Log); + + revng_assert(not isVarArg(F)); + revng_assert(F.arg_size() <= 64); + + // Create a map associating to each instruction a set of bits representing + // the set of arguments that affect it + llvm::DenseMap Taint; + for (const auto &[Index, Argument] : llvm::enumerate(F.args())) { + // Define the queue and helper lambda to enqueue more stuff + SmallVector Queue; + auto EnqueueUses = [&Queue](const llvm::Value &V) { + for (const llvm::Use &U : V.uses()) { + if (auto *I = dyn_cast(U.getUser())) { + if (not I->isDebugOrPseudoInst() and not isa(I) + and not I->getType()->isVoidTy()) { + Queue.push_back(&U); + } + } + } + }; + + // Initialize the queue with the uses of arguments + EnqueueUses(Argument); + + // Taint users of elements in the queue, and keep iterating until fixed + // point. Note that we only re-enqueue only if the set of + // arguments has grown. Given the number of arguments is finite, this will + // converge. + while (not Queue.empty()) { + auto *U = Queue.pop_back_val(); + auto *I = cast(U->getUser()); + + // If necessary, create an entry for I in the taint map + auto It = Taint.find(I); + if (It == Taint.end()) + It = Taint.insert({ I, 0 }).first; + + // In case of a call, if we already analyzed the callee, propagate from + // actual argument to return value only if, according to the current + // results, the return value is affected by the corresponding actual + // argument. + if (auto *Call = dyn_cast(I)) { + if (const Value *Result = getCallResult(*Call)) { + if (Call->isArgOperand(U)) { + unsigned ActualArgumentIndex = Call->getArgOperandNo(U); + if (not Result->collectArguments().contains(ActualArgumentIndex)) { + continue; + } + } + } + } + + uint64_t &MapEntry = It->second; + uint64_t OldValue = MapEntry; + MapEntry |= 1 << Index; + + // Re-enqueue only if the entry has changed + if (OldValue != MapEntry) + EnqueueUses(*I); + } + } + + // The analysis has terminated: we now know what arguments affect each + // instruction (modulo escaped arguments, but those are handled later) + + // Initialize all the tainted instructions with FunctionOf(Arg0, Arg3, ...) + for (const llvm::Instruction &I : llvm::instructions(&F)) { + if (I.getType()->isVoidTy()) + continue; + + SmallVector Values; + + auto It = Taint.find(&I); + if (It != Taint.end()) { + uint64_t ArgumentSet = It->second; + while (ArgumentSet) { + unsigned Index = llvm::findFirstSet(ArgumentSet); + Values.push_back(&TheContext.getArgument(Index)); + ArgumentSet &= (ArgumentSet - 1); + } + } + + FunctionResults.set(I, TheContext.getFunctionOf(std::move(Values))); + } +} + +const Value * +ArgumentUsageAnalysis::analyzeInstruction(Function &FunctionResults, + const llvm::Instruction &I) { + + if (I.getType()->isVoidTy()) + return nullptr; + + auto &C = TheContext; + auto Get = [&FunctionResults](const llvm::Value &V) -> const Value & { + return FunctionResults.get(V); + }; + + switch (I.getOpcode()) { + case llvm::Instruction::Load: + return nullptr; + + case llvm::Instruction::Add: + return &C.getAdd(Get(*I.getOperand(0)), Get(*I.getOperand(1))); + + case llvm::Instruction::Sub: + return &C.getSubtract(Get(*I.getOperand(0)), Get(*I.getOperand(1))); + + case llvm::Instruction::Mul: + return &C.getMultiply(Get(*I.getOperand(0)), Get(*I.getOperand(1))); + + case llvm::Instruction::GetElementPtr: { + const auto *GEP = cast(&I); + const llvm::Value *Base = GEP->getPointerOperand(); + llvm::Type *BaseType = GEP->getSourceElementType(); + const Value &BaseValue = Get(*Base); + + SmallVector Indices; + llvm::Type *NextType = BaseType; + + // Handle zero-th index + auto ElementSize = DL.getTypeAllocSize(GEP->getSourceElementType()); + llvm::Value &FirstIndex = **GEP->idx_begin(); + const Value &IndexValue = Get(FirstIndex); + const auto *Result = &C.getAdd(BaseValue, + C.getMultiply(IndexValue, + C.getConstant(ElementSize))); + + Indices.push_back(&FirstIndex); + NextType = llvm::GetElementPtrInst::getIndexedType(BaseType, Indices); + + // Iterate over other indices + for (const llvm::Use &IndexUse : skip(GEP->indices(), 1)) { + llvm::Value *Index = IndexUse.get(); + const Value &IndexValue = Get(*Index); + + if (auto *ArrayType = dyn_cast(NextType)) { + auto ElementSize = DL.getTypeAllocSize(NextType->getArrayElementType()); + auto &C = TheContext; + Result = &C.getAdd(*Result, + C.getMultiply(IndexValue, + C.getConstant(ElementSize))); + } else if (auto *StructType = dyn_cast(NextType)) { + auto FieldIndex = getLimitedValue(Index); + const llvm::StructLayout *Layout = DL.getStructLayout(StructType); + auto Offset = Layout->getElementOffset(FieldIndex); + Result = &C.getAdd(*Result, C.getConstant(Offset)); + } else { + revng_abort(); + } + + Indices.push_back(Index); + NextType = llvm::GetElementPtrInst::getIndexedType(BaseType, Indices); + } + + return Result; + } + + case llvm::Instruction::Call: + return handleCall(FunctionResults, *cast(&I)); + + case llvm::Instruction::PHI: { + // Handle phis by emitting an AnyOf with one entry per incoming of the phi + llvm::SmallVector Alternatives; + for (const llvm::Value *Incoming : I.operands()) + Alternatives.push_back(&Get(*Incoming)); + return &C.getAnyOf(std::move(Alternatives)); + } + + case llvm::Instruction::IntToPtr: + case llvm::Instruction::PtrToInt: + case llvm::Instruction::BitCast: + case llvm::Instruction::ZExt: + case llvm::Instruction::SExt: + case llvm::Instruction::Trunc: + // TODO: should we distinguish zero- vs sign-extending? + return &Get(*I.getOperand(0)); + + default: { + // Everything else is just FunctionOf the arguments used in its operands + llvm::SmallVector ArgumentsInOperand; + for (const llvm::Use &Operand : I.operands()) { + for (const ArgumentValue *Argument : + Get(*Operand.get()).collect()) { + ArgumentsInOperand.push_back(Argument); + } + } + return &C.getFunctionOf(std::move(ArgumentsInOperand)); + } + } +} + +void ArgumentUsageAnalysis::registerFunctionResults(Function &FunctionResults, + llvm::Instruction &I) { + auto Get = [&FunctionResults](const llvm::Value &V) -> const Value & { + return FunctionResults.get(V); + }; + + switch (I.getOpcode()) { + case llvm::Instruction::Load: + registerLoad(FunctionResults, *cast(&I)); + break; + + case llvm::Instruction::Store: + registerStore(FunctionResults, *cast(&I)); + break; + + case llvm::Instruction::Ret: + if (cast(&I)->getNumOperands() != 0) + FunctionResults.registerReturnValue(TheContext, Get(*I.getOperand(0))); + break; + + case llvm::Instruction::Call: { + auto *Call = cast(&I); + if ((Call->getIntrinsicID() == llvm::Intrinsic::memcpy + or Call->getIntrinsicID() == llvm::Intrinsic::memmove) + and isa(Call->getArgOperand(2))) { + registerMemcpy(FunctionResults, *Call); + } else if ((Call->getIntrinsicID() == llvm::Intrinsic::memset) + and isa(Call->getArgOperand(2))) { + registerWrite(FunctionResults, Call->getArgOperandUse(0)); + } else { + registerCall(FunctionResults, *Call); + } + } break; + + default: + break; + } +} + +void ArgumentUsageAnalysis::registerCall(Function &FunctionResults, + llvm::CallInst &Call) { + auto CallSite = analyzeCallSite(Call); + + if (Log.isEnabled()) { + Log << "registerCall: "; + dumpCall(Log, &Call); + Log << DoLog; + } + + LoggerIndent<> Indent(Log); + if (Log.isEnabled()) { + CallSite.dump(Log); + Log << DoLog; + } + + if (CallSite.IsNoReturn or (CallSite.IsDeclared and CallSite.IsPure)) { + // We do not consider noreturn calls and pure functions as escaping + revng_log(Log, "The callee is norerutrn or pure. Ignoring call."); + } else if (CallSite.IsIndirect or CallSite.IsDeclared + or not CallSite.HasBeenAnalyzed or CallSite.IsVarArg) { + revng_log(Log, "Can't handle this call."); + + for (const llvm::Value *Argument : Call.args()) { + const Value &Value = FunctionResults.get(*Argument); + FunctionResults.logEscapedValue("call " + getName(&Call), Value); + FunctionResults.registerEscapedValue(Call, Value); + } + } else { + revng_log(Log, "Registering."); + + // Register the call + const Function &CalleeResults = Results.at(CallSite.Callee); + aua::Call NewCall(Call, CalleeResults); + for (auto &&[Index, Argument] : llvm::enumerate(Call.args())) { + const Value &ArgumentValue = FunctionResults.get(*Argument.get()); + NewCall.registerActualArgument(Index, ArgumentValue); + } + + FunctionResults.registerCall(std::move(NewCall)); + } +} + +ArgumentUsageAnalysis::CallSite +ArgumentUsageAnalysis::analyzeCallSite(const llvm::CallInst &Call) { + CallSite Result; + + Result.Callee = getCalledFunction(&Call); + Result.IsIndirect = Result.Callee == nullptr; + Result.IsDeclared = not Result.IsIndirect and Result.Callee->isDeclaration(); + Result.IsVarArg = not Result.IsIndirect and isVarArg(*Result.Callee); + Result.HasBeenAnalyzed = Results.contains(Result.Callee); + Result.IsNoReturn = Call.doesNotReturn() + or (not Result.IsIndirect + and Result.Callee->doesNotReturn()); + Result.IsPure = Call.doesNotAccessMemory(); + return Result; +} + +const Value *ArgumentUsageAnalysis::getCallResult(const llvm::CallInst &Call) { + auto CallSite = analyzeCallSite(Call); + + if (CallSite.IsIndirect or CallSite.IsDeclared or not CallSite.HasBeenAnalyzed + or CallSite.IsVarArg) { + if (Log.isEnabled()) { + Log << "The following call cannot be handled "; + dumpCall(Log, &Call); + Log << "\n"; + CallSite.dump(Log); + Log << DoLog; + } + + // We have a call we cannot handle, return nullptr so we preserve the + // result of the taint analysis, which is conservative since it assumes + // the result is a FunctionOf all the arguments passed in. + return nullptr; + } + + return Results.at(CallSite.Callee).returnValue(); +} + +const Value *ArgumentUsageAnalysis::handleCall(Function &FunctionResults, + const llvm::CallInst &Call) { + // Compute result of the call + if (const Value *Result = getCallResult(Call)) { + llvm::DenseMap ActualArguments; + for (auto &&[ArgumentIndex, Argument] : llvm::enumerate(Call.args())) + ActualArguments[ArgumentIndex] = &FunctionResults.get(*Argument); + return TheContext.replaceArguments(*Result, std::move(ActualArguments)); + } + + // This means we haven't analyzed the function yet, leave the + // conservative value left by taintAnalysis + return nullptr; +} + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/ArgumentUsageAnalysis.h b/lib/HelperArgumentsAnalysis/ArgumentUsageAnalysis.h new file mode 100644 index 000000000..db1165baf --- /dev/null +++ b/lib/HelperArgumentsAnalysis/ArgumentUsageAnalysis.h @@ -0,0 +1,116 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include + +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/Analysis/CallGraph.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Module.h" + +#include "revng/Support/Debug.h" + +#include "Function.h" + +namespace aua { + +class ArgumentUsageAnalysis { +private: + struct CallSite { + llvm::Function *Callee = nullptr; + bool IsIndirect = false; + bool IsDeclared = false; + bool IsVarArg = false; + bool HasBeenAnalyzed = false; + bool IsNoReturn = false; + bool IsPure = false; + + template + void dump(O &Stream) const { + Stream << "IsIndirect: " << IsIndirect << "\n"; + Stream << "IsDeclared: " << IsDeclared << "\n"; + Stream << "IsVarArg: " << IsVarArg << "\n"; + Stream << "HasBeenAnalyzed: " << HasBeenAnalyzed << "\n"; + Stream << "IsNoReturn: " << IsNoReturn << "\n"; + Stream << "IsPure: " << IsPure << "\n"; + } + }; + +private: + Context &TheContext; + std::map Results; + llvm::Module &M; + const llvm::DataLayout &DL; + +public: + ArgumentUsageAnalysis(Context &TheContext, llvm::Module &M) : + TheContext(TheContext), M(M), DL(M.getDataLayout()) {} + +public: + auto begin() { return Results.begin(); } + auto end() { return Results.end(); } + + const Function &at(const llvm::Function *F) const { return Results.at(F); } + auto find(const llvm::Function *F) const { return Results.find(F); } + auto begin() const { return Results.begin(); } + auto end() const { return Results.end(); } + +public: + void run(); + +private: + void analyzeFunction(llvm::Function &F); + + CallSite analyzeCallSite(const llvm::CallInst &Call); + + void taintAnalysis(Function &FunctionResults, const llvm::Function &F); + + const Value *analyzeInstruction(Function &FunctionResults, + const llvm::Instruction &I); + + void registerFunctionResults(Function &FunctionResults, llvm::Instruction &I); + + void registerLoad(Function &FunctionResults, const llvm::LoadInst &Load) { + auto Size = DL.getTypeAllocSize(Load.getType()); + registerRead(FunctionResults, + Load.getOperandUse(Load.getPointerOperandIndex())); + } + + void registerRead(Function &FunctionResults, const llvm::Use &Location) { + const Value &PointerValue = FunctionResults.get(*Location.get()); + FunctionResults.registerAccess(Location, PointerValue); + } + + void registerStore(Function &FunctionResults, llvm::StoreInst &Store) { + auto Size = DL.getTypeAllocSize(Store.getValueOperand()->getType()); + registerWrite(FunctionResults, + Store.getOperandUse(Store.getPointerOperandIndex())); + + // Register escaped value from value operand + const Value &Value = FunctionResults.get(*Store.getValueOperand()); + FunctionResults.logEscapedValue("store " + getName(&Store), Value); + FunctionResults.registerEscapedValue(Store, Value); + } + + void registerWrite(Function &FunctionResults, const llvm::Use &Location) { + const Value &PointerValue = FunctionResults.get(*Location.get()); + FunctionResults.registerAccess(Location, PointerValue); + } + + void registerMemcpy(Function &FunctionResults, llvm::CallInst &Call) { + registerWrite(FunctionResults, Call.getArgOperandUse(0)); + registerRead(FunctionResults, Call.getArgOperandUse(1)); + } + + void registerCall(Function &FunctionResults, llvm::CallInst &Call); + + const Value *getCallResult(const llvm::CallInst &Call); + + const Value *handleCall(Function &FunctionResults, + const llvm::CallInst &Call); +}; + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/CMakeLists.txt b/lib/HelperArgumentsAnalysis/CMakeLists.txt new file mode 100644 index 000000000..cd651b626 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# + +revng_add_analyses_library_internal( + revngHelperArgumentsAnalysis + AnalyzeHelperArguments.cpp + Annotation.cpp + ArgumentUsageAnalysis.cpp + Context.cpp + CPULoopExitPass.cpp + CPUStateUsage.cpp + FixHelpers.cpp + SlimDownHelpersModule.cpp + Value.cpp) + +target_link_libraries(revngHelperArgumentsAnalysis revngLift revngSupport + ${LLVM_LIBRARIES}) diff --git a/lib/HelperArgumentsAnalysis/CPULoopExitPass.cpp b/lib/HelperArgumentsAnalysis/CPULoopExitPass.cpp new file mode 100644 index 000000000..e9f64b8f6 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/CPULoopExitPass.cpp @@ -0,0 +1,226 @@ +/// \file CPULoopExitPass.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/IR/Constants.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Metadata.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Value.h" + +#include "revng/HelperArgumentsAnalysis/CPULoopExitPass.h" +#include "revng/Support/Debug.h" +#include "revng/Support/IRHelpers.h" + +using namespace llvm; + +char CPULoopExitPass::ID = 0; + +static void purgeNoReturn(Function *F) { + auto &Context = F->getParent()->getContext(); + + if (F->hasFnAttribute(Attribute::NoReturn)) + F->removeFnAttr(Attribute::NoReturn); + + for (User *U : F->users()) { + if (auto *Call = dyn_cast(U)) { + if (Call->hasFnAttr(Attribute::NoReturn)) { + auto OldAttr = Call->getAttributes(); + auto NewAttr = OldAttr.removeFnAttribute(Context, Attribute::NoReturn); + Call->setAttributes(NewAttr); + } + } + } +} + +static ReturnInst *createRet(Instruction *Position) { + Function *F = Position->getParent()->getParent(); + purgeNoReturn(F); + + Type *ReturnType = F->getFunctionType()->getReturnType(); + if (ReturnType->isVoidTy()) { + return ReturnInst::Create(F->getParent()->getContext(), nullptr, Position); + } else if (ReturnType->isIntegerTy() or ReturnType->isPointerTy()) { + auto *Null = Constant::getNullValue(ReturnType); + return ReturnInst::Create(F->getParent()->getContext(), Null, Position); + } else if (ReturnType->isStructTy()) { + auto *StructTy = cast(ReturnType); + auto *Null = ConstantAggregateZero::get(StructTy); + return ReturnInst::Create(F->getParent()->getContext(), Null, Position); + } + + revng_abort("Return type not supported"); +} + +/// Find all calls to cpu_loop_exit and replace them with: +/// +/// * call invoke_handle_exception +/// * set cpu_loop_exiting = true +/// * return +/// +/// Then look for all the callers of the function calling cpu_loop_exit and make +/// them check whether they should return immediately (cpu_loop_exiting == true) +/// or not. +/// Then when we reach the root function, set cpu_loop_exiting to false after +/// the call. +bool CPULoopExitPass::runOnModule(llvm::Module &M) { + LLVMContext &Context = M.getContext(); + + // Replace uses of cpu_loop_exit_restore with cpu_loop_exit, some targets + // e.g. mips only use cpu_loop_exit_restore + Function *CpuLoopExitRestore = M.getFunction("cpu_loop_exit_restore"); + if (CpuLoopExitRestore != nullptr) { + Type *FirstArgumentType = CpuLoopExitRestore->getArg(0)->getType(); + Type *Void = Type::getVoidTy(Context); + auto CpuLoopExitCallee = M.getOrInsertFunction("cpu_loop_exit", + Void, + FirstArgumentType); + auto *CpuLoopExit = cast(CpuLoopExitCallee.getCallee()); + SmallVector ToErase; + for (User *U : CpuLoopExitRestore->users()) { + auto *Call = cast(U); + Value *CpuStateArg = Call->getArgOperand(0); + IRBuilder<> Builder(Call); + Value *NewCall = Builder.CreateCall(CpuLoopExit, { CpuStateArg }); + Call->replaceAllUsesWith(NewCall); + ToErase.push_back(Call); + } + + for (auto &V : ToErase) { + eraseFromParent(V); + } + } + + Function *CpuLoopExit = M.getFunction("cpu_loop_exit"); + // Nothing to do here + if (CpuLoopExit == nullptr) + return false; + + purgeNoReturn(CpuLoopExit); + + IntegerType *BoolType = Type::getInt1Ty(Context); + std::set FixedCallers; + GlobalVariable *CpuLoopExitingVariable = nullptr; + CpuLoopExitingVariable = new GlobalVariable(M, + BoolType, + false, + GlobalValue::CommonLinkage, + ConstantInt::getFalse(BoolType), + StringRef("cpu_loop_exiting")); + + Function *InvokeHandleException = M.getFunction("invoke_handle_exception"); + revng_assert(InvokeHandleException != nullptr); + + for (User *U : to_vector(CpuLoopExit->users())) { + auto *Call = cast(U); + revng_assert(Call->getCalledFunction() == CpuLoopExit); + Function *Caller = Call->getParent()->getParent(); + + // Call handle_exception + auto *CallCpuLoop = CallInst::Create(InvokeHandleException, + { Call->getArgOperand(0) }, + "", + Call); + + // In recent versions of LLVM you can no longer inject a CallInst in a + // Function with debug location if the call itself has not a debug location + // as well, otherwise module verification will fail + CallCpuLoop->setDebugLoc(Call->getDebugLoc()); + + // Set cpu_loop_exiting to true + new StoreInst(ConstantInt::getTrue(BoolType), CpuLoopExitingVariable, Call); + + // Return immediately + createRet(Call); + auto *Unreach = cast(&*(++Call->getIterator())); + eraseFromParent(Unreach); + + // Remove the call to cpu_loop_exit + eraseFromParent(Call); + + if (FixedCallers.contains(Caller)) + continue; + FixedCallers.insert(Caller); + + std::queue WorkList; + WorkList.push(Caller); + + while (!WorkList.empty()) { + Value *V = WorkList.front(); + WorkList.pop(); + + if (auto *F = dyn_cast(V)) + F->setMetadata("revng.cpu_loop_exits", MDTuple::get(Context, {})); + + for (User *User : V->users()) { + auto *Call = dyn_cast(User); + if (Call == nullptr) { + if (auto *Cast = dyn_cast(User)) { + revng_assert(Cast->getOperand(0) == V && Cast->isCast()); + WorkList.push(Cast); + continue; + } else if (isa(User)) { + continue; + } else if (auto *Store = dyn_cast(User)) { + // We're leaking a pointer to a function that we're instrumenting, + // fail at run-time. + CallInst::Create(M.getFunction("abort"), "", Store); + continue; + } else { + revng_abort("Unexpected user"); + } + } + + Function *RecCaller = Call->getParent()->getParent(); + + // TODO: make this more reliable than using function name + // If the caller is a QEMU helper function make it check + // cpu_loop_exiting and if it's true, make it return + + // Split BB + BasicBlock *OldBB = Call->getParent(); + BasicBlock::iterator SplitPoint = ++Call->getIterator(); + revng_assert(SplitPoint != OldBB->end()); + BasicBlock *NewBB = OldBB->splitBasicBlock(SplitPoint); + + // Add a BB with a ret + BasicBlock *QuitBB = BasicBlock::Create(Context, + "cpu_loop_exit_return", + RecCaller, + NewBB); + UnreachableInst *Temp = new UnreachableInst(Context, QuitBB); + createRet(Temp); + eraseFromParent(Temp); + + // Check value of cpu_loop_exiting + auto *Branch = cast(&*++(Call->getIterator())); + auto *PointeeTy = CpuLoopExitingVariable->getValueType(); + auto *Compare = new ICmpInst(Branch, + CmpInst::ICMP_EQ, + new LoadInst(PointeeTy, + CpuLoopExitingVariable, + "", + Branch), + ConstantInt::getTrue(BoolType)); + + BranchInst::Create(QuitBB, NewBB, Compare, Branch); + eraseFromParent(Branch); + + // Add to the work list only if it hasn't been fixed already + if (!FixedCallers.contains(RecCaller)) { + FixedCallers.insert(RecCaller); + WorkList.push(RecCaller); + } + } + } + } + + return true; +} + +using RegisterCLE = RegisterPass; +static RegisterCLE Z("cpu-loop-exit", "CPULoopExit Pass", false, false); diff --git a/lib/HelperArgumentsAnalysis/CPUStateUsage.cpp b/lib/HelperArgumentsAnalysis/CPUStateUsage.cpp new file mode 100644 index 000000000..7af175c4b --- /dev/null +++ b/lib/HelperArgumentsAnalysis/CPUStateUsage.cpp @@ -0,0 +1,596 @@ +/// \file CPUStateUsage.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include +#include + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Metadata.h" +#include "llvm/IR/ModuleSlotTracker.h" + +#include "revng/ADT/RecursiveCoroutine.h" +#include "revng/HelperArgumentsAnalysis/Annotation.h" +#include "revng/Support/IRHelpers.h" + +#include "CPUStateUsage.h" +#include "Function.h" + +namespace aua { + +static RecursiveCoroutine fromValueImpl(const Value &V) { + switch (V.kind()) { + case Value::Kind::Invalid: + revng_abort(); + case Value::Kind::Constant: + rc_return PointerSet::fromConstant(llvm::cast(V).value()); + + case Value::Kind::AnyOf: { + PointerSet Result; + for (const Value *Operand : llvm::cast(V).operands()) + Result.merge(rc_recur fromValueImpl(*Operand)); + rc_return Result; + } + + case Value::Kind::BinaryOperator: { + auto &Operator = llvm::cast(V); + PointerSet LHS = rc_recur fromValueImpl(Operator.firstOperand()); + PointerSet RHS = rc_recur fromValueImpl(Operator.secondOperand()); + + switch (Operator.type()) { + case BinaryOperatorValue::Invalid: + revng_abort(); + case BinaryOperatorValue::Add: + rc_return LHS.add(RHS); + case BinaryOperatorValue::Subtract: + rc_return LHS.add(RHS.negate()); + case BinaryOperatorValue::Multiply: + // TODO: we could restrict the possible values using SCEV/LVI + if (const int64_t *Value = LHS.getConstant()) + rc_return PointerSet::fromStrided(*Value); + else if (const int64_t *Value = RHS.getConstant()) + rc_return PointerSet::fromStrided(*Value); + else + rc_return PointerSet::unknown(); + } + } + + case Value::Kind::Argument: + rc_return PointerSet::unknown(); + case Value::Kind::FunctionOf: + rc_return PointerSet::fromStrided(1); + } +} + +PointerSet PointerSet::fromValue(const Value &V) { + return fromValueImpl(V); +} + +[[nodiscard]] PointerSet PointerSet::add(const PointerSet &Other) const { + PointerSet Result; + + for (int64_t LHS : Offsets) + for (int64_t RHS : Other.Offsets) + Result.Offsets.insert(LHS + RHS); + + std::set_union(Strides.begin(), + Strides.end(), + Other.Strides.begin(), + Other.Strides.end(), + std::inserter(Result.Strides, Result.Strides.end())); + + return Result; +} + +[[nodiscard]] std::string PointerSet::toString() const { + std::string Result = "{"; + for (int64_t Offset : Offsets) + Result += " " + std::to_string(Offset); + Result += " }"; + + for (auto &&[Index, Stride] : llvm::enumerate(Strides)) + Result += " + i" + std::to_string(Index) + " * " + std::to_string(Stride); + + return Result; +} + +std::optional> +CPUStateUsageAnalysis::computeAccessesInRoot(const Value &Offset) const { + llvm::DenseSet Result; + + revng_log(Log, "PointerSet: " << PointerSet::fromValue(Offset).toString()); + + auto Pointers = PointerSet::fromValue(Offset).enumerate(); + + revng_assert(Pointers.size() > 0); + + for (const PointerSet &Pointer : Pointers) { + revng_log(Log, "Considering " << Pointer.toString()); + LoggerIndent<> Indent(Log); + + // Collect the number of elements in arrays whose elements match the + // strides in Pointer + using namespace llvm; + APInt Offset(64, Pointer.offset()); + llvm::DenseMap StrideToArraySize; + + Type *CurrentType = &RootType; + std::optional MaybeIndex = APInt(32, 0); + while (MaybeIndex.has_value()) { + if (Log.isEnabled()) { + Log << "Considering offset " << Offset.getLimitedValue() << " in type "; + CurrentType->print(*Log.getAsLLVMStream(), true); + Log << DoLog; + } + + if (auto *Array = dyn_cast(CurrentType)) { + auto ElementSize = DL.getTypeAllocSize(Array->getElementType()); + revng_assert(StrideToArraySize.count(ElementSize) == 0); + auto Elements = Array->getArrayNumElements(); + revng_log(Log, + "Registering array of " << Elements << " elements of size " + << ElementSize); + StrideToArraySize[ElementSize] = Elements; + } else if (auto *Struct = dyn_cast(CurrentType)) { + auto Elements = Struct->getNumElements(); + if (Elements > 1) { + llvm::Type *FirstType = Struct->getElementType(0); + auto IsAsFirst = [FirstType](llvm::Type *ElementType) { + return ElementType == FirstType; + }; + if (llvm::all_of(Struct->elements(), IsAsFirst)) { + auto ElementSize = DL.getTypeAllocSize(FirstType); + revng_assert(StrideToArraySize.count(ElementSize) == 0); + revng_log(Log, + "Registering struct of " + << Elements << " elements of size " << ElementSize); + StrideToArraySize[ElementSize] = Elements; + } + } + } + + MaybeIndex = DL.getGEPIndexForOffset(CurrentType, Offset); + if (MaybeIndex) { + revng_log(Log, "Found at index " << MaybeIndex->getLimitedValue()); + } else { + revng_log(Log, "Not found"); + break; + } + } + + // Enumerate all the possible offsets considering all the arrays + struct ArrayEntry { + uint64_t Index = 0; + const uint64_t ArraySize = 0; + const int64_t Stride = 0; + }; + SmallVector WorkList; + + // Check if we found all the strides + if (Pointer.strides().size() > 0) { + + // Identify the strides we couldn't assign + SmallVector MissingStrides; + for (int64_t Stride : Pointer.strides()) { + auto It = StrideToArraySize.find(Stride); + if (It == StrideToArraySize.end()) { + revng_log(Log, + "Couldn't find an array with elements of size " + << Stride << ". Will try with a compatible array later."); + MissingStrides.push_back(Stride); + continue; + } + StrideToArraySize.erase(It); + WorkList.push_back({ 0, It->second, Stride }); + } + + // Try to assign to a compatible array, if it had a different array + // element size + for (int64_t Stride : MissingStrides) { + bool Found = false; + for (auto &&[ElementSize, Elements] : StrideToArraySize) { + auto ArraySize = Elements * ElementSize; + if (ArraySize % Stride == 0) { + WorkList.push_back({ 0, ArraySize / Stride, Stride }); + StrideToArraySize.erase(ElementSize); + revng_log(Log, + "Stride " << Stride << " assigned to an array of " + << Elements << " of size " << ElementSize); + Found = true; + break; + } + } + + if (not Found) { + revng_log(Log, + "Couldn't find an array with elements compatible with " + "stride " + << Stride); + return std::nullopt; + } + } + + } else { + WorkList.push_back({ 0, 1, 0 }); + } + + bool Done = WorkList.empty(); + while (not Done) { + // Compute new entry + int64_t NewOffset = Pointer.offset(); + for (ArrayEntry &Entry : WorkList) + NewOffset += Entry.Index * Entry.Stride; + Result.insert(NewOffset); + + // Move forward + Done = true; + for (ArrayEntry &Entry : WorkList) { + ++Entry.Index; + if (Entry.Index == Entry.ArraySize) { + Entry.Index = 0; + } else { + Done = false; + break; + } + } + } + } + + return Result; +} + +void CPUStateUsageAnalysis::analyze(llvm::Function &Function) { + llvm::Task T(2, "Analyze CPU state usage of " + Function.getName()); + FastValuePrinter Printer(*Function.getParent()); + revng_log(Log, "Collecting interprocedural data in " << Function.getName()); + LoggerIndent<> Indent(Log); + + revng_log(Log, "Collecting global results"); + T.advance("Collecting global results"); + CPUStateUsage HelperResult; + HelperResult.RawAUAResults = collectGlobalAUAResults(Function); + + T.advance("Processing arguments"); + revng_log(Log, "Processing arguments of " << Function.getName()); + LoggerIndent<> Indent2(Log); + + for (auto &&[ArgumentIndex, Argument] : llvm::enumerate(Function.args())) { + auto &Offsets = Initializer.getOffsetsFor(Argument); + SmallVector OffsetsValues; + for (uint64_t Offset : Offsets) + OffsetsValues.push_back(&TheContext.getConstant(Offset)); + + if (Offsets.size() == 0) + continue; + + if (Log.isEnabled()) { + Log << "Processing argument " << ArgumentIndex << " of " + << Function.getName() << " which can be at the following offsets:"; + for (uint64_t Offset : Offsets) { + Log << " " << Offset; + } + Log << DoLog; + } + + LoggerIndent<> Indent(Log); + + // If the argument is escaping, bail out + bool Escapes = false; + for (const EscapedArgument &Escaper : + HelperResult.RawAUAResults.EscapedArguments) { + // TODO: we could use lower_bound with + // EscapedArgument(nullptr, Escaper.index()); + if (Escaper.index() == ArgumentIndex) { + HelperCPUStateUsage[&Function] = CPUStateUsage::escapes(); + llvm::Instruction *I = &Escaper.location(); + Escaping.insert(I); + Escapes = true; + revng_log(Log, + "Argument escapes at " << Printer.toString(*I) << " in " + << Function.getName()); + } + } + + if (Escapes) + return; + + revng_log(Log, + "Consindering " << HelperResult.RawAUAResults.Accesses.size() + << " memory accesses"); + for (auto &[Access, Count] : HelperResult.RawAUAResults.Accesses) { + + if (not Access.start().collectArguments().contains(ArgumentIndex)) { + revng_log(Log, + "Ignoring memory access since it's not based on argument " + << ArgumentIndex); + continue; + } + + revng_log(Log, + "Considering access starting at " + << Access.start().toString() << " in " + << getName(Access.location())); + + llvm::DenseMap Replacements; + + auto &C = TheContext; + SmallVector Copy = OffsetsValues; + Replacements[ArgumentIndex] = &C.getAnyOf(std::move(Copy)); + const Value &Pointer = *C.replaceArguments(Access.start(), Replacements); + revng_log(Log, "Folding it to " << Pointer.toString()); + auto MaybeOffsets = computeAccessesInRoot(Pointer); + + if (not MaybeOffsets) { + auto *I = cast(Access.location().getUser()); + revng_log(Log, + "Marking argument as escaped: we couldn't compute the set " + "of offsets for " + << getName(I) << " in " + << I->getParent()->getParent()->getName()); + HelperCPUStateUsage[&Function] = CPUStateUsage::escapes(); + Escaping.insert(I); + return; + } + + revng_assert(MaybeOffsets->size() > 0); + + // Update the number of accesses this expands to + revng_assert(Count == 0); + Count = MaybeOffsets->size(); + + if (Log.isEnabled()) { + Log << "Offsets: {"; + for (uint64_t Offset : *MaybeOffsets) { + Log << " " << Offset; + } + Log << " }" << DoLog; + } + + auto Size = size(Access); + bool IsWrite = Access.isWrite(); + + for (uint64_t Offset : *MaybeOffsets) + MemoryAccessOffsets[&Access.location()].insert({ Offset, Size }); + + if (IsWrite) { + for (uint64_t Offset : *MaybeOffsets) { + HelperResult.Writes.insert({ Offset, Size }); + } + } else { + for (uint64_t Offset : *MaybeOffsets) { + HelperResult.Reads.insert({ Offset, Size }); + } + } + } + } + + // Commit results + HelperCPUStateUsage[&Function] = std::move(HelperResult); +} + +GlobalAUAResults +CPUStateUsageAnalysis::collectGlobalAUAResults(const llvm::Function &Function) { + llvm::Task T({}, "Collecting global Argument Usage Analysis results"); + FastValuePrinter Printer(*Function.getParent()); + + GlobalAUAResults Result; + const aua::Function &FunctionResults = AUA.at(&Function); + auto &C = TheContext; + + if (Log.isEnabled()) { + FunctionResults.dump(Log, ""); + Log << DoLog; + } + + // We are now going to collect interprocedural data for the requested + // function. This means that we'll integrate the information of the current + // function "inlining" all of the functions it calls directly or indirectly. + + // If we are going to visit a call with the same context as a previous + // visit, we'll stop, since it wouldn't provide any additional information. + // Moreover, in case of recursion, we'll turn the context into FunctionOf + // the used arguments so that the previous condition will be triggered for + // sure at the next iteration. + + for (auto &Entry : FunctionResults.localAccesses()) + Result.registerAccess(Entry); + + Result.EscapedArguments = FunctionResults.localEscapedArguments(); + + unsigned Iterations = 0; + std::unordered_set VisitedCalls; + struct QueueEntry { + aua::Call Call; + llvm::DenseSet FunctionsInStack; + }; + SmallVector Queue; + + // Initialize queue + for (const aua::Call &Call : FunctionResults.calls()) + Queue.push_back({ Call, {} }); + + // TODO: In this loop, we analyze many many times the same function with the + // same context. + // Some data suggests that we could go from considering 2496029 calls to + // just 18014 distinct calls. We'd benefit from having a cache of + // analysis results. + while (not Queue.empty()) { + ++Iterations; + const auto &[Current, FunctionsInStack] = Queue.pop_back_val(); + auto &Callee = Current.callee(); + auto &ActualArguments = Current.actualArguments(); + auto SimplifiedActualArguments = ActualArguments; + auto CalleeName = getCalledFunction(&Current.callInstruction())->getName(); + T.advance(CalleeName); + + for (auto &&[_, SimplifiedActualArgument] : SimplifiedActualArguments) { + SmallVector Arguments; + llvm::copy(SimplifiedActualArgument->collect(), + std::back_inserter(Arguments)); + SimplifiedActualArgument = &C.getFunctionOf(std::move(Arguments)); + } + + VisitedCalls.insert(Current); + + auto NewFunctionsInStack = FunctionsInStack; + NewFunctionsInStack.insert(&Callee); + + bool IsRecursiveCall = FunctionsInStack.contains(&Callee); + + if (Log.isEnabled()) { + Log << "Processing"; + if (IsRecursiveCall) + Log << " recursive"; + Log << " call:\n"; + Current.dump(Log, " ", false); + Log << " Callee:\n"; + Callee.dump(Log, " "); + Log << DoLog; + } + LoggerIndent<> Indent(Log); + + // Register escaped arguments replacing arguments + for (const EscapedArgument &EscapedArgument : + Callee.localEscapedArguments()) { + const auto &Adjusted = *C.replaceArguments(C.getArgument(EscapedArgument + .index()), + ActualArguments); + for (unsigned ArgumentIndex : Adjusted.collectArguments()) { + // If an argument is not pointing into the tracked data structure, + // ignore the fact that's escaping + if (not Initializer.pointsIntoStruct(*Function.getArg(ArgumentIndex))) + continue; + + bool New = Result.EscapedArguments + .insert({ EscapedArgument.location(), ArgumentIndex }) + .second; + if (New) { + revng_log(Log, + "Argument " + << ArgumentIndex << " of " << Function.getName().str() + << " escapes from " + << Printer.toString(EscapedArgument.location()) << " in " + << EscapedArgument.location().getFunction()->getName()); + } + } + } + + // Register memory accesses replacing arguments + for (const MemoryAccess &Access : Callee.localAccesses()) { + const auto &Adjusted = *C.replaceArguments(Access.start(), + ActualArguments); + MemoryAccess AdjustedAccess = Access.replaceStart(Adjusted); + revng_log(Log, + "Registering access " << Access.toString() << " as " + << AdjustedAccess.toString() << " at " + << getName(Access.location())); + Result.registerAccess(AdjustedAccess); + } + + // Enqueue calls replacing arguments + for (const aua::Call &InnerCall : Callee.calls()) { + auto AdjustedCall = InnerCall; + + const llvm::DenseMap + *ArgumentsMap = IsRecursiveCall ? &SimplifiedActualArguments : + &ActualArguments; + + for (auto &&[_, ActualArgument] : AdjustedCall.actualArguments()) { + ActualArgument = C.replaceArguments(*ActualArgument, *ArgumentsMap); + } + + if (not VisitedCalls.contains(AdjustedCall)) { + revng_log(Log, + "Adding call " << getName(&AdjustedCall.callInstruction())); + Queue.push_back({ std::move(AdjustedCall), NewFunctionsInStack }); + } + } + } + + revng_log(Log, "Collect performed " << Iterations << " iterations"); + + return Result; +} + +void CPUStateUsageAnalysis::annotate(llvm::Module &M) const { + // Annotate functions + for (auto &&[Function, Usage] : HelperCPUStateUsage) + Annotation(Usage.Escapes, Usage.Reads, Usage.Writes).serialize(*Function); + + // Collect instruction annotations + std::map InstructionAnnotations; + + for (auto &&[Use, Access] : MemoryAccessOffsets) { + auto *I = cast(Use->getUser()); + if (MemoryAccess::isWrite(Use)) + InstructionAnnotations[I].Writes = Access; + else + InstructionAnnotations[I].Reads = Access; + } + + for (llvm::Instruction *I : Escaping) + InstructionAnnotations[I].Escapes = true; + + // Annotate instructions + for (auto &&[ToAnnotate, Annotation] : InstructionAnnotations) + Annotation.serialize(*ToAnnotate); +} + +void StructPointers::visitType(llvm::Type &Type, uint64_t StartingOffset) { + // Note: this function is recursive but its depth is limited by build time + // features, i.e., the depth of the CPU state. + if (auto *Struct = dyn_cast(&Type)) { + revng_log(Log, + "Registering an instance of " << Struct->getName() + << " at offset " << StartingOffset); + LoggerIndent<> Indent(Log); + if (Struct->getName().size() != 0) + OffsetsOfStructs[Struct].push_back(StartingOffset); + + const llvm::StructLayout *Layout = DL.getStructLayout(Struct); + for (unsigned Index = 0; Index < Struct->getNumElements(); ++Index) { + visitType(*Struct->getTypeAtIndex(Index), + StartingOffset + Layout->getElementOffset(Index)); + } + } else if (auto *Array = dyn_cast(&Type)) { + auto ElementsCount = Array->getNumElements(); + revng_log(Log, "Handling an array of " << ElementsCount << " elements"); + LoggerIndent<> Indent(Log); + auto &ElementType = *Array->getElementType(); + auto ElementSize = DL.getTypeAllocSize(&ElementType); + for (unsigned I = 0; I < ElementsCount; ++I) + visitType(ElementType, StartingOffset + I * ElementSize); + } +} + +void StructPointers::propagateFromActualArguments() { + bool Again = true; + + while (Again) { + Again = false; + SmallVector> ToAdd; + for (auto &&[Value, Struct] : Pointers) { + auto *Argument = dyn_cast(Value); + if (Argument == nullptr) + continue; + + for (llvm::CallBase *Call : callers(Argument->getParent())) { + auto *V = Call->getArgOperand(Argument->getArgNo()); + if (Pointers.count(V) != 0) + continue; + + ToAdd.emplace_back(V, Struct); + Again = true; + } + } + for (auto &&[Value, Struct] : ToAdd) + Pointers[Value] = Struct; + } +} + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/CPUStateUsage.h b/lib/HelperArgumentsAnalysis/CPUStateUsage.h new file mode 100644 index 000000000..c263a3b77 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/CPUStateUsage.h @@ -0,0 +1,346 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "revng/Support/FastValuePrinter.h" + +#include "ArgumentUsageAnalysis.h" + +namespace aua { + +class PointerSet { +private: + std::set Offsets; + std::set Strides; + +public: + const int64_t *getConstant() const { + if (Strides.size() == 0 and Offsets.size() == 1) + return &*Offsets.begin(); + return nullptr; + } + + int64_t offset() const { + revng_assert(Offsets.size() == 1); + return *Offsets.begin(); + } + + auto offsets() const { return Offsets; } + auto strides() const { return Strides; } + +public: + static PointerSet none() { return PointerSet(); } + + static PointerSet fromConstant(int64_t Offset) { + PointerSet Result; + Result.Offsets.insert(Offset); + return Result; + } + + static PointerSet fromStrided(int64_t Stride) { + PointerSet Result = fromConstant(0); + revng_assert(Stride > 0); + Result.Strides.insert(Stride); + return Result; + } + + static PointerSet unknown() { return fromStrided(1); } + + static PointerSet fromValue(const Value &V); + +public: + void merge(const PointerSet &Other) { + if (Strides != Other.Strides) { + *this = unknown(); + return; + } + + Offsets.insert(Other.Offsets.begin(), Other.Offsets.end()); + } + + SmallVector enumerate() const { + SmallVector Result; + + for (int64_t Offset : Offsets) { + PointerSet New; + New.Strides = Strides; + New.Offsets.insert(Offset); + Result.push_back(std::move(New)); + } + + return Result; + } + +public: + [[nodiscard]] PointerSet add(const PointerSet &Other) const; + + [[nodiscard]] PointerSet negate() const { + PointerSet Result; + Result.Strides = Strides; + for (int64_t Value : Offsets) + Result.Offsets.insert(-Value); + return Result; + } + +public: + [[nodiscard]] std::string toString() const; +}; + +struct GlobalAUAResults { + /// The value of the map is the number of accesses it is expanded to + std::map Accesses; + std::set EscapedArguments; + + void registerAccess(const MemoryAccess &Access) { + if (Access.start().collect().size() > 0) + Accesses.insert({ Access, 0 }); + } +}; + +using OffsetAndSize = std::pair; + +struct CPUStateUsage { + GlobalAUAResults RawAUAResults; + + llvm::DenseSet Reads; + llvm::DenseSet Writes; + bool Escapes = false; + +public: + template + void dump(O &Output, llvm::StringRef Prefix) const { + Output << Prefix.str() << "CPU State " << (Escapes ? "does" : "does not") + << " escape\n"; + + Output << Prefix.str() << "Read offsets: {"; + const char *ListPrefix = ""; + for (const auto &[Offset, Size] : Reads) { + Output << ListPrefix << "i" << (Size * 8) << " @ " << Offset; + ListPrefix = ", "; + } + Output << " }\n"; + + Output << Prefix.str() << "Written offsets: {"; + ListPrefix = ""; + for (const auto &[Offset, Size] : Writes) { + Output << " i" << (Size * 8) << " @ " << Offset; + ListPrefix = ", "; + } + Output << " }\n"; + + if (RawAUAResults.Accesses.size() > 0) { + Output << Prefix.str() << "Global accesses:\n"; + for (const auto &[Access, Count] : RawAUAResults.Accesses) + Output << Prefix.str() << " " << Access.toString() << " (" << Count + << "x)" + << "\n"; + } + + if (RawAUAResults.EscapedArguments.size() > 0) { + Output << Prefix.str() << "Global escaped arguments: {"; + for (const EscapedArgument &EscapedArgument : + RawAUAResults.EscapedArguments) + Output << " " << EscapedArgument.toString(); + Output << " }\n"; + } + } + +public: + static CPUStateUsage escapes() { + CPUStateUsage Result; + Result.Escapes = true; + return Result; + } +}; + +class StructPointers { +private: + const llvm::Module &M; + const llvm::DataLayout &DL; + std::map> OffsetsOfStructs; + llvm::DenseMap Pointers; + +public: + StructPointers(const llvm::Module &M, llvm::StructType &Struct) : + M(M), DL(M.getDataLayout()) { + revng_log(Log, "Analyzing CPU struct"); + LoggerIndent Indent(Log); + visitType(Struct, 0); + } + +public: + void registerPointer(llvm::Value &Pointer, llvm::StructType &Pointee) { + if (OffsetsOfStructs.contains(&Pointee)) + Pointers[&Pointer] = &Pointee; + } + + void propagateFromActualArguments(); + + bool pointsIntoStruct(llvm::Value &V) const { + return Pointers.count(&V) != 0; + } + + const SmallVector &getOffsetsFor(llvm::Value &V) const { + static SmallVector Empty; + auto It = Pointers.find(&V); + if (It == Pointers.end()) + return Empty; + else + return OffsetsOfStructs.at(It->second); + } + +public: + template + void dump(O &Output) { + FastValuePrinter FVP(M); + for (auto &&[V, Struct] : Pointers) { + std::string Function = ""; + if (auto *I = dyn_cast(V)) + Function = " in function " + I->getFunction()->getName().str(); + else if (auto *Argument = dyn_cast(V)) + Function = " in function " + Argument->getParent()->getName().str(); + + Output << "Value " << FVP.toString(*V) << Function << " is a pointer to " + << Struct->getName().str() + << " which is present at the following offsets: "; + for (uint64_t Offset : OffsetsOfStructs.at(Struct)) + Output << " " << Offset; + Output << "\n"; + } + } + + void dump() debug_function { dump(dbg); } + +private: + void visitType(llvm::Type &Type, uint64_t StartingOffset); +}; + +class CPUStateUsageAnalysis { +private: + Context &TheContext; + const ArgumentUsageAnalysis &AUA; + const llvm::DataLayout &DL; + llvm::Type &RootType; + + /// These are information about usage of CPU state by a specific helper. + std::map HelperCPUStateUsage; + + /// These are the set of parts of the CPU state that each memory access could + /// touch. + std::map> + MemoryAccessOffsets; + + /// These are the set of memory accesses where the CPU state escapes. + /// This takes precedence over MemoryAccessOffsets. + llvm::DenseSet Escaping; + + StructPointers Initializer; + +public: + CPUStateUsageAnalysis(Context &TheContext, + const ArgumentUsageAnalysis &AUA, + const llvm::DataLayout &DL, + llvm::Type &RootType, + StructPointers &&Initializer) : + TheContext(TheContext), + AUA(AUA), + DL(DL), + RootType(RootType), + Initializer(std::move(Initializer)) {} + +public: + CPUStateUsage *get(llvm::Function &F) { + auto It = HelperCPUStateUsage.find(&F); + if (It == HelperCPUStateUsage.end()) + return nullptr; + return &It->second; + } + + const llvm::DenseSet> & + getOffsets(const llvm::Use &U) const { + static llvm::DenseSet> Empty; + auto It = MemoryAccessOffsets.find(&U); + if (It == MemoryAccessOffsets.end()) + return Empty; + return It->second; + } + + bool isEscaping(const llvm::Instruction &I) const { + return Escaping.contains(&I); + } + +public: + void analyze(llvm::Function &Function); + + void registerAsEscaping(llvm::Function &Function) { + HelperCPUStateUsage[&Function] = CPUStateUsage::escapes(); + } + +public: + void annotate(llvm::Module &M) const; + +public: + template + void dumpStats(O &Stream, llvm::StringRef Prefix) const { + for (auto &&[Function, Usage] : HelperCPUStateUsage) { + Stream << Prefix.str() << Function->getName().str() << ": "; + if (Usage.Escapes) { + Stream << "escapes"; + } else { + Stream << "reads " << Usage.Reads.size() << " fields and "; + Stream << "writes " << Usage.Writes.size() << " fields."; + } + Stream << "\n"; + + std::map> CalleeStats; + for (auto &[Access, Count] : Usage.RawAUAResults.Accesses) { + if (auto *I = dyn_cast(Access.location() + .getUser())) { + if (Access.isWrite()) + CalleeStats[I->getFunction()].second += Count; + else + CalleeStats[I->getFunction()].first += Count; + } + } + + for (auto &[F, P] : CalleeStats) { + auto [ReadCount, WriteCount] = P; + Stream << Prefix.str() << " " << F->getName().str() << ": reads " + << ReadCount << " fields and writes " << WriteCount + << " fields.\n"; + } + } + } + +private: + unsigned size(llvm::Type &Type) const { + if (auto *IntegerType = dyn_cast(&Type)) + return IntegerType->getIntegerBitWidth() / 8; + else if (isa(&Type)) + return DL.getPointerTypeSize(&Type); + revng_abort(); + } + + unsigned size(const aua::MemoryAccess &Access) const { + llvm::User *U = Access.location().getUser(); + if (auto *Store = dyn_cast(U)) { + return size(*Store->getValueOperand()->getType()); + } else if (auto *Load = dyn_cast(U)) { + return size(*Load->getType()); + } else if (auto *Call = dyn_cast(U)) { + // memcpy, memmove, memset + return cast(Call->getArgOperand(2))->getLimitedValue(); + } + + revng_abort(); + } + + GlobalAUAResults collectGlobalAUAResults(const llvm::Function &Function); + + std::optional> + computeAccessesInRoot(const Value &Offset) const; +}; + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/Context.cpp b/lib/HelperArgumentsAnalysis/Context.cpp new file mode 100644 index 000000000..cf7d5fe4e --- /dev/null +++ b/lib/HelperArgumentsAnalysis/Context.cpp @@ -0,0 +1,44 @@ +/// \file Context.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "revng/ADT/RecursiveCoroutine.h" + +#include "Context.h" + +namespace aua { + +RecursiveCoroutine +Context::replaceArguments(const Value &Original, + const llvm::DenseMap + &NewArguments) { + + if (auto *Argument = llvm::dyn_cast(&Original)) { + auto It = NewArguments.find(Argument->index()); + if (It != NewArguments.end()) + rc_return It->second; + else + rc_return &Original; + } + + SmallVector NewOperandList; + for (const Value *V : Original.Operands) { + const Value *Replacement = rc_recur replaceArguments(*V, NewArguments); + NewOperandList.push_back(Replacement); + } + + if (NewOperandList == Original.Operands) { + rc_return &Original; + } else { + rc_return Original.upcast([this, &NewOperandList](auto &Upcasted) + -> const Value * { + auto Copy = Upcasted; + Copy.Operands = NewOperandList; + return &get(std::move(Copy)); + }); + } +} + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/Context.h b/lib/HelperArgumentsAnalysis/Context.h new file mode 100644 index 000000000..fb1f452d0 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/Context.h @@ -0,0 +1,240 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include + +#include "revng/ADT/RecursiveCoroutine.h" + +#include "Value.h" + +template +bool compareByDereferencing(const T *LHS, const T *RHS) { + revng_assert(LHS != nullptr); + revng_assert(RHS != nullptr); + return *LHS < *RHS; +} + +template +bool equalByDereferencing(const T *LHS, const T *RHS) { + revng_assert(LHS != nullptr); + revng_assert(RHS != nullptr); + return *LHS == *RHS; +} + +template +struct HashByDereferencing { + std::size_t operator()(const T *Value) const noexcept { + revng_assert(Value != nullptr); + return std::hash(*Value); + } +}; + +namespace aua { + +class Context { +private: + std::set) *> Values; + +public: + Context() : Values(compareByDereferencing) {} + + ~Context() { + // We manually delete the value to avoid having a virtual destructor in + // Value. A virtual method would introduce a virtual table, which is + // redundant given we use LLVM RTTI. + for (Value *Entry : Values) + Entry->deleteValue(); + } + +private: + template + const Value &get(T &&Object) { + + auto IsArgument = [](const Value *Value) { + return llvm::isa(Value); + }; + auto IsFunctionOf = [](const Value *Value) { + return llvm::isa(Value); + }; + auto IsAnyOf = [](const Value *Value) { + return llvm::isa(Value); + }; + + switch (Object.kind()) { + case Value::Kind::Invalid: + revng_abort(); + case Value::Kind::AnyOf: { + Object.deduplicate(); + + if (llvm::any_of(Object.Operands, IsFunctionOf)) { + SmallVector Operands; + llvm::copy(Object.template collect(), + std::back_inserter(Operands)); + return get(FunctionOfValue(std::move(Operands))); + } + + // Optimize AnyOf(AnyOf(...), ...) + if (llvm::any_of(Object.Operands, IsAnyOf)) { + SmallVector MergedValues; + + for (const Value *Value : Object.Operands) { + if (llvm::isa(Value)) { + llvm::copy(Value->Operands, std::back_inserter(MergedValues)); + } else { + MergedValues.push_back(Value); + } + } + + return get(AnyOfValue(std::move(MergedValues))); + } + + } break; + case Value::Kind::Argument: + break; + case Value::Kind::Constant: + break; + case Value::Kind::BinaryOperator: { + auto &BinaryOperator = llvm::cast(Object); + auto *ConstantLHS = llvm::dyn_cast(Object.Operands[0]); + auto *ConstantRHS = llvm::dyn_cast(Object.Operands[1]); + + switch (BinaryOperator.type()) { + case BinaryOperatorValue::Invalid: + revng_abort(); + + case BinaryOperatorValue::Add: + case BinaryOperatorValue::Subtract: + if (ConstantLHS != nullptr and ConstantLHS->value() == 0) { + return *BinaryOperator.Operands[1]; + } else if (ConstantRHS != nullptr and ConstantRHS->value() == 0) { + return *BinaryOperator.Operands[0]; + } else if (ConstantLHS != nullptr and ConstantRHS != nullptr) { + // TODO: should we consider zero- vs sign-extension? + switch (BinaryOperator.type()) { + case BinaryOperatorValue::Add: + return get(ConstantValue(ConstantLHS->value() + + ConstantRHS->value())); + case BinaryOperatorValue::Subtract: + return get(ConstantValue(ConstantLHS->value() + - ConstantRHS->value())); + default: + revng_abort(); + } + } + break; + case BinaryOperatorValue::Multiply: + if (ConstantLHS != nullptr and ConstantLHS->value() == 0) { + return *Object.Operands[0]; + } else if (ConstantLHS != nullptr and ConstantLHS->value() == 1) { + return *Object.Operands[1]; + } else if (ConstantRHS != nullptr and ConstantRHS->value() == 0) { + return *Object.Operands[1]; + } else if (ConstantRHS != nullptr and ConstantRHS->value() == 1) { + return *Object.Operands[0]; + } else if (ConstantLHS != nullptr and ConstantRHS != nullptr) { + return get(ConstantValue(ConstantLHS->value() + * ConstantRHS->value())); + } + break; + } + + } break; + case Value::Kind::FunctionOf: + Object.deduplicate(); + + // Optimize FunctionOf(FunctionOf(...), ...) + if (llvm::any_of(Object.Operands, IsFunctionOf)) { + SmallVector MergedValues; + + for (const Value *Value : Object.Operands) { + if (llvm::isa(Value)) { + llvm::copy(Value->Operands, std::back_inserter(MergedValues)); + } else { + MergedValues.push_back(Value); + } + } + + return get(FunctionOfValue(std::move(MergedValues))); + } + + if (not llvm::all_of(Object.Operands, IsArgument)) { + auto Result = Object; + + llvm::DenseSet UsedArguments; + for (const Value *Operand : Object.Operands) { + if (auto *Argument = llvm::dyn_cast(Operand)) { + UsedArguments.insert(Argument); + } else { + for (const ArgumentValue *Argument : + Operand->collect()) { + UsedArguments.insert(Argument); + } + } + } + + Result.Operands.clear(); + for (const ArgumentValue *Argument : UsedArguments) + Result.Operands.push_back(Argument); + + return get(Result); + } + break; + } + + auto It = Values.find(&Object); + + // If it's the first time we see this object, copy it on the heap + if (It == Values.end()) { + It = Values.insert(It, Object.upcast([](auto &Upcasted) -> Value * { + using P = std::decay_t; + return new P(Upcasted); + })); + } + + return **It; + } + +public: + const Value &getAnyOf(SmallVector &&Values) { + return get(AnyOfValue(std::move(Values))); + } + + const Value &getUnknown() { return getFunctionOf({}); } + + const Value &getFunctionOf(SmallVector &&Values) { + return get(FunctionOfValue(std::move(Values))); + } + + const Value &getArgument(uint64_t Index) { return get(ArgumentValue(Index)); } + + const Value &getConstant(uint64_t Value) { return get(ConstantValue(Value)); } + + const Value &getAdd(const Value &FirstValue, const Value &SecondValue) { + return get(BinaryOperatorValue(BinaryOperatorValue::Add, + FirstValue, + SecondValue)); + } + + const Value &getSubtract(const Value &FirstValue, const Value &SecondValue) { + return get(BinaryOperatorValue(BinaryOperatorValue::Subtract, + FirstValue, + SecondValue)); + } + + const Value &getMultiply(const Value &FirstValue, const Value &SecondValue) { + return get(BinaryOperatorValue(BinaryOperatorValue::Multiply, + FirstValue, + SecondValue)); + } + +public: + RecursiveCoroutine + replaceArguments(const Value &Original, + const llvm::DenseMap &NewArguments); +}; + +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/FixHelpers.cpp b/lib/HelperArgumentsAnalysis/FixHelpers.cpp new file mode 100644 index 000000000..8c11b6249 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/FixHelpers.cpp @@ -0,0 +1,453 @@ +/// \file FixHelpers.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include +#include +#include +#include +#include + +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalValue.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Support/NativeFormatting.h" + +#include "revng/HelperArgumentsAnalysis/Annotation.h" +#include "revng/Lift/LibTcg.h" +#include "revng/Lift/VariableManager.h" +#include "revng/Support/CommandLine.h" +#include "revng/Support/Debug.h" +#include "revng/Support/IRBuilder.h" +#include "revng/Support/IRHelpers.h" + +#include "revng/Model/Generated/Early/Architecture.h" + +using namespace llvm; + +using std::string; + +static Logger<> Log("fix-helpers"); + +static cl::opt ArchitectureName("fix-helpers-architecture", + cl::desc("architecture of the helper " + "of the input file for " + "fix-helpers."), + cl::cat(MainCategory)); + +static void setMetadata(VariableManager &Variables, + Function &F, + StringRef MetadataName, + const aua::Annotation::OffsetAndSizeSet &Offsets) { + revng_log(Log, "Parsing " << MetadataName << ":"); + LoggerIndent<> Indent(Log); + + QuickMetadata QMD(getContext(&F)); + SmallVector CSVList; + + for (auto [Offset, Size] : Offsets) { + int32_t RemainingSize = Size; + while (RemainingSize != 0) { + auto [CSV, + OffsetInField] = Variables.getByCPUStateOffsetWithRemainder(Offset); + + if (CSV == nullptr) { + revng_log(Log, "Couldn't find CSV for offset " << Offset); + break; + } + + revng_log(Log, CSV->getName()); + + revng_assert(CSV->getParent() == F.getParent()); + + auto CSVSize = CSV->getValueType()->getIntegerBitWidth() / 8; + CSVList.push_back(QMD.get(CSV->getName())); + + // If necessary, move on to the next field + int64_t AvailableSize = CSVSize - OffsetInField; + RemainingSize = std::max(0L, RemainingSize - AvailableSize); + Offset = 0; + } + } + + if (Offsets.size() > 0) { + revng_log(Log, + "Identified " << CSVList.size() << " CSVs out of " + << Offsets.size() << " offsets"); + } + + MDNode *MDAnnotation = QMD.tuple({ QMD.get(0), QMD.tuple(CSVList) }); + F.setMetadata(MetadataName, MDAnnotation); +} + +static void convertToCSVAnnotation(VariableManager &Variables, + Function &F, + const aua::Annotation &Annotation) { + revng_log(Log, "Converting CSV to annotations"); + LoggerIndent<> Indent(Log); + setMetadata(Variables, F, "revng.csvaccess.offsets.load", Annotation.Reads); + setMetadata(Variables, F, "revng.csvaccess.offsets.store", Annotation.Writes); +} + +class AccessFixer { +public: + enum AccessType { + Read, + Write + }; + +private: + VariableManager &Variables; + Type &IntPtrType; + revng::NonDebugInfoCheckingIRBuilder Builder; + Function &Abort; + +public: + AccessFixer(Module &M, VariableManager &Variables, Type &IntPtrType) : + Variables(Variables), + IntPtrType(IntPtrType), + Builder(IntPtrType.getContext()), + Abort(notNull(M.getFunction("abort"))) {} + +public: + void fixMemoryAccess(Instruction &I, const aua::Annotation &Annotation); + + CallInst *emitAbort(Function &F) { + Builder.SetInsertPointPastAllocas(&F); + auto *AbortCall = Builder.CreateCall(&Abort); + AbortCall->setMetadata("dbg", nullptr); + return AbortCall; + } + +private: + template + void handle(Instruction &I, const aua::Annotation::OffsetAndSizeSet &Targets); + + template + Value *emit(Instruction &I, uint64_t Offset, uint64_t Size); + + CallInst *emitAbort(revng::IRBuilder &Builder) { + return Builder.CreateCall(&Abort); + } + + CallInst *emitAbort(Instruction &Before) { + Builder.SetInsertPoint(&Before); + return emitAbort(Builder); + } + + std::pair decomposeMemcpy(llvm::Instruction &I); + + template + std::pair getOffsetAndSize(Instruction &I); +}; + +template<> +Value *AccessFixer::emit(Instruction &I, + uint64_t Offset, + uint64_t Size) { + revng_log(Log, "Handling a write to " << Offset << " of size " << Size); + + if (auto *Store = dyn_cast(&I)) { + Value *ToStore = Store->getValueOperand(); + + auto MaybeStore = Variables.storeToCPUStateOffset(Builder, + Size, + Offset, + ToStore); + if (MaybeStore) { + return *MaybeStore; + } else { + // storeToCPUStateOffset can fail in case of padding and the like + revng_log(Log, "Nothing emitted"); + return nullptr; + } + } else if (auto *Call = dyn_cast(&I)) { + Variables.memOpAtCPUStateOffset(Builder, Call, Offset, false); + return nullptr; + } else { + revng_abort(); + } +} + +template<> +Value *AccessFixer::emit(Instruction &I, + uint64_t Offset, + uint64_t Size) { + revng_log(Log, "Handling a read from " << Offset << " of size " << Size); + if (auto *Load = dyn_cast(&I)) { + if (auto *Result = Variables.loadFromCPUStateOffset(Builder, + Size, + Offset)) { + return Result; + } else { + // loadFromCPUStateOffset can fail in case of padding and the like + revng_log(Log, "Nothing emitted"); + return llvm::UndefValue::get(I.getType()); + } + } else if (auto *Call = dyn_cast(&I)) { + Variables.memOpAtCPUStateOffset(Builder, Call, Offset, true); + return nullptr; + + } else { + revng_abort(); + } +} + +template<> +std::pair +AccessFixer::getOffsetAndSize(Instruction &I) { + uint64_t Size = 0; + Value *Address = nullptr; + if (auto *Store = dyn_cast(&I)) { + Address = Store->getPointerOperand(); + Size = Store->getValueOperand()->getType()->getIntegerBitWidth() / 8; + } else if (auto *Call = dyn_cast(&I)) { + Address = Call->getArgOperand(0); + Size = cast(Call->getOperand(2))->getLimitedValue(); + } else { + revng_abort(); + } + return { Builder.CreatePtrToInt(Address, &IntPtrType), Size }; +} + +template<> +std::pair +AccessFixer::getOffsetAndSize(Instruction &I) { + uint64_t Size = 0; + Value *Address = nullptr; + if (auto *Load = dyn_cast(&I)) { + Address = Load->getPointerOperand(); + Size = Load->getType()->getIntegerBitWidth() / 8; + } else if (auto *Call = dyn_cast(&I)) { + Address = Call->getArgOperand(1); + Size = cast(Call->getOperand(2))->getLimitedValue(); + } else { + revng_abort(); + } + return { Builder.CreatePtrToInt(Address, &IntPtrType), Size }; +} + +template +void AccessFixer::handle(Instruction &I, + const aua::Annotation::OffsetAndSizeSet &Targets) { + LLVMContext &Context = getContext(&I); + Function *F = I.getFunction(); + + Builder.SetInsertPoint(&I); + auto [Address, Size] = getOffsetAndSize(I); + auto *AddressType = cast(Address->getType()); + + revng_log(Log, + "Handling " << getName(&I) << ", which has " << Targets.size() + << " targets"); + LoggerIndent<> Indent(Log); + + // Handle the single-offset situation + if (Targets.size() == 1) { + Value *Result = emit(I, Targets.begin()->first, Size); + if (Result != nullptr) + I.replaceAllUsesWith(Result); + return; + } + + // There are multiple offsets. We need to emit a switch. + + // Split the block + BasicBlock *Before = I.getParent(); + BasicBlock *After = Before->splitBasicBlock(I.getIterator()); + Before->getTerminator()->eraseFromParent(); + + // Create the default case + BasicBlock *DefaultCase = BasicBlock::Create(Context, "", F, After); + Builder.SetInsertPoint(DefaultCase); + emitAbort(*Builder.CreateUnreachable()); + + // Create the switch + Builder.SetInsertPoint(Before); + auto *Switch = Builder.CreateSwitch(Address, DefaultCase, Targets.size()); + PHINode *Phi = nullptr; + + // If the instruction provides a result, we also need to emit a phi + bool HasResult = not I.getType()->isVoidTy(); + if (HasResult) + Phi = PHINode::Create(I.getType(), Targets.size(), "", &*After->begin()); + + // For each target, create a basic block, add a case to the switch and an + // incoming to the phi + for (auto &&[Offset, Size] : Targets) { + BasicBlock *OffsetCase = BasicBlock::Create(Context, "", F, DefaultCase); + Builder.SetInsertPoint(OffsetCase); + Value *Result = emit(I, Offset, Size); + Builder.CreateBr(After); + revng_assert(not OffsetCase->empty()); + + Switch->addCase(ConstantInt::get(AddressType, Offset), OffsetCase); + + if (HasResult) + Phi->addIncoming(Result, OffsetCase); + } + + if (HasResult) + I.replaceAllUsesWith(Phi); +} + +static void +setArgumentAlignment(CallBase &Call, unsigned ArgIdx, unsigned Align) { + LLVMContext &Context = Call.getContext(); + AttributeList Attrs = Call.getAttributes(); + + auto AlignAttribute = Attribute::get(Context, Attribute::Alignment, Align); + AttributeList NewAttrs = Attrs.addParamAttribute(Context, + ArgIdx, + AlignAttribute); + + Call.setAttributes(NewAttrs); +} + +std::pair +AccessFixer::decomposeMemcpy(llvm::Instruction &I) { + auto &Call = cast(I); + revng_assert(Call.getIntrinsicID() == Intrinsic::memcpy); + auto *Size = cast(Call.getArgOperand(2)); + + BasicBlock &Entry = I.getFunction()->getEntryBlock(); + Builder.SetInsertPoint(&Entry, Entry.begin()); + auto *UInt8Type = IntegerType::getInt8Ty(Abort.getContext()); + auto *Storage = Builder.CreateAlloca(UInt8Type, 0, Size); + uint64_t Alignment = Storage->getAlign().value(); + auto &Read = Call; + auto &Write = *cast(Call.clone()); + Write.insertAfter(&Read); + + Read.setArgOperand(0, Storage); + setArgumentAlignment(Read, 0, Alignment); + + Write.setArgOperand(1, Storage); + setArgumentAlignment(Write, 1, Alignment); + + return { Read, Write }; +} + +void AccessFixer::fixMemoryAccess(Instruction &I, + const aua::Annotation &Annotation) { + revng_log(Log, "Handling memory access " << getName(&I)); + LoggerIndent<> Indent(Log); + + revng_assert(not I.isTerminator()); + auto *Load = dyn_cast(&I); + auto *Store = dyn_cast(&I); + if (Annotation.Escapes) { + revng_log(Log, "It escapes, emitting abort."); + emitAbort(I); + } else if (Load != nullptr and Load->getType()->isPointerTy()) { + revng_log(Log, "Loading a pointer, emitting abort."); + emitAbort(I); + } else if (Store != nullptr + and Store->getValueOperand()->getType()->isPointerTy()) { + revng_log(Log, "Storing a pointer, emitting abort."); + emitAbort(I); + } else { + revng_log(Log, "It's a memcpy-like."); + + bool IsRead = Annotation.Reads.size() != 0; + bool IsWrite = Annotation.Writes.size() != 0; + + if (IsRead and IsWrite) { + revng_log(Log, "Decomposing memcpy."); + const auto &[Read, Write] = decomposeMemcpy(I); + handle(Read, Annotation.Reads); + handle(Write, Annotation.Writes); + + // Read is I and will be erased later + Write.eraseFromParent(); + } else if (IsWrite) { + handle(I, Annotation.Writes); + } else if (IsRead) { + handle(I, Annotation.Reads); + } else { + revng_abort(); + } + + I.eraseFromParent(); + } +} + +static void fixHelpers(VariableManager &Variables, Module &Module) { + LLVMContext &Context = Module.getContext(); + auto &DL = Module.getDataLayout(); + AccessFixer Fixer(Module, Variables, *DL.getIntPtrType(Context)); + + for (Function &F : Module) { + revng_log(Log, "Handling " << F.getName()); + LoggerIndent<> Indent(Log); + + auto Annotation = aua::Annotation::deserialize(F) + .value_or(aua::Annotation()); + + // Upgrade { Offset, Size } annotations to reference the CSV for + // consumptions of other passes + convertToCSVAnnotation(Variables, F, Annotation); + + if (Annotation.Escapes) { + revng_log(Log, "CPU state escapes, emitting an abort"); + Fixer.emitAbort(F); + } + + // Go through all instructions and replace memory accesses in env with + // accesses to CSVs + SmallVector Annotated; + for (Instruction &I : instructions(F)) + if (aua::Annotation::isAnnotated(I)) + Annotated.push_back(&I); + + for (Instruction *I : Annotated) { + Fixer.fixMemoryAccess(*I, *aua::Annotation::deserialize(*I)); + } + } + + revng::verify(&Module); +} + +class FixHelpers : public llvm::ModulePass { +public: + static char ID; + +public: + FixHelpers() : llvm::ModulePass(ID) {} + + void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {} + + bool runOnModule(llvm::Module &M) override { + auto Architecture = model::Architecture::fromQEMUName(ArchitectureName); + revng_assert(Architecture != model::Architecture::Invalid); + auto TheLibTcg = LibTcg::get(Architecture); + + // TODO: fix TargetIsLittleEndian once we support non-x86-64 targets + VariableManager Variables(M, + /* TargetIsLittleEndian */ true, + TheLibTcg.archInfo().env_offset, + TheLibTcg.envPointer(), + TheLibTcg.globalNames()); + + fixHelpers(Variables, M); + + return true; + } +}; + +char FixHelpers::ID = 0; +using Register = RegisterPass; +static Register X("fix-helpers", "Fix helper functions", true, true); diff --git a/lib/HelperArgumentsAnalysis/Function.h b/lib/HelperArgumentsAnalysis/Function.h new file mode 100644 index 000000000..e6dc1c38e --- /dev/null +++ b/lib/HelperArgumentsAnalysis/Function.h @@ -0,0 +1,314 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/ADT/Hashing.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Use.h" + +#include "revng/Support/IRHelpers.h" + +#include "Context.h" +#include "Value.h" + +inline Logger<> Log("argument-usage-analysis"); + +template +void dumpCall(O &Output, const llvm::CallInst *Call) { + const llvm::Function *Callee = getCalledFunction(Call); + Output << "Call " << getName(Call) << " in " + << Call->getParent()->getParent()->getName().str(); + if (Callee == nullptr) { + Output << " (indirect)"; + } else { + Output << " targeting " << Callee->getName().str(); + } +} + +namespace aua { + +class MemoryAccess { +private: + const llvm::Use *Location = nullptr; + const Value *Start = nullptr; + +public: + MemoryAccess(const llvm::Use &Location, const Value &Start) : + Location(&Location), Start(&Start) {} + +public: + MemoryAccess replaceStart(const Value &NewStart) const { + MemoryAccess Result = *this; + Result.Start = &NewStart; + return Result; + } + +public: + std::strong_ordering operator<=>(const MemoryAccess &Other) const = default; + +public: + [[nodiscard]] const llvm::Use &location() const { return *Location; } + [[nodiscard]] const Value &start() const { return *Start; } + [[nodiscard]] bool isWrite() const { return isWrite(Location); } + +public: + [[nodiscard]] static bool isWrite(const llvm::Use *Use) { + llvm::User *U = Use->getUser(); + if (isa(U)) { + return true; + } else if (isa(U)) { + return false; + } else if (auto *Call = dyn_cast(U)) { + return Use->getOperandNo() == 0; + } + + revng_abort(); + } + +public: + std::string toString() const { + return Start->toString() + " (operand " + + std::to_string(Location->getOperandNo()) + " of " + + getName(Location->getUser()) + ")"; + } +}; + +class EscapedArgument { +private: + unsigned ArgumentIndex = 0; + llvm::Instruction *Location = nullptr; + +public: + EscapedArgument(llvm::Instruction &Location, unsigned ArgumentIndex) : + ArgumentIndex(ArgumentIndex), Location(&Location) {} + +public: + std::strong_ordering + operator<=>(const EscapedArgument &Other) const = default; + +public: + unsigned index() const { return ArgumentIndex; } + llvm::Instruction &location() const { return *Location; } + +public: + std::string toString() const { return "Argument" + std::to_string(index()); } +}; + +class Call { + friend std::hash; + +private: + /// The call is here for debugging purposes only + const llvm::CallInst *CallInstruction = nullptr; + const Function *Callee = nullptr; + llvm::DenseMap ActualArguments; + +public: + Call(const llvm::CallInst &CallInstruction, const Function &Callee) : + CallInstruction(&CallInstruction), Callee(&Callee) {} + + bool operator<(const Call &Other) const { + auto ToSortable = + [](const llvm::DenseMap &ToSort) { + SmallVector> Result; + for (auto &&[Index, Value] : ToSort) + Result.push_back({ Index, Value }); + llvm::sort(Result); + return Result; + }; + auto ThisSortableActualArguments = ToSortable(ActualArguments); + auto ThisTuple = std::tie(CallInstruction, + Callee, + ThisSortableActualArguments); + + auto OtherSortableActualArguments = ToSortable(Other.ActualArguments); + auto OtherTuple = std::tie(Other.CallInstruction, + Other.Callee, + OtherSortableActualArguments); + return ThisTuple < OtherTuple; + } + + bool operator==(const Call &Other) const = default; + +public: + const llvm::CallInst &callInstruction() const { return *CallInstruction; } + const Function &callee() const { return *Callee; } + auto &actualArguments() { return ActualArguments; } + const llvm::DenseMap &actualArguments() const { + return ActualArguments; + } + +public: + void registerActualArgument(uint64_t Index, const Value &Argument) { + revng_assert(ActualArguments.count(Index) == 0); + ActualArguments[Index] = &Argument; + } + +public: + template + void dump(O &Output, llvm::StringRef Prefix, bool JustArguments) const { + std::string Indent; + if (not JustArguments) { + Output << Prefix.str(); + dumpCall(Output, CallInstruction); + Output << "\n"; + Indent = " "; + } + + Output << Prefix.str() << Indent << "Actual arguments:\n"; + for (uint64_t Index = 0; Index < ActualArguments.size(); ++Index) { + Output << Prefix.str() << Indent << " " << Index << ". " + << ActualArguments.find(Index)->second->toString() << "\n"; + } + } + + void dump() const debug_function { dump(dbg, "", false); } +}; + +class Function { +private: + Context &TheContext; + std::set LocalAccesses; + std::set LocalEscapedArguments; + SmallVector Calls; + const Value *ReturnValue = nullptr; + llvm::DenseMap AnalysisResult; + +public: + Function(Context &TheContext) : TheContext(TheContext) {} + +public: + const Value *tryGet(const llvm::Value &V) const { + auto It = AnalysisResult.find(&V); + if (It == AnalysisResult.end()) { + if (auto *Constant = dyn_cast(&V)) { + if (Constant->getBitWidth() <= 64) { + // Handle constants on the fly + return &TheContext.getConstant(getLimitedValue(Constant)); + } else { + // Ignore the rest + return &TheContext.getUnknown(); + } + } else if (isa(&V)) { + // Ignore globals, function pointer, constant expressions and so on + return &TheContext.getUnknown(); + } else { + return nullptr; + } + } else { + return It->second; + } + } + + const Value &get(const llvm::Value &V) const { + const Value *Result = tryGet(V); + revng_assert(Result != nullptr, dumpToString(V).c_str()); + return *Result; + } + + const auto &localAccesses() const { return LocalAccesses; } + const SmallVector &calls() const { return Calls; } + const Value *returnValue() const { return ReturnValue; } + const auto &localEscapedArguments() const { return LocalEscapedArguments; } + +public: + void set(const llvm::Value &V, const Value &NewValue) { + AnalysisResult[&V] = &NewValue; + } + + void registerAccess(const llvm::Use &Location, const Value &NewValue) { + LocalAccesses.insert({ Location, NewValue }); + } + + void logEscapedValue(llvm::StringRef Context, const Value &EscapedValue) { + if (Log.isEnabled()) { + llvm::DenseSet EscapedArguments = EscapedValue + .collectArguments(); + if (EscapedArguments.size() != 0) { + Log << "In " << Context.str() + << " the following value escapes: " << EscapedValue.toString() + << ". Therefore, the following arguments escape: {"; + for (unsigned ArgumentIndex : EscapedArguments) + Log << " " << ArgumentIndex; + Log << " }" << DoLog; + } + } + } + + void registerEscapedValue(llvm::Instruction &Location, + const Value &EscapedValue) { + for (unsigned ArgumentIndex : EscapedValue.collectArguments()) + LocalEscapedArguments.insert({ Location, ArgumentIndex }); + } + + void registerReturnValue(Context &Context, const Value &NewReturnValue) { + if (ReturnValue == nullptr) { + ReturnValue = &NewReturnValue; + } else { + ReturnValue = &Context.getAnyOf({ ReturnValue, &NewReturnValue }); + } + } + + void registerCall(aua::Call &&NewCall) { + Calls.push_back(std::move(NewCall)); + } + +public: + template + void dump(O &Output, llvm::StringRef Prefix) const { + if (LocalAccesses.size() > 0) { + Output << Prefix.str() << "Local reads:\n"; + std::set Reads; + for (const MemoryAccess &Access : LocalAccesses) + if (not Access.isWrite()) + Reads.insert(&Access.start()); + + for (const Value *Read : Reads) + Output << Prefix.str() << " " << Read->toString() << "\n"; + + Output << Prefix.str() << "Local writes:\n"; + std::set Writes; + for (const MemoryAccess &Access : LocalAccesses) + if (Access.isWrite()) + Writes.insert(&Access.start()); + + for (const Value *Write : Writes) + Output << Prefix.str() << " " << Write->toString() << "\n"; + } + + if (LocalEscapedArguments.size() > 0) { + Output << Prefix.str() << "Local escaped arguments: {"; + for (const EscapedArgument &EscapedArgument : LocalEscapedArguments) + Output << " " << EscapedArgument.toString(); + Output << " }\n"; + } + + Output << Prefix.str() << "Return value: " << returnValue()->toString() + << "\n"; + } + + void dump() const debug_function { dump(dbg, ""); } +}; + +} // namespace aua + +// Specialization of std::hash for Lel +namespace std { +template<> +struct hash { + std::size_t operator()(const aua::Call &Call) const { + using namespace llvm; + std::size_t Hash = 0; + Hash = hash_combine(Hash, + reinterpret_cast(Call.CallInstruction)); + Hash = hash_combine(Hash, reinterpret_cast(Call.Callee)); + for (const auto &[Index, Argument] : Call.ActualArguments) { + Hash = hash_combine(Hash, Index); + Hash = hash_combine(Hash, reinterpret_cast(Argument)); + } + return Hash; + } +}; +} // namespace std diff --git a/lib/HelperArgumentsAnalysis/SlimDownHelpersModule.cpp b/lib/HelperArgumentsAnalysis/SlimDownHelpersModule.cpp new file mode 100644 index 000000000..6b8c4945f --- /dev/null +++ b/lib/HelperArgumentsAnalysis/SlimDownHelpersModule.cpp @@ -0,0 +1,89 @@ +/// \file SlimDownHelpersModule.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/IR/Constants.h" +#include "llvm/IR/Module.h" +#include "llvm/Pass.h" + +using namespace llvm; + +class SlimDownHelpersModule : public llvm::ModulePass { +public: + static char ID; + +public: + SlimDownHelpersModule() : llvm::ModulePass(ID) {} + + void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {} + + bool runOnModule(llvm::Module &M) override { + LLVMContext &Context = M.getContext(); + + auto DbgKindID = M.getContext().getMDKindID("dbg"); + for (Function &F : M) { + // Remove the body of all the functions *not* tagged with `revng_inline`. + // Also preserve helper_initialize_env, which needs to survive but it's + // not to be inlined. + if (F.getSection() != "revng_inline" + and F.getName() != "helper_initialize_env") { + F.eraseMetadata(DbgKindID); + SmallVector, 8> MDs; + F.getAllMetadata(MDs); + F.deleteBody(); + for (auto [ID, Node] : MDs) + F.setMetadata(ID, Node); + } + } + + // Mark all global variables as internal, so we can purge those that are now + // unused due to the removal of most of helpers' bodies. + // Also preserve the `arch_cpu_type_beacon` and all the variables with + // revng.tags metadata, since they are CSVs produced by VariableManager. + for (GlobalVariable &GV : M.globals()) { + if (not GV.isDeclaration() and GV.getName() != "cpu_loop_exiting" + and GV.getName() != "arch_cpu_type_beacon" + and not GV.hasMetadata("revng.tags")) { + GV.setLinkage(llvm::GlobalValue::InternalLinkage); + } + } + + // Assume the module M already contains the functions. + // Collect all functions whose name starts with "helper_" to prevent DCE + llvm::DenseSet Helpers; + for (Function &F : M) + if (F.getName().startswith("helper_")) + Helpers.insert(&F); + + PointerType *PointerType = PointerType::get(Context, 0); + + // Create array type: [N x ] + ArrayType *FunctionArray = ArrayType::get(PointerType, Helpers.size()); + + // Create constant array initializer with function pointers + std::vector FunctionPointers; + for (Function *F : Helpers) + FunctionPointers.push_back(ConstantExpr::getBitCast(F, PointerType)); + + Constant *Initializer = ConstantArray::get(FunctionArray, FunctionPointers); + + new GlobalVariable(M, + FunctionArray, + true, + GlobalValue::ExternalLinkage, + Initializer, + "helpers_list"); + + return true; + } +}; + +char SlimDownHelpersModule::ID = 0; +using Register = RegisterPass; +static Register X("slim-down-helpers-module", + "Purge non-inline helper functions but keep their " + "declarations alive in a array of pointers.", + true, + true); diff --git a/lib/HelperArgumentsAnalysis/Value.cpp b/lib/HelperArgumentsAnalysis/Value.cpp new file mode 100644 index 000000000..b8cbd190c --- /dev/null +++ b/lib/HelperArgumentsAnalysis/Value.cpp @@ -0,0 +1,228 @@ +/// \file Value.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "Value.h" + +inline std::size_t hashCombine(std::size_t LHS, std::size_t RHS) { + return LHS ^ (RHS << 1); +} + +// TODO: this won't be necessary once we update libc++ +template +constexpr auto lexicographicalCompareThreeWay(I1 LeftBegin, + I1 LeftEnd, + I1 RightBegin, + I1 RigthEnd) + -> decltype(std::compare_three_way()(*LeftBegin, *RightBegin)) { + using ret_t = decltype(std::compare_three_way()(*LeftBegin, *RightBegin)); + static_assert(std::disjunction_v, + std::is_same, + std::is_same>, + "The return type must be a comparison category type."); + + bool Exhaust1 = (LeftBegin == LeftEnd); + bool Exhaust2 = (RightBegin == RigthEnd); + for (; !Exhaust1 && !Exhaust2; Exhaust1 = (++LeftBegin == LeftEnd), + Exhaust2 = (++RightBegin == RigthEnd)) + if (auto C = std::compare_three_way()(*LeftBegin, *RightBegin); C != 0) + return C; + + return !Exhaust1 ? std::strong_ordering::greater : + !Exhaust2 ? std::strong_ordering::less : + std::strong_ordering::equal; +} + +namespace aua { + +std::size_t Value::hash() const { + std::size_t Result = std::hash()(TheKind); + for (const Value *V : Operands) + Result = hashCombine(Result, std::hash()(V)); + return Result; +} + +std::strong_ordering Value::compare(const Value &Other) const { + return lexicographicalCompareThreeWay(Operands.begin(), + Operands.end(), + Other.Operands.begin(), + Other.Operands.end()); +} + +std::string Value::toGraph() const { + std::string Result; + Result += "digraph {\n"; + Result += " node[shape=box];\n"; + Result += " rankdir = BT;\n"; + Result += toGraphPart(); + Result += "}\n"; + return Result; +} + +std::strong_ordering AnyOfValue::operator<=>(const AnyOfValue &Other) const { + std::strong_ordering Result = compare(Other); + if (Result == std::strong_ordering::equal) { + return Result; + } else { + return Result; + } +} + +std::string AnyOfValue::toString() const { + std::string Result = "AnyOf("; + const char *Separator = ""; + for (const Value *Value : Operands) { + Result += Separator; + Result += Value->toString(); + Separator = ", "; + } + Result += ")"; + return Result; +} + +std::string AnyOfValue::toGraphPart() const { + std::string Result = node("AnyOf"); + + for (const Value *Value : Operands) { + Result += Value->toGraphPart(); + Result += edgeTo(*Value); + } + + return Result; +} + +std::strong_ordering +ArgumentValue::operator<=>(const ArgumentValue &Other) const { + std::strong_ordering Result = compare(Other); + if (Result == std::strong_ordering::equal) { + return Index <=> Other.Index; + } else { + return Result; + } +} + +std::size_t ArgumentValue::hash() const { + return hashCombine(Value::hash(), std::hash()(Index)); +} + +std::strong_ordering +ConstantValue::operator<=>(const ConstantValue &Other) const { + std::strong_ordering Result = compare(Other); + if (Result == std::strong_ordering::equal) { + return TheValue <=> Other.TheValue; + } else { + return Result; + } +} + +std::size_t ConstantValue::hash() const { + return hashCombine(Value::hash(), std::hash()(TheValue)); +} + +std::strong_ordering +BinaryOperatorValue::operator<=>(const BinaryOperatorValue &Other) const { + std::strong_ordering Result = compare(Other); + if (Result == std::strong_ordering::equal) { + return Type <=> Other.Type; + } else { + return Result; + } +} + +std::size_t BinaryOperatorValue::hash() const { + return hashCombine(Value::hash(), std::hash()(Type)); +} + +std::string BinaryOperatorValue::toString() const { + return "(" + Operands[0]->toString() + " " + symbol() + " " + + Operands[1]->toString() + ")"; +} + +std::string BinaryOperatorValue::toGraphPart() const { + std::string Result; + Result += node(symbol()); + Result += Operands[0]->toGraphPart(); + Result += edgeTo(*Operands[0]); + Result += Operands[1]->toGraphPart(); + Result += edgeTo(*Operands[1]); + return Result; +} + +const char *BinaryOperatorValue::symbol() const { + switch (Type) { + case Add: + return "+"; + case Subtract: + return "-"; + case Multiply: + return "*"; + default: + case Invalid: + revng_abort(); + } +} + +std::strong_ordering +FunctionOfValue::operator<=>(const FunctionOfValue &Other) const { + std::strong_ordering Result = compare(Other); + if (Result == std::strong_ordering::equal) { + return Result; + } else { + return Result; + } +} + +std::string FunctionOfValue::toString() const { + std::string Result = "FunctionOf("; + const char *Separator = ""; + for (const Value *Value : Operands) { + Result += Separator; + Result += Value->toString(); + Separator = ", "; + } + Result += ")"; + return Result; +} + +std::string FunctionOfValue::toGraphPart() const { + std::string Result = node("FunctionOf"); + + for (const Value *Value : Operands) { + Result += Value->toGraphPart(); + Result += edgeTo(*Value); + } + + return Result; +} + +llvm::DenseSet Value::collectArguments() const { + llvm::DenseSet Result; + for (auto &Argument : collect()) + Result.insert(Argument->index()); + return Result; +} + +void Value::deleteValue() { + upcast([](auto &Upcasted) { delete &Upcasted; }); +} + +std::strong_ordering Value::operator<=>(const Value &Other) const { + if (TheKind != Other.TheKind) + return TheKind <=> Other.TheKind; + + return upcast([&Other](auto &Upcasted) { + using Type = std::decay_t; + return Upcasted <=> *(llvm::cast(&Other)); + }); +} + +std::string Value::toString() const { + return upcast([](auto &Upcasted) { return Upcasted.toString(); }); +} + +std::string Value::toGraphPart() const { + return upcast([](const auto &Upcasted) { return Upcasted.toGraphPart(); }); +} +} // namespace aua diff --git a/lib/HelperArgumentsAnalysis/Value.h b/lib/HelperArgumentsAnalysis/Value.h new file mode 100644 index 000000000..45fdd6c86 --- /dev/null +++ b/lib/HelperArgumentsAnalysis/Value.h @@ -0,0 +1,293 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" + +#include "revng/Support/Debug.h" + +namespace aua { + +using llvm::SmallVector; + +class Function; + +class Value { + friend class Context; + +public: + enum class Kind { + Invalid, + AnyOf, + Argument, + Constant, + BinaryOperator, + FunctionOf + }; + +protected: + Kind TheKind = Kind::Invalid; + SmallVector Operands; + +protected: + Value(Kind TheKind, SmallVector &&Values) : + TheKind(TheKind), Operands(std::move(Values)) {} + +public: + ~Value() = default; + +private: + void deleteValue(); + +protected: + void deduplicate() { + llvm::sort(Operands); + Operands.erase(std::unique(Operands.begin(), Operands.end()), + Operands.end()); + } + +public: + std::strong_ordering operator<=>(const Value &Other) const; + bool operator==(const Value &Other) const = default; + +public: + template + SmallVector collect() const { + SmallVector Result; + + SmallVector Queue{ this }; + while (not Queue.empty()) { + const Value *Current = Queue.pop_back_val(); + if (const T *Upcasted = llvm::dyn_cast(Current)) + Result.push_back(Upcasted); + + for (const Value *Value : Current->Operands) + Queue.push_back(Value); + } + + return Result; + } + + llvm::DenseSet collectArguments() const; + +public: + std::string toString() const; + +public: + auto kind() const { return TheKind; } + const auto &operands() const { return Operands; } + +public: + void setOperands(SmallVector &&NewOperands) { + Operands = std::move(NewOperands); + } + +public: + auto upcast(auto &&Handler); + auto upcast(auto &&Handler) const; + +public: + std::size_t hash() const; + +protected: + std::strong_ordering compare(const Value &Other) const; + +protected: + std::string nodeName() const { + return std::to_string(reinterpret_cast(this)); + } + + std::string node(const std::string &Label) const { + return " " + nodeName() + "[label=\"" + Label + "\"];\n"; + } + + std::string edgeTo(const Value &Other) const { + return " " + nodeName() + " -> " + Other.nodeName() + "\n"; + } + +public: + std::string toGraphPart() const; + + std::string toGraph() const; +}; + +class AnyOfValue : public Value { + friend class Context; + +private: + AnyOfValue(SmallVector &&Values) : + Value(Kind::AnyOf, std::move(Values)) { + deduplicate(); + } + +public: + static bool classof(const Value *B) { return B->kind() == Kind::AnyOf; } + +public: + std::strong_ordering operator<=>(const AnyOfValue &Other) const; + +public: + auto operands() const { return Operands; } + +public: + std::string toString() const; + std::string toGraphPart() const; +}; + +class ArgumentValue : public Value { + friend class Context; + +private: + unsigned Index = 0; + +private: + ArgumentValue(unsigned Index) : Value(Kind::Argument, {}), Index(Index) {} + +public: + static bool classof(const Value *B) { return B->kind() == Kind::Argument; } + +public: + std::strong_ordering operator<=>(const ArgumentValue &Other) const; + +public: + auto index() const { return Index; } + std::size_t hash() const; + +public: + std::string toString() const { return "Argument" + std::to_string(Index); } + std::string toGraphPart() const { return node(toString()); } +}; + +class ConstantValue : public Value { + friend class Context; + +private: + uint64_t TheValue = 0; + +public: + ConstantValue(uint64_t TheValue) : + Value(Kind::Constant, {}), TheValue(TheValue) {} + +public: + static bool classof(const Value *B) { return B->kind() == Kind::Constant; } + +public: + std::strong_ordering operator<=>(const ConstantValue &Other) const; + std::size_t hash() const; + +public: + auto value() const { return TheValue; } + +public: + std::string toString() const { return std::to_string(TheValue); } + std::string toGraphPart() const { return node(toString()); } +}; + +class BinaryOperatorValue : public Value { + friend class Context; + +public: + enum Operator { + Invalid, + Add, + Subtract, + Multiply + }; + +private: + Operator Type = Invalid; + +private: + BinaryOperatorValue(Operator Type, + const Value &FirstValue, + const Value &SecondValue) : + Value(Kind::BinaryOperator, { &FirstValue, &SecondValue }), Type(Type) {} + +public: + static bool classof(const Value *B) { + return B->kind() == Kind::BinaryOperator; + } + +public: + std::strong_ordering operator<=>(const BinaryOperatorValue &Other) const; + +public: + auto type() const { return Type; } + const Value &firstOperand() const { return *Operands[0]; } + const Value &secondOperand() const { return *Operands[1]; } + +public: + std::size_t hash() const; + +public: + std::string toString() const; + + std::string toGraphPart() const; + +private: + const char *symbol() const; +}; + +class FunctionOfValue : public Value { + friend class Context; + +private: + FunctionOfValue(SmallVector &&Values) : + Value(Kind::FunctionOf, std::move(Values)) {} + +public: + static bool classof(const Value *B) { return B->kind() == Kind::FunctionOf; } + +public: + std::strong_ordering operator<=>(const FunctionOfValue &Other) const; + + bool isUnknown() const { return Operands.size() == 0; } + +public: + std::string toString() const; + std::string toGraphPart() const; +}; + +inline auto Value::upcast(auto &&Handler) { + switch (TheKind) { + revng_abort(); + case Kind::AnyOf: + return Handler(*llvm::cast(this)); + case Kind::Argument: + return Handler(*llvm::cast(this)); + case Kind::Constant: + return Handler(*llvm::cast(this)); + case Kind::BinaryOperator: + return Handler(*llvm::cast(this)); + case Kind::FunctionOf: + return Handler(*llvm::cast(this)); + case Kind::Invalid: + default: + revng_abort(); + } +} + +auto Value::upcast(auto &&Handler) const { + switch (TheKind) { + case Kind::AnyOf: + return Handler(*llvm::cast(this)); + case Kind::Argument: + return Handler(*llvm::cast(this)); + case Kind::Constant: + return Handler(*llvm::cast(this)); + case Kind::BinaryOperator: + return Handler(*llvm::cast(this)); + case Kind::FunctionOf: + return Handler(*llvm::cast(this)); + case Kind::Invalid: + default: + revng_abort(); + } +} + +} // namespace aua diff --git a/lib/ImportFromC/HeaderToModel.cpp b/lib/ImportFromC/HeaderToModel.cpp index cd98b6fc9..dffcb29ef 100644 --- a/lib/ImportFromC/HeaderToModel.cpp +++ b/lib/ImportFromC/HeaderToModel.cpp @@ -2,6 +2,7 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include "clang/AST/Decl.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/TextDiagnostic.h" @@ -606,8 +607,8 @@ bool DeclVisitor::VisitFunctionDecl(const clang::FunctionDecl *FD) { return false; } - auto Location = model::Register::fromCSVName(*Register, - Model->Architecture()); + auto Location = model::Register::fromRegisterName(*Register, + Model->Architecture()); if (Location == model::Register::Invalid) { Errors.emplace_back("import-from-c: While parsing the return value:\n"); Errors.emplace_back("import-from-c failed: Unknown register: `" @@ -682,9 +683,10 @@ bool DeclVisitor::VisitFunctionDecl(const clang::FunctionDecl *FD) { return false; } - auto Location = model::Register::fromCSVName(*Register, - Model->Architecture()); - if (Location == model::Register::Invalid) { + using namespace model; + auto Location = Register::fromRegisterName(*Register, + Model->Architecture()); + if (Location == Register::Invalid) { Errors.emplace_back("import-from-c: While parsing argument #" + std::to_string(I) + ":\n"); Errors.emplace_back("import-from-c failed: Unknown register: `" @@ -906,7 +908,8 @@ bool DeclVisitor::handleStructType(const clang::RecordDecl *RD) { return false; } - Location = model::Register::fromCSVName(*Register, Model->Architecture()); + Location = model::Register::fromRegisterName(*Register, + Model->Architecture()); if (Location == model::Register::Invalid) { Errors.emplace_back("import-from-c: While parsing return value #" + std::to_string(Struct->Fields().size()) + ":\n"); diff --git a/lib/Lift/CMakeLists.txt b/lib/Lift/CMakeLists.txt index 24546162c..27d137c85 100644 --- a/lib/Lift/CMakeLists.txt +++ b/lib/Lift/CMakeLists.txt @@ -6,17 +6,17 @@ revng_add_library_internal( revngLift SHARED CodeGenerator.cpp - CPUStateAccessAnalysisPass.cpp CSVOffsets.cpp ExternalJumpsHandler.cpp InstructionTranslator.cpp IRAnnotators.cpp + LibTcg.cpp Lift.cpp LiftPipe.cpp LinkSupportPipe.cpp LoadBinaryPass.cpp JumpTargetManager.cpp - PTCDump.cpp + PostLiftVerifyPass.cpp RootAnalyzer.cpp SelfReferencingDbgAnnotationWriter.cpp ValueMaterializerPass.cpp diff --git a/lib/Lift/CPUStateAccessAnalysisPass.cpp b/lib/Lift/CPUStateAccessAnalysisPass.cpp deleted file mode 100644 index 3c38073b2..000000000 --- a/lib/Lift/CPUStateAccessAnalysisPass.cpp +++ /dev/null @@ -1,3279 +0,0 @@ -/// \file CPUStateAccessAnalysisPass.cpp -/// This file performs an analysis for reconstructing the access patterns to the -/// CPU State Variables (CSV). - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include -#include -#include - -#include "llvm/ADT/SetVector.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/IR/Instructions.h" -#include "llvm/IR/Module.h" -#include "llvm/IR/Type.h" -#include "llvm/IR/Value.h" -#include "llvm/IR/Verifier.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/raw_ostream.h" - -#include "revng/Lift/CPUStateAccessAnalysisPass.h" -#include "revng/Lift/VariableManager.h" -#include "revng/Model/FunctionTags.h" -#include "revng/Support/Debug.h" -#include "revng/Support/IRHelperRegistry.h" -#include "revng/Support/IRHelpers.h" - -namespace llvm { -class DataLayout; -} - -using namespace llvm; - -using std::optional; - -using ConstFunctionPtrSet = std::set; -using InstrPtrSet = llvm::SetVector; -using CallPtrSet = std::set; -using ConstValuePtrSet = std::set; - -/// Logger for forwardTaintAnalysis -static auto TaintLog = Logger<>("cpustate-taint-analysis"); -/// Logger for the creation of WorkItem -static auto CSVAccessLog = Logger<>("cpustate-access-analysis"); -/// Logger for fixing the accesses to CPUState -static auto FixAccessLog = Logger<>("cpustate-fix-access"); - -static uint64_t NumUnknown = 0; -static std::map FunToNumUnknown; -static std::map> FunToUnknowns; - -/// Computes the set of Functions reachable from a given Function through direct -/// calls. -/// -/// \param RootFunction is a pointer to the Function from which is the -/// starting point for computing reachability. -/// \param LoadMDKind is the metadata kind used for decorating call sites that -/// access CPU State to load data -/// \param StoreMDKind is the metadata kind used for decorating call sites that -/// access CPU State to store data -/// \param Lazy tells if the analysis is running in Lazy mode -/// \return set of pointers to the reachable Functions -/// -/// This function can probably be implemented using the call graph utilities -/// already present in LLVM, but when I tried it I spent a day doing it and I -/// gave up because of bugs in the functions coming from LLVM. Then I -/// implemented my own version, i.e. this function -static ConstFunctionPtrSet -computeDirectlyReachableFunctions(const Function *RootFunction, - const unsigned LoadMDKind, - const unsigned StoreMDKind, - const bool Lazy) { - std::map CallGraph; - const Module &M = *RootFunction->getParent(); - - // Initialize empty CallGraph - for (const Function &F : M) - CallGraph[&F] = {}; - - for (const Function &F : M) { - for (const Use &U : F.uses()) { - const User *TheUser = U.getUser(); - if (const auto *TheCall = dyn_cast(TheUser)) { - // If this call has already been decorated with LoadMDKind or - // StoreMDKind metadata it means that it has already been processed by - // a previous run of CPUStateAccessAnalysis. - // If we're running in lazy mode, we can skip calls that have already - // been decorated. - if (Lazy) { - if (TheCall->getMetadata(LoadMDKind) != nullptr - or TheCall->getMetadata(StoreMDKind) != nullptr) { - continue; - } - } - const Function *Caller = TheCall->getFunction(); - const Function *Callee = getCallee(TheCall); - if (Callee == &F) { - CallGraph[Caller].insert(Callee); - } - } else if (const auto *CExpr = dyn_cast(TheUser)) { - SmallPtrSet CurBitCasts; - SmallPtrSet NextBitCasts; - CurBitCasts.insert(CExpr); - while (not CurBitCasts.empty()) { - NextBitCasts.clear(); - for (const ConstantExpr *BitCast : CurBitCasts) { - for (const User *BitCastUser : BitCast->users()) { - const auto *TheCall = dyn_cast(BitCastUser); - const auto *NewCExpr = dyn_cast(BitCastUser); - if (TheCall) { - // If this call has already been decorated with LoadMDKind or - // StoreMDKind metadata it means that it has already been - // processed by a previous run of CPUStateAccessAnalysis. - // If we're running in lazy mode, we can skip calls that have - // already been decorated. - if (Lazy) { - if (TheCall->getMetadata(LoadMDKind) != nullptr - or TheCall->getMetadata(StoreMDKind) != nullptr) { - continue; - } - } - const Function *Caller = TheCall->getFunction(); - const Function *Callee = getCallee(TheCall); - if (Callee == &F) { - CallGraph[Caller].insert(Callee); - } - } else if (NewCExpr) { - NextBitCasts.insert(NewCExpr); - } - } - } - std::swap(CurBitCasts, NextBitCasts); - } - } - } - } - - ConstFunctionPtrSet ReachableFunctions = { RootFunction }; - ConstFunctionPtrSet CurrentChildren = { RootFunction }; - ConstFunctionPtrSet NextChildren; - while (not CurrentChildren.empty()) { - NextChildren.clear(); - for (const Function *F : CurrentChildren) { - for (const Function *Callee : CallGraph.at(F)) { - bool NewInsertion = ReachableFunctions.insert(Callee).second; - if (NewInsertion) - NextChildren.insert(Callee); - } - } - std::swap(CurrentChildren, NextChildren); - } - - return ReachableFunctions; -} - -struct TaintResults { - - // A set of Instructions that access the CSV to load data. They can be - // LoadInst or CallInst to Intrinsic::memcpy for which the size is known. - InstrPtrSet TaintedLoads; - - // A set of Instructions that access the CSV to store data. They can be - // LoadInst or CallInst to Intrinsic::memcpy for which the size is known. - InstrPtrSet TaintedStores; - - // A set of Values that are tainted during the analysis. - ConstValuePtrSet TaintedValues; - - // A set of CallInst that are considered illegal. These include indirect - // calls, calls to functions without body, and calls to Intrinsic::memcpy - // with unknown size. They are considered illegal because we have no way of - // knowing how they access the CSV. - CallPtrSet IllegalCalls; - - bool empty() const noexcept { - return TaintedLoads.empty() and TaintedStores.empty() - and IllegalCalls.empty(); - } -}; - -/// Interprocedural forward taint analysis. -// -/// \param CPUStatePtr is a pointer to the CPU State Variable, which is a global -/// variable. This variable is the Value that taints all the others. -/// \param ReachableFunctions is a set of functions that are reachable from -/// the root function. The analysis is restricted to those functions. -/// \param LoadMDKind is the metadata kind used for decorating call sites that -/// access CPU State to load data -/// \param StoreMDKind is the metadata kind used for decorating call sites that -/// access CPU State to store data -/// \param Lazy tells if the analysis is running in Lazy mode -/// \return a TaintResults containing information on: -/// 1) a set of instructions that access the CSV to load data; -/// 2) a set of instructions that access the CSV to store data; -/// 3) a set of illegal calls, for which it is impossible to understand -/// if and how they will access the CSV. -static TaintResults -forwardTaintAnalysis(const Module *M, - Value *CPUStatePtr, - const ConstFunctionPtrSet &ReachableFunctions, - const unsigned LoadMDKind, - const unsigned StoreMDKind, - const bool Lazy) { - // - // Interprocedural Forward Taint Analysis - // - - // This analysis aims to understand all the Values that are affected by the - // CPUStatePtr pointer, that points to the CPUStateVariables (CSVs). - // The main idea is that we explore the use chains depth first, propagating - // interprocedurally when we find a Use whose User is a CallInstr. - // - // The function is structured as follows: - // 1. Iterate on the users of `CPUStatePtr` - // 2. For each user of `CPUStatePtr`, consider its next user, - // building a WorkList of `Use`s in exploration - // 3. If we find unexplored uses keep pushing them on the WorkList. If we - // find a Load or a Store we taint it and don't push anything on the - // WorkList. If it's not a Load or Store we mark it as tainted separately - // 3(a) If the next `Use` to explore is a `CallInst` the taint is - // propagated interprocedurally to the callee, through the arguments - // (propagation from caller to callee) - // 3(b) If the next `Use` to explore is a `RetInst` the taint is - // propagated interprocedurally to the Function. - // (propagation from the callee to all call sites) - // 4. If we didn't push anything on the WorkList we can start exploring the - // other `Use`s of the item that is currently on top of the WorkList - // 5. If we didn't push anything on the WorkList we can start popping `Use`s - // from the WorkList, until we reach a `Value` that still has unexplored - // `Use`s - // 5(a) If we're popping an argument of a function this means that we've - // finished analyzing the uses of that argument. We have to make sure - // that, if the taint reached the return instructions in the function, - // the taint is propagated to the call sites. - // 6. After popping the top of the WorkList in 5., if the new top of the - // WorkList still has unexplored uses start to explore them. - - TaintResults Results; - std::set> - FunctionArgTaintsReturn; - - revng_assert(CPUStatePtr != nullptr); - revng_assert(CPUStatePtr->getType()->isPointerTy()); - - struct CallSiteInfo { - const CallInst *CallSite = nullptr; - const Argument *Arg = nullptr; - const unsigned ArgNo; - CallSiteInfo(const CallInst *C, const Argument *A, const unsigned N) : - CallSite(C), Arg(A), ArgNo(N) {} - }; - - if (TaintLog.isEnabled()) { - TaintLog << "MODULE:" << DoLog; - TaintLog << dumpToString(M) << DoLog; - } - - // 1. Iterate on the users of `CPUStatePtr` - std::set Loads; - { - std::queue WorkList; - for (const User *U : CPUStatePtr->users()) - WorkList.push(U); - - while (not WorkList.empty()) { - const User *U = WorkList.front(); - WorkList.pop(); - - if (auto *CE = dyn_cast(U)) { - if (CE->isCast()) { - for (const User *UCE : CE->users()) { - WorkList.push(UCE); - } - } - } else if (auto *Load = dyn_cast(U)) { - Loads.insert(Load); - } else { - revng_abort("Unexpected user"); - } - } - } - - for (const LoadInst *Load : Loads) { - - // During the analysis we keep two stacks. - // ToTaintWorkList is a stack representing the Values currently enqued that - // must be tainted and for which we still have to analyze the uses. - // CallSites is a stack representing the CallInst that we entered for - // interprocedural propagation. - std::stack ToTaintWorkList; - std::stack CallSiteInfos; - - // Sanity check for the uses of CPUStatePtr. - // They must all be direct Loads from CPUStatePtr. - revng_assert(Load->getPointerOperand()->stripPointerCasts() == CPUStatePtr); - - // Push the first use on the WorkList - const Function *F = Load->getFunction(); - if (Load->getNumUses() != 0 and ReachableFunctions.contains(F)) { - if (TaintLog.isEnabled()) { - TaintLog << "Tainted origin: " << Load << DoLog; - TaintLog << dumpToString(Load) << DoLog; - TaintLog.indent(); - } - ToTaintWorkList.push(&*Load->use_begin()); - } - - // 2. For each user of `CPUStatePtr`, consider its next user, - // building a WorkList of `Use`s in exploration - while (not ToTaintWorkList.empty()) { - const Use *TheUse = ToTaintWorkList.top(); - revng_assert(TheUse != nullptr); - auto *TheUser = cast(TheUse->getUser()); - const auto OpCode = TheUser->getOpcode(); - if (TaintLog.isEnabled()) { - TaintLog << "Inst: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - - const size_t Size = ToTaintWorkList.size(); - - // 3. If we find unexplored uses keep pushing them on the WorkList. If we - // find a Load or a Store we taint it and don't push anything on the - // WorkList. If it's not a Load or Store we mark it as tainted separately - - // This switch explores the use-chains depth-first, pushing unexplored - // uses on the ToTaintWorkList if necessary. - unsigned OperandNo = TheUse->getOperandNo(); - switch (OpCode) { - case Instruction::Load: { - TaintLog << "LOAD" << DoLog; - - revng_assert(OperandNo == LoadInst::getPointerOperandIndex()); - auto *L = cast(TheUser); - if (TheUse->get() == L->getPointerOperand()) { - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - Results.TaintedLoads.insert(TheUser); - } - } break; - case Instruction::Store: { - TaintLog << "STORE" << DoLog; - revng_assert(OperandNo == StoreInst::getPointerOperandIndex()); - auto *S = cast(TheUser); - if (TheUse->get() == S->getPointerOperand()) { - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - Results.TaintedStores.insert(TheUser); - } - } break; - case Instruction::Trunc: - case Instruction::ZExt: - case Instruction::SExt: - case Instruction::BitCast: - case Instruction::IntToPtr: - case Instruction::PtrToInt: - case Instruction::GetElementPtr: - case Instruction::PHI: - case Instruction::Add: { - TaintLog << "OP" << DoLog; - auto OperandId = GetElementPtrInst::getPointerOperandIndex(); - revng_assert(OpCode != Instruction::GetElementPtr - or TheUse->getOperandNo() == OperandId); - - // Taint TheUser, and if this is the first time we taint it we also push - // on the ToTaintWorkList its first use that is not tainted - bool JustTainted = Results.TaintedValues.insert(TheUser).second; - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - if (JustTainted) { - TaintLog << "Just Tainted" << DoLog; - for (const Use &U : TheUser->uses()) { - TaintLog << "User: " << U.getUser() << DoLog; - if (!Results.TaintedValues.contains(U.getUser())) { - TaintLog << "PUSH" << DoLog; - ToTaintWorkList.push(&U); - TaintLog.indent(); - break; - } - } - } - } break; - case Instruction::Call: { - TaintLog << "CALL" << DoLog; - auto *TheCall = cast(TheUser); - - // If this call has already been decorated with LoadMDKind or - // StoreMDKind metadata it means that it has already been processed by - // a previous run of CPUStateAccessAnalysis. - // If we're running in lazy mode, we can skip calls that have already - // been decorated. - if (Lazy) { - if (TheCall->getMetadata(LoadMDKind) != nullptr - or TheCall->getMetadata(StoreMDKind) != nullptr) { - TaintLog << "Decorated in a previous run" << DoLog; - break; - } - } - - // 3(a) If the next `Use` to explore is a `CallInst` the taint is - // propagated interprocedurally to the callee, through the arguments - // (propagation from caller to callee) - Function *Callee = getCallee(TheCall); - - // Indirect calls, calls to functions without body, and calls to - // Intrinsic::memcpy with non-constant size are considered illegal, - // because we cannot know how they will affect the CPU State - if (Callee == nullptr) { - TaintLog << "Illegal -- indirect call" << DoLog; - Results.IllegalCalls.insert(TheCall); - break; - } - if (Callee->getIntrinsicID() == Intrinsic::memcpy) { - unsigned OpNo = TheUse->getOperandNo(); - revng_assert(OpNo == 0 or OpNo == 1); - if (isa(TheCall->getArgOperand(2))) { - if (OpNo == 0) { - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - Results.TaintedStores.insert(TheUser); - } - if (OpNo == 1) { - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - Results.TaintedLoads.insert(TheUser); - } - } else { - TaintLog << "Illegal -- unknown size memcpy" << DoLog; - Results.IllegalCalls.insert(TheCall); - } - break; - } else if (Callee->empty()) { - TaintLog << "Illegal -- no body" << DoLog; - Results.IllegalCalls.insert(TheCall); - break; - } - revng_assert(ReachableFunctions.contains(Callee)); - - // Select the correct formal argument associated with this use - const Argument *FormalArgument = nullptr; - unsigned ArgNo = 0; - for (const Argument &Arg : Callee->args()) { - ArgNo = Arg.getArgNo(); - if (TheUse->getOperandNo() == ArgNo) { - FormalArgument = &Arg; - break; - } - } - TaintLog << "Found Argument" << DoLog; - revng_assert(FormalArgument != nullptr); - - // Taint the Argument, and if this is the first time we taint it we - // also push on the ToTaintWorkList its first use that is not tainted. - if (TaintLog.isEnabled()) { - TaintLog << "Argument: " << FormalArgument << DoLog; - TaintLog << dumpToString(FormalArgument) << DoLog; - } - bool JustTainted = Results.TaintedValues.insert(FormalArgument).second; - if (JustTainted) { - TaintLog << "Just Tainted" << DoLog; - for (const Use &U : FormalArgument->uses()) { - if (TaintLog.isEnabled()) { - TaintLog << "User: " << U.getUser() << DoLog; - TaintLog << dumpToString(U.getUser()) << DoLog; - } - if (!Results.TaintedValues.contains(U.getUser())) { - TaintLog << "PUSH" << DoLog; - ToTaintWorkList.push(&U); - TaintLog.indent(); - - // We also push the call to the CallSites stack, because if we put - // the uses of the argument on the ToTaintWorkList we are actually - // starting to perform the analysis inside the callee. - CallSiteInfos.push(CallSiteInfo(TheCall, FormalArgument, ArgNo)); - break; - } - } - } else if (FunctionArgTaintsReturn.contains({ Callee, - FormalArgument })) { - // It means that a previous exploration of the graph has reached this - // argument, and propagated a taint until a return value. This means - // that this we must taint the call and start exploring its unexplored - // users if any. - bool JustTainted = Results.TaintedValues.insert(TheUser).second; - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - if (JustTainted) { - TaintLog << "Just Tainted" << DoLog; - for (const Use &U : TheUser->uses()) { - TaintLog << "User: " << U.getUser() << DoLog; - if (!Results.TaintedValues.contains(U.getUser())) { - TaintLog << "PUSH" << DoLog; - ToTaintWorkList.push(&U); - TaintLog.indent(); - break; - } - } - } - } - } break; - case Instruction::Ret: { - - // 3(b) If the next `Use` to explore is a `RetInst` the taint is - // propagated interprocedurally to the Function. - // (propagation from the callee to all call sites) - TaintLog << "RET" << DoLog; - revng_assert(not CallSiteInfos.empty()); - - // Taint the return instruction, then, if this is the first time that we - // taint also the call site, so that it's marked for propagation of the - // taint analysis to its uses. - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << TheUser << DoLog; - TaintLog << dumpToString(TheUser) << DoLog; - } - bool JustTainted = Results.TaintedValues.insert(TheUser).second; - if (JustTainted) { - const CallSiteInfo &CSInfo = CallSiteInfos.top(); - - Results.TaintedValues.insert(CSInfo.CallSite); - - const Function *Callee = getCallee(CSInfo.CallSite); - FunctionArgTaintsReturn.insert({ Callee, CSInfo.Arg }); - - if (TaintLog.isEnabled()) { - TaintLog << "TAINT: " << CSInfo.CallSite << DoLog; - TaintLog << dumpToString(CallSiteInfos.top().CallSite) << DoLog; - llvm::StringRef Name = getCallee(CSInfo.CallSite)->getName(); - TaintLog << "pair: < " << Name << ", " << CSInfo.ArgNo << " > " - << DoLog; - } - } - } break; - case Instruction::Switch: - case Instruction::ICmp: - case Instruction::And: - case Instruction::Or: - break; - default: - revng_abort(); - } - - // If we pushed something new on the ToTaintWorkList we want to keep - // exploring its uses until we reach a leaf. - if (Size < ToTaintWorkList.size()) - continue; - TaintLog << "not grown" << DoLog; - - // 4. If we didn't push anything on the WorkList we can start exploring - // the other `Use`s of the item that is currently on top of the WorkList - if (Size == ToTaintWorkList.size()) { - Use *NextUse = TheUse->getNext(); - if (NextUse != nullptr) { - TaintLog << "advance" << DoLog; - ToTaintWorkList.top() = NextUse; - continue; - } - } - - TaintLog << "Done" << DoLog; - - // 5. If we didn't push anything on the WorkList we can start popping - // `Use`s from the WorkList, until we reach a `Value` that still has - // unexplored `Use`s - // - // If we reach this point we have finished to explore all the uses of the - // item that is currently on top of the ToTaintWorkList stack. - // We want to pop it and to handle Arguments in a special way. - const Use *UnexploredUse = nullptr; - while (not ToTaintWorkList.empty() and UnexploredUse == nullptr) { - const Use *PoppedTopUse = ToTaintWorkList.top(); - if (TaintLog.isEnabled()) { - TaintLog << "POP : " << PoppedTopUse->get() << DoLog; - TaintLog << dumpToString(PoppedTopUse->get()) << DoLog; - } - if (TaintLog.isEnabled()) { - TaintLog << "PoppedUser : " << PoppedTopUse->getUser() << DoLog; - TaintLog << dumpToString(PoppedTopUse->getUser()) << DoLog; - } - ToTaintWorkList.pop(); - TaintLog.unindent(); - Argument *Arg = dyn_cast(PoppedTopUse->get()); - bool PoppedHasAllExploredSources = PoppedTopUse->getNext() == nullptr; - if (Arg and PoppedHasAllExploredSources) { - // 5(a) If we're popping an argument of a function this means that we - // finished analyzing the uses of that argument. We have to make sure - // that, if the taint reached the return instructions in the function, - // the taint is propagated to the call sites. - TaintLog << "Finish Argument" << DoLog; - - const CallSiteInfo &CSInfo = CallSiteInfos.top(); - revng_assert(CSInfo.Arg == Arg); - - unsigned ArgNo = Arg->getArgNo(); - revng_assert(CSInfo.ArgNo == ArgNo); - - const User *ArgUser = ToTaintWorkList.top()->getUser(); - const auto *CallSite = cast(ArgUser); - const Function *Callee = getCallee(CallSite); - - if (TaintLog.isEnabled()) { - TaintLog << "CallSite: " << CallSite << DoLog; - TaintLog << dumpToString(CallSite) << DoLog; - } - - // If the CallSite was tainted it means that the taint analysis - // reached at least one of the return values of the callee. - // Hence, the taint can propagate to the uses of the call. - // The same holds if we already know that the pair (Callee, ArgNo) - // taints the return. - if (Results.TaintedValues.contains(CallSite) - or FunctionArgTaintsReturn.contains({ Callee, Arg })) { - if (CallSite->getNumUses() != 0) { - UnexploredUse = &*CallSite->use_begin(); - } - } - - // If PoppedTopUse is an Use of an Argument the next available Use on - // the stack must be an argument of a CallInst. This CallInst was the - // call from where we started analyzing the uses of the function - // Argument for which we just popped the PoppedTopUse. - CallSiteInfos.pop(); - } else { - TaintLog << "NOT Finished or NOT Argument" << DoLog; - UnexploredUse = PoppedTopUse->getNext(); - } - } - - // 6. After popping the top of the WorkList in 5., if the new top of the - // WorkList still has unexplored uses start to explore them. - - // If we have a new UnexploredUse we push it and we continue - // because that's the new use that must be analyzed. - if (UnexploredUse != nullptr) { - TaintLog << "PUSH: " << UnexploredUse->get() << DoLog; - TaintLog << "User: " << UnexploredUse->getUser() << DoLog; - ToTaintWorkList.push(UnexploredUse); - TaintLog.indent(); - } - } - } - - if (not Lazy) { - QuickMetadata QMD(M->getContext()); - for (CallInst *Call : Results.IllegalCalls) { - CallInst &Abort = emitMessage(Call, "", Call->getDebugLoc()); - auto IllegalCallsMDKind = M->getContext().getMDKindID("revng.csaa." - "illegal.calls"); - Abort.setMetadata(IllegalCallsMDKind, QMD.tuple((uint32_t) 0)); - } - } - return Results; -} - -class WorkItem { - -public: - using size_type = SmallVector::size_type; - using iterator = SmallVector::iterator; - using const_iterator = SmallVector::const_iterator; - -private: - // The value whose sources we're analyzing - Value *CurrentValue = nullptr; - - // Sources are kind of the opposite of `Use`s. Every pointer in this vector - // points to a `Use` whose `User` is the `Value` pointed by the `CurrentValue` - // member of this `WorkItem`. - SmallVector Sources; - - // The index of the Source that is currently considered for the analysis - size_type SourceIndex; - -public: - WorkItem() : CurrentValue(nullptr), Sources(), SourceIndex(0) {} - - explicit WorkItem(Instruction *I) : - CurrentValue(I), Sources(), SourceIndex(0) { - if (not isa(I)) { - for (const Use &OpUse : I->operands()) { - Sources.push_back(&OpUse); - } - } else { - const auto PtrOpNum = StoreInst::getPointerOperandIndex(); - Sources.push_back(&I->getOperandUse(PtrOpNum)); - } - revng_assert(not Sources.empty()); - } - - explicit WorkItem(Argument *A, - const ConstFunctionPtrSet &ReachableFunctions, - const bool IsLazy, - const unsigned LoadMDKind, - const unsigned StoreMDKind) : - CurrentValue(A), Sources(), SourceIndex(0) { - const Function *F = A->getParent(); - revng_assert(not F->empty()); - revng_log(CSVAccessLog, "Function: " << F); - const unsigned ArgNo = A->getArgNo(); - revng_log(CSVAccessLog, "ArgNo: " << ArgNo); - for (const Use &FUse : F->uses()) { - const User *FUser = FUse.getUser(); - revng_log(CSVAccessLog, "FUser: " << FUser); - if (const auto *FCall = dyn_cast(FUser)) { - revng_log(CSVAccessLog, "Is a Call"); - const Function *Caller = FCall->getFunction(); - revng_log(CSVAccessLog, "Caller: " << Caller); - - // If this call has already been decorated with LoadMDKind or - // StoreMDKind metadata it means that it has already been processed by - // a previous run of CPUStateAccessAnalysis. - // If we're running in lazy mode, we can skip calls that have already - // been decorated. - if (IsLazy) { - if (FCall->getMetadata(LoadMDKind) != nullptr - or FCall->getMetadata(StoreMDKind) != nullptr) { - revng_log(CSVAccessLog, "Already marked"); - continue; - } - } - - if (ReachableFunctions.contains(Caller)) { - revng_log(CSVAccessLog, - "Is Reachable CallInst:" << FCall << " : " - << dumpToString(FCall)); - const Use &ActualArgUse = FCall->getArgOperandUse(ArgNo); - revng_log(CSVAccessLog, - "ActualUse:" << ActualArgUse.getUser() << " : " - << dumpToString(ActualArgUse.getUser())); - Sources.push_back(&ActualArgUse); - } else { - revng_log(CSVAccessLog, "NOT Reachable"); - } - } else if (const auto *CExpr = dyn_cast(FUser)) { - revng_log(CSVAccessLog, "Bitcast"); - const auto OpCode = CExpr->getOpcode(); - revng_assert(OpCode == Instruction::BitCast); - for (const User *RealCall : CExpr->users()) { - revng_log(CSVAccessLog, - "RealCall: " << RealCall << " : " - << dumpToString(RealCall)); - const auto *FCall = dyn_cast(RealCall); - revng_log(CSVAccessLog, - "CallInst: " << FCall << " : " << dumpToString(FCall)); - - // If this call has already been decorated with LoadMDKind or - // StoreMDKind metadata it means that it has already been processed by - // a previous run of CPUStateAccessAnalysis. - // If we're running in lazy mode, we can skip calls that have already - // been decorated. - if (IsLazy) { - if (FCall->getMetadata(LoadMDKind) != nullptr - or FCall->getMetadata(StoreMDKind) != nullptr) { - revng_log(CSVAccessLog, "Already marked"); - continue; - } - } - - if (FCall) { - const Function *Caller = FCall->getFunction(); - revng_log(CSVAccessLog, "Caller: " << Caller); - if (ReachableFunctions.contains(Caller)) { - const Use &ActualArgUse = FCall->getArgOperandUse(ArgNo); - revng_log(CSVAccessLog, - "ActualUse:" << ActualArgUse.getUser() << " : " - << dumpToString(ActualArgUse.getUser())); - Sources.push_back(&ActualArgUse); - } - } - } - } - } - // This might be too strict, because the arguments of the root function - // don't have any sources. However, we assume that we never reach them. - revng_assert(not Sources.empty()); - } - - explicit WorkItem(CallInst *C, - const bool IsLoad, - const bool IsLazy, - const unsigned LoadMDKind, - const unsigned StoreMDKind) : - CurrentValue(C), Sources(), SourceIndex(0) { - - revng_assert(not IsLazy - or (C->getMetadata(LoadMDKind) == nullptr - and C->getMetadata(StoreMDKind) == nullptr)); - - const Function *F = getCallee(C); - revng_assert(F != nullptr); // Assume no indirect calls - if (F->getIntrinsicID() == Intrinsic::memcpy) { - const Use &AddrOp = C->getOperandUse(IsLoad ? 1 : 0); - Sources.push_back(&AddrOp); - const Use &SizeOp = C->getOperandUse(2); - Sources.push_back(&SizeOp); - } else { - for (const BasicBlock &BB : *F) { - const Instruction *I = BB.getTerminator(); - if (I and isa(I) and I->getNumOperands() != 0) { - revng_assert(I->getNumOperands() == 1); - const Use &RetValUse = I->getOperandUse(0); - Sources.push_back(&RetValUse); - } - } - } - revng_assert(not Sources.empty()); - } - -public: - friend inline void writeToLog(Logger &L, const WorkItem &I, int) { - L << "Value: " << I.val() << " : " << dumpToString(I.val()) << DoLog; - L << "Sources = {" << DoLog; - L.indent(); - for (const Use *U : I.sources()) - L << U->get() << " : " << dumpToString(U->get()) << DoLog; - L << "}" << DoLog; - L << "Curr Src Id: " << I.SourceIndex; - L.unindent(); - } - -public: - Value *val() const { return CurrentValue; }; - - const Use *currentSourceUse() const { - if (SourceIndex < Sources.size()) - return Sources[SourceIndex]; - return nullptr; - } - - Value *currentSourceValue() const { - const Use *CurrUse = currentSourceUse(); - return CurrUse ? CurrUse->get() : nullptr; - } - - const Use *nextSourceUse() const { - const auto Size = Sources.size(); - if (SourceIndex < Size and (SourceIndex + 1) < Size) - return Sources[SourceIndex + 1]; - return nullptr; - } - - Value *nextSourceValue() const { - const Use *CurrUse = nextSourceUse(); - return CurrUse ? CurrUse->get() : nullptr; - } - - size_type getSourceIndex() const { return SourceIndex; } - - void setSourceIndex(size_type I) { - revng_assert(I < Sources.size()); - SourceIndex = I; - } - - size_type getNumSources() const { return Sources.size(); } - - llvm::iterator_range sources() const { - return { Sources.begin(), Sources.end() }; - } -}; - -/// Gets a valid pointer to `CallInst` if the current source of `Item` is a call -/// from `Root` -/// -/// This function returns `nullptr` if the current source of `Item` is not a -/// call from `Root` -static CallInst *getCurSourceRootCall(const WorkItem &Item, - const Function *Root) { - revng_log(CSVAccessLog, "getCurSourceRootCall"); - CallInst *RootCall = nullptr; - if (isa(Item.val())) { - revng_log(CSVAccessLog, "isa"); - User *ActualArgUser = Item.currentSourceUse()->getUser(); - auto *Call = cast(ActualArgUser); - revng_log(CSVAccessLog, "argument: " << dumpToString(Item.val())); - revng_log(CSVAccessLog, "call: " << dumpToString(Call)); - Function *F = Call->getFunction(); - revng_log(CSVAccessLog, "parent: " << F->getName()); - if (F == Root) - RootCall = Call; - } - revng_log(CSVAccessLog, RootCall); - return RootCall; -} - -/// Gets a valid pointer to `CallInst` if the next source of `Item` is a call -/// from `Root` -/// -/// This function return `nullptr` if the next source of `Item` is not a call -/// from `Root` -static CallInst *getNextSourceRootCall(const WorkItem &Item, - const Function *Root) { - revng_log(CSVAccessLog, "getNextSourceRootCall"); - CallInst *RootCall = nullptr; - if (isa(Item.val())) { - revng_log(CSVAccessLog, "isa"); - const Use *NextSrcUse = Item.nextSourceUse(); - if (NextSrcUse != nullptr) { - User *ActualArgUser = NextSrcUse->getUser(); - auto *Call = cast(ActualArgUser); - revng_log(CSVAccessLog, "argument: " << dumpToString(Item.val())); - revng_log(CSVAccessLog, "call: " << dumpToString(Call)); - Function *F = Call->getFunction(); - revng_log(CSVAccessLog, "parent: " << F->getName()); - if (F == Root) - RootCall = Call; - } - } - revng_log(CSVAccessLog, RootCall); - return RootCall; -} - -/// Gets the `Shift`-th bit of `Input` -static int getBit(uint64_t Input, int Shift) { - return (Input >> Shift) & 1; -}; - -using CallSiteOffsetMap = std::map; -using ValueCallSiteOffsetMap = std::map; -using OptCSVOffsets = std::optional; - -/// This class is used to fold constant offsets on different instructions -template -class CRTPOffsetFolder { - -protected: - using offset_iterator = std::set::const_iterator; - using offset_iterator_range = llvm::iterator_range; - using OffsetPair = std::pair; - -protected: - // These should be constant but ConstantInt::get() does not have - // const-qualifier on the first argument: - // static ConstantInt *get(IntegerType *Ty, uint64_t V, bool isSigned=false) - // However, it does not really change Ty, because it only call const - // methods on it, so it should be safe. - IntegerType *Int64Ty = nullptr; - IntegerType *Int32Ty = nullptr; - const DataLayout &DL; - -public: - CRTPOffsetFolder(const Module &M) : - Int64Ty(IntegerType::getInt64Ty(M.getContext())), - Int32Ty(IntegerType::getInt32Ty(M.getContext())), - DL(M.getDataLayout()) {} - - static void insertOrCombine(Value *V, - CallInst *C, - CSVOffsets &&O, - ValueCallSiteOffsetMap &OffsetMap) { - revng_log(CSVAccessLog, "MAP: " << V); - bool Inserted; - CallSiteOffsetMap::iterator It; - std::tie(It, Inserted) = OffsetMap[V].insert(std::make_pair(C, O)); - if (not Inserted) - It->second.combine(O); - } - -public: - /// This method folds the offsets on the sources to Item - /// - /// \param Item is the `WorkItem` whose sources must be folded - /// \param [in, out] is the Map used to retrieve the values of the offsets of - /// the sources of `Item`, and also to store the result of - /// the folded offsets for `Item`. - void fold(const WorkItem &Item, ValueCallSiteOffsetMap &OffsetMap) { - WorkItem::size_type NumSrcs = Item.getNumSources(); - revng_assert(NumSrcs); - SmallVector Operands(NumSrcs, nullptr); - // Collect Call Sites across all the sources of this User - CallPtrSet CallSites; - SmallVector SrcCallSiteOffsetsPtrs; - SmallVector NonRootOffsetsPtrs; - SrcCallSiteOffsetsPtrs.reserve(NumSrcs); - NonRootOffsetsPtrs.reserve(NumSrcs); - for (const Use *U : Item.sources()) { - const CallSiteOffsetMap &CallSiteOffsets = OffsetMap.at(U->get()); - revng_assert(not CallSiteOffsets.empty()); - const CSVOffsets *NonRootOffsets = nullptr; - for (const auto &CSO : CallSiteOffsets) { - CallInst *TheCall = CSO.first; - CallSites.insert(TheCall); - if (TheCall == nullptr) { - NonRootOffsets = &CSO.second; - } - } - - // The order of iteration on SrcCallSiteOffsetsPtrs is the same - // as the order of iteration on Item.sources() - SrcCallSiteOffsetsPtrs.push_back(&CallSiteOffsets); - - // The order of iteration on NonRootOffsets is the same as the order - // of iteration on Item.sources() - NonRootOffsetsPtrs.push_back(NonRootOffsets); - } - revng_assert(NumSrcs == SrcCallSiteOffsetsPtrs.size()); - revng_assert(NumSrcs == NonRootOffsetsPtrs.size()); - - if (CSVAccessLog.isEnabled()) - for (const CallInst *C : CallSites) - CSVAccessLog << "C: " << C << " : " << dumpToString(C) << DoLog; - - // Check that each source has all the callsites or nullptr - for (const auto &CSOffsets : SrcCallSiteOffsetsPtrs) { - bool FoundNullptr = false; - if (CSOffsets->contains(nullptr)) - FoundNullptr = true; - - bool FoundAllCalls = true; - for (CallInst *C : CallSites) { - if (C != nullptr and not CSOffsets->contains(C)) { - FoundAllCalls = false; - break; - } - } - revng_assert(FoundNullptr or FoundAllCalls); - } - - for (CallInst *C : CallSites) { - SmallVector SrcOffsets(NumSrcs, - OffsetPair(nullptr, nullptr)); - bool EmptyPair = false; - for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { - const CSVOffsets *NonRootOffset = NonRootOffsetsPtrs[SI]; - if (C != nullptr) - SrcOffsets[SI].second = NonRootOffset; - const auto CallSiteOffsets = SrcCallSiteOffsetsPtrs[SI]; - auto OffsetsEnd = CallSiteOffsets->end(); - auto OffsetsIt = CallSiteOffsets->find(C); - if (OffsetsIt != OffsetsEnd) - SrcOffsets[SI].first = &OffsetsIt->second; - if (SrcOffsets[SI].first == nullptr - and SrcOffsets[SI].second == nullptr) { - // This means that one the pairs is empty and we can drop entirely - // this call. This happens when C == nullptr and one of the sources - // has no nullptr call site - EmptyPair = true; - } - } - if (EmptyPair) - continue; - revng_log(CSVAccessLog, "start"); - revng_assert(NumSrcs < (8ULL * sizeof(uint64_t))); - uint64_t Combinations = 1ULL << NumSrcs; - Value *V = Item.val(); - Instruction *I = cast(V); - for (uint64_t Index = 0; Index < Combinations; ++Index) { - revng_log(CSVAccessLog, "Index: " << Index); - SmallVector OffsetTuple; - OffsetTuple.reserve(NumSrcs); - - // Build the tuple of offset sets that we want to use to compute the - // transfer function. If the analyzed call is nullptr we can skip - // some stuff and keep the computation smaller. - revng_log(CSVAccessLog, "callsite: " << C << " : " << dumpToString(C)); - if (C != nullptr) { - for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { - int Bit = getBit(Index, SI); - const CSVOffsets *O0 = SrcOffsets[SI].first; - const CSVOffsets *O1 = SrcOffsets[SI].second; - const CSVOffsets *O = Bit ? O1 : O0; - if (O == nullptr) - break; - revng_log(CSVAccessLog, "nonnull"); - OffsetTuple.push_back(O); - } - } else { - for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { - const CSVOffsets *O = NonRootOffsetsPtrs[SI]; - if (O == nullptr) - break; - revng_log(CSVAccessLog, "nonnull"); - OffsetTuple.push_back(O); - } - } - revng_log(CSVAccessLog, - "NumSrcs:" << NumSrcs - << " -- Tuple Size:" << OffsetTuple.size()); - if (OffsetTuple.size() != NumSrcs) - continue; - - bool Valid; - CSVOffsets::Kind ResKind; - SmallVector, 4> UpdatedOffsetTuple(NumSrcs); - revng_assert(not UpdatedOffsetTuple[0].has_value()); - std::tie(Valid, - ResKind) = T::checkOffsetTupleIsValid(OffsetTuple, - I, - UpdatedOffsetTuple); - revng_assert(UpdatedOffsetTuple.size() == OffsetTuple.size()); - if (not Valid) { - revng_log(CSVAccessLog, "invalid tuple"); - revng_log(CSVAccessLog, CSVOffsets(ResKind)); - insertOrCombine(V, C, CSVOffsets(ResKind), OffsetMap); - continue; - } - revng_log(CSVAccessLog, "valid tuple"); - - SmallVector OffsetsRanges; - SmallVector OffsetsIt; - OffsetsRanges.reserve(NumSrcs); - OffsetsIt.reserve(NumSrcs); - - WorkItem::size_type CartesianSize = 1; - for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { - OptCSVOffsets &UpdatedOffsets = UpdatedOffsetTuple[SI]; - bool WasUpdated = UpdatedOffsets.has_value(); - const CSVOffsets *Tuple = WasUpdated ? &*UpdatedOffsets : - OffsetTuple[SI]; - OffsetsRanges.push_back(make_range(Tuple->begin(), Tuple->end())); - OffsetsIt.push_back(Tuple->begin()); - const WorkItem::size_type OffsetSize = Tuple->size(); - revng_assert(OffsetSize); - revng_assert(CartesianSize <= CartesianSize * OffsetSize); - CartesianSize *= OffsetSize; - } - - do { - CSVOffsets ResOffset = foldOffsets(ResKind, NumSrcs, I, OffsetsIt); - insertOrCombine(V, C, std::move(ResOffset), OffsetMap); - // Advance the iterators - { - WorkItem::size_type SI = 0; - bool Wrapped = false; - do { - revng_log(CSVAccessLog, "SI: " << SI); - if (std::next(OffsetsIt[SI]) == OffsetsRanges[SI].end()) { - OffsetsIt[SI] = OffsetsRanges[SI].begin(); - Wrapped = true; - revng_log(CSVAccessLog, "WRAP"); - } else { - revng_log(CSVAccessLog, "NO-WRAP"); - std::advance(OffsetsIt[SI], 1); - Wrapped = false; - } - } while (Wrapped and ++SI < NumSrcs); - revng_log(CSVAccessLog, "incremented"); - } - } while (--CartesianSize); - } - } - } - -private: - CSVOffsets foldOffsets(CSVOffsets::Kind ResKind, - WorkItem::size_type NumSrcs, - Instruction *I, - const SmallVector &OffsetsIt) { - return static_cast(this)->foldOffsets(ResKind, NumSrcs, I, OffsetsIt); - } -}; - -/// Specialization of CRTPOffsetFolder for sums and subtractions -class AddSubOffsetFolder : public CRTPOffsetFolder { - -public: - AddSubOffsetFolder(const Module &M) : - CRTPOffsetFolder(M) {} - -public: - friend class CRTPOffsetFolder; - -private: - static std::pair - checkOffsetTupleIsValid(const SmallVector &OffsetTuple, - const Instruction *I, - SmallVector &) { - revng_assert(OffsetTuple.size() == 2); - auto OpCode = I->getOpcode(); - revng_assert(OpCode == Instruction::Add or OpCode == Instruction::Sub); - const auto O0 = OffsetTuple[0], O1 = OffsetTuple[1]; - if (OpCode == Instruction::Add) { - // Cannot add pointers - revng_assert(not(O0->isPtr() and O1->isPtr())); - } else { - // Cannot subtract a pointer from something else - revng_assert(not O1->isPtr()); - } - - if (O0->isUnknown() or O1->isUnknown()) { - if (O0->isOnlyInPtr() or O1->isOnlyInPtr()) - return { false, CSVOffsets::Kind::UnknownInPtr }; - if (O0->isInOutPtr() or O1->isInOutPtr()) - return { false, CSVOffsets::Kind::OutAndUnknownInPtr }; - return { false, CSVOffsets::Kind::Unknown }; - } - - { - bool Num0 = O0->isNumeric(); - if (Num0 or O1->isNumeric()) { - CSVOffsets::Kind ResKind = Num0 ? O1->getKind() : O0->getKind(); - if (O0->isUnknownInPtr() or O1->isUnknownInPtr()) - return { false, ResKind }; - else - return { true, ResKind }; - } - } - revng_abort(); - } - - CSVOffsets foldOffsets(CSVOffsets::Kind ResultKind, - WorkItem::size_type NumSrcs, - Instruction *I, - const SmallVector &OffsetsIt) { - auto OpCode = I->getOpcode(); - revng_assert(OpCode == Instruction::Add or OpCode == Instruction::Sub); - SmallVector Operands(NumSrcs, nullptr); - // Setup operands - for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { - const int64_t O = *OffsetsIt[SI]; - Operands[SI] = ConstantInt::get(Int64Ty, APInt(64, O, true)); - } - // Constant fold the operation with the selected operands - ArrayRef TmpOp(Operands); - Constant *Res = ConstantFoldInstOperands(I, TmpOp, DL); - ConstantInt *R = cast(Res); - const int64_t ResO = R->getSExtValue(); - return CSVOffsets(ResultKind, ResO); - } -}; - -/// Specialization of CRTPOffsetFolder for GEPs -class GEPOffsetFolder : public CRTPOffsetFolder { - -public: - GEPOffsetFolder(const Module &M) : CRTPOffsetFolder(M) {} - -public: - friend class CRTPOffsetFolder; - -private: - static std::pair - checkOffsetTupleIsValid(const SmallVector &OffsetTuple, - const Instruction *I, - SmallVector &UpdatedOffsetTuple) { - auto OpCode = I->getOpcode(); - revng_assert(OpCode == Instruction::GetElementPtr); - size_t NOperands = OffsetTuple.size(); - revng_assert(NOperands > 1); - CSVOffsets::Kind GEPOp0Kind = OffsetTuple[0]->getKind(); - if (CSVOffsets::isUnknownInPtr(GEPOp0Kind) - or CSVOffsets::isUnknown(GEPOp0Kind)) - return { false, GEPOp0Kind }; - - auto *GEP = cast(I); - Type *PointeeTy = GEP->getSourceElementType(); - - if (GEP->hasIndices() and PointeeTy->isArrayTy()) { - - auto FirstIdxConst = dyn_cast(*GEP->idx_begin()); - bool FirstIdxPropagatedConst = OffsetTuple[1]->size() == 1; - revng_assert(not(FirstIdxConst != nullptr) or FirstIdxPropagatedConst); - - if (FirstIdxPropagatedConst) { - - bool FirstIdxZero = FirstIdxConst->isZero(); - bool FirstIdxPropagatedZero = *OffsetTuple[1]->begin() == 0; - revng_assert(not FirstIdxZero or FirstIdxPropagatedZero); - - if (FirstIdxPropagatedZero) { - SmallVector ConstIdxList = { 0 }; - - auto IdxIt = GEP->idx_begin(); - auto IdxEnd = GEP->idx_end(); - int IdxOpNum = 1; - std::set LastTypeOffsets = { 0 }; - - for (; IdxIt != IdxEnd; ++IdxIt, ++IdxOpNum) { - const CSVOffsets *IdxCSVOffset = OffsetTuple[IdxOpNum]; - revng_assert(not IdxCSVOffset->isPtr()); - - Type *ElementTy = GEP->getIndexedType(PointeeTy, ConstIdxList); - - if (ElementTy->isAggregateType()) { - if (ElementTy->isArrayTy()) { - if (IdxCSVOffset->isUnknown()) { - CSVOffsets U(CSVOffsets::Kind::Numeric, LastTypeOffsets); - UpdatedOffsetTuple[IdxOpNum] = std::move(U); - } else { - // If it's not Unknown we can leave it like it is. - } - - auto *ArrayTy = cast(ElementTy); - uint64_t ArrayNumElem = ArrayTy->getNumElements(); - revng_assert(ArrayNumElem); - LastTypeOffsets.clear(); - for (uint64_t O = 0; O < ArrayNumElem; ++O) - LastTypeOffsets.insert(O); - - ConstIdxList.push_back(0); - } else if (ElementTy->isStructTy()) { - if (IdxCSVOffset->isUnknown()) { - if (IdxIt + 1 != IdxEnd) { - // I cannot fold structs with unknown index. - // Early exit. - return { false, CSVOffsets::makeUnknown(GEPOp0Kind) }; - } else { - revng_assert(not LastTypeOffsets.empty()); - CSVOffsets U(CSVOffsets::Kind::Numeric, LastTypeOffsets); - UpdatedOffsetTuple[IdxOpNum] = std::move(U); - break; - } - } else { - // If it's not Unknown we can leave it like it is. - revng_assert(not IdxCSVOffset->empty()); - ConstIdxList.push_back(*IdxCSVOffset->begin()); - - revng_assert(IdxCSVOffset->size() != 0); - LastTypeOffsets.clear(); - LastTypeOffsets.insert(IdxCSVOffset->begin(), - IdxCSVOffset->end()); - } - } else { - revng_abort(); - } - } else { - // I'm done. - revng_assert(IdxIt + 1 == IdxEnd); - if (IdxCSVOffset->isUnknown()) { - CSVOffsets U(CSVOffsets::Kind::Numeric, LastTypeOffsets); - UpdatedOffsetTuple[IdxOpNum] = std::move(U); - } else { - // If it's not Unknown we can leave it like it is. - } - } - } - return { true, GEPOp0Kind }; - } else { - // For now we don't handle cases when the first index of the GEP is - // not zero, so in those case we fall back outside the if and we fold - // them as usual. - } - } else { - // For now we don't handle cases when the first index of the GEP is - // not constant, so in those case we fall back outside the if and we - // fold them as usual. - } - } else { - // For now we don't handle cases when the GEP does not index an array, so - // in those case we fall back outside the if and we fold them as usual. - } - - for (size_t O = 1; O < NOperands; O++) { - revng_assert(not OffsetTuple[O]->isPtr()); - if (OffsetTuple[O]->isUnknown()) - return { false, CSVOffsets::makeUnknown(GEPOp0Kind) }; - } - return { true, GEPOp0Kind }; - } - - CSVOffsets foldOffsets(CSVOffsets::Kind ResultKind, - WorkItem::size_type NumSrcs, - Instruction *I, - const SmallVector &OffsetsIt) { - const auto *GEP = cast(I); - const auto PtrOpTy = GEP->getPointerOperand()->getType(); - SmallVector Operands(NumSrcs, nullptr); - // Setup operands - int64_t PtrOp = *OffsetsIt[0]; - Operands[0] = Constant::getIntegerValue(PtrOpTy, APInt(64, PtrOp, true)); - for (WorkItem::size_type SI = 1; SI < NumSrcs; ++SI) { - const int64_t O = *OffsetsIt[SI]; - Operands[SI] = ConstantInt::get(Int32Ty, APInt(32, O, true)); - } - // Constant fold the operation with the selected operands - ArrayRef TmpOp(Operands); - Constant *Res = ConstantFoldInstOperands(I, TmpOp, DL); - ConstantInt *R = getConstValue(Res, DL); - const int64_t ResO = getSExtValue(R, DL); - return CSVOffsets(ResultKind, ResO); - } -}; - -/// Specialization of CRTPOffsetFolder for non-address binary operations -class NumericOffsetFolder : public CRTPOffsetFolder { - -public: - NumericOffsetFolder(const Module &M) : - CRTPOffsetFolder(M) {} - -public: - friend class CRTPOffsetFolder; - -private: - static std::pair - checkOffsetTupleIsValid(const SmallVector &OffsetTuple, - const Instruction *I, - SmallVector &) { - revng_assert(OffsetTuple.size() == 2); - - auto OpCode = I->getOpcode(); - revng_assert(OpCode == Instruction::Shl or OpCode == Instruction::AShr - or OpCode == Instruction::LShr or OpCode == Instruction::Mul - or OpCode == Instruction::URem or OpCode == Instruction::SRem - or OpCode == Instruction::SDiv or OpCode == Instruction::UDiv); - - const auto O0 = OffsetTuple[0], O1 = OffsetTuple[1]; - revng_assert(not O0->isPtr() and not O1->isPtr()); - if (O0->isUnknown() or O1->isUnknown()) { - return std::make_pair(false, CSVOffsets::Kind::Unknown); - } else { - return std::make_pair(true, CSVOffsets::Kind::Numeric); - } - } - - CSVOffsets foldOffsets(CSVOffsets::Kind ResultKind, - WorkItem::size_type NumSrcs, - Instruction *I, - const SmallVector &OffsetsIt) { - - auto OpCode = I->getOpcode(); - revng_assert(OpCode == Instruction::Shl or OpCode == Instruction::AShr - or OpCode == Instruction::LShr or OpCode == Instruction::Mul - or OpCode == Instruction::URem or OpCode == Instruction::SRem - or OpCode == Instruction::SDiv or OpCode == Instruction::UDiv); - - SmallVector Operands(NumSrcs, nullptr); - // Setup operands - for (WorkItem::size_type SI = 0; SI < NumSrcs; ++SI) { - const int64_t O = *OffsetsIt[SI]; - Operands[SI] = ConstantInt::get(Int64Ty, APInt(64, O, true)); - } - // Constant fold the operation with the selected operands - ArrayRef TmpOp(Operands); - Constant *Res = ConstantFoldInstOperands(I, TmpOp, DL); - ConstantInt *R = cast(Res); - const int64_t ResO = R->getSExtValue(); - return CSVOffsets(ResultKind, ResO); - } -}; - -using AccessOffsetMap = CPUStateAccessAnalysisPass::AccessOffsetMap; - -class CPUStateAccessOffsetAnalysis { - -private: - const bool Lazy; - const unsigned LoadMDKind; - const unsigned StoreMDKind; - const Module &M; - const Value *CPUStatePtr = nullptr; - const Function *RootFunction = nullptr; - const ConstFunctionPtrSet &ReachableFunctions; - const TaintResults &TaintedAccesses; - VariableManager *Variables = nullptr; - - AccessOffsetMap &LoadOffsets; // result, maps load or load-memcpy to offsets - AccessOffsetMap &StoreOffsets; // result, maps store or store-memcpy to - // offsets - CallSiteOffsetMap &CallSiteLoadOffsets; // result, maps call in root to load - // offsets - CallSiteOffsetMap &CallSiteStoreOffsets; // result, maps call in root to store - // offsets - - // ValueCallSiteOffsets is used to keep track of the offsets associated with - // each value. The primary key is the `Value` for which we're tracking the - // offsets. The secondary key is a `CallInst` representing a call site in - // root. This call site represents the call from which the mapped offsets are - // possible. The mapped value is a `CSVOffsets`. - ValueCallSiteOffsetMap ValueCallSiteOffsets; - - // The two following maps have the same structure as ValueCallSiteOffsets, but - // they are use to hold results on the specific loads and stores that access - // CSV. They are used after the computation to generate the metadata to attach - // to the root call sites and the final results of the analysis. - ValueCallSiteOffsetMap LoadCallSiteOffsets; - ValueCallSiteOffsetMap StoreCallSiteOffsets; - - CallPtrSet CrossedCallSites; - using WorkListVector = std::vector; - WorkListVector WorkList; - ConstValuePtrSet InExploration; - - // Helper folders - AddSubOffsetFolder AddSubFolder; - NumericOffsetFolder NumericFolder; - GEPOffsetFolder GEPFolder; - - const Function *CpuLoop = nullptr; - -public: - CPUStateAccessOffsetAnalysis(const Module &Mod, - const Value *EnvPtr, - const Function *Root, - const ConstFunctionPtrSet &Reachable, - const TaintResults &Tainted, - const bool IsLazy, - const unsigned LoadKind, - const unsigned StoreKind, - VariableManager *Vars, - AccessOffsetMap &LoadOff, - AccessOffsetMap &StoreOff, - CallSiteOffsetMap &CallSiteLoadOff, - CallSiteOffsetMap &CallSiteStoreOff) : - Lazy(IsLazy), - LoadMDKind(LoadKind), - StoreMDKind(StoreKind), - M(Mod), - CPUStatePtr(EnvPtr), - RootFunction(Root), - ReachableFunctions(Reachable), - TaintedAccesses(Tainted), - Variables(Vars), - LoadOffsets(LoadOff), - StoreOffsets(StoreOff), - CallSiteLoadOffsets(CallSiteLoadOff), - CallSiteStoreOffsets(CallSiteStoreOff), - ValueCallSiteOffsets(), - LoadCallSiteOffsets(), - StoreCallSiteOffsets(), - CrossedCallSites(), - WorkList(), - InExploration(), - AddSubFolder(M), - NumericFolder(M), - GEPFolder(M), - CpuLoop(getIRHelper("cpu_loop", M)) {} - -public: - bool run(); - -private: - void cleanup() { - ValueCallSiteOffsets = {}; - LoadCallSiteOffsets = {}; - StoreCallSiteOffsets = {}; - CrossedCallSites = {}; - WorkList = {}; - InExploration = {}; - } - - /// Analyzes the access to env performed by \p I, saving results according to - /// \p IsLoad - /// - /// \param I is the `Instruction` whose accesses are analyzed - /// \param IsLoad must be true if called when analyzing loads, false if called - /// when analyzing stores. This is important because it is used to - /// update the correct ValueCallSiteOffsetMap (either - /// LoadCallSiteOffsets or StoreCallSiteOffsets) if during - /// the exploration the analysis ends because all the immediate sources - /// are already resolved. - void analyzeAccess(Instruction *I, bool IsLoad); - - /// Explores the sources of `V` and pushes a `WorkItem` on `WorkList` - /// if something new is found - /// - /// \param V is the `Value` whose sources are analyzed - /// \param IsLoad must be true if called when propagating loads, false if - /// called when propagating stores. This is important because it is used to - /// update the correct ValueCallSiteOffsetMap (either LoadCallSiteOffsets or - /// StoreCallSiteOffsets) if during the exploration the analysis ends because - /// all the immediate sources are already resolved. - bool exploreImmediateSources(Value *V, bool IsLoad); - - /// Returns an emptyoOptional and fill W if there are unexplored sources, - /// otherwise return the offsets - /// - /// \param V the `Value` whose sources must be explored. - /// \param [out] W a `WorkItem` that will be initialized with the unexplored - /// sources of `V` if any. - /// \param IsLoad true if we're exploring from a load - OptCSVOffsets - getOffsetsOrExploreSrc(Value *V, WorkItem &W, bool IsLoad) const; - - void insertCallSiteOffset(Value *V, CSVOffsets &&Offset); - - /// Removes the root call site associated with `Item` (if any) from - /// `CrossedCallSites` - /// - /// \return `true` if it was removed, `false` otherwise - bool tryRemoveCurCrossedCallSite(const WorkItem &Item) { - if (CallInst *RootCallSite = getCurSourceRootCall(Item, RootFunction)) - return CrossedCallSites.erase(RootCallSite); - return false; - } - - /// Returns true if `V` is visited for the first time with the callsite - /// `NewCallSite` - /// - /// \param V is the `Value` that is being visited - /// \param NewCallSite is the new call site from which we're exploring V and - /// we want to check if it's the first time we visit V with that - /// particular call site - /// - /// This function returns `true` if this is the first visit, `false` otherwise - bool isNewVisitWithCallSite(Value *V, CallInst *NewCallSite) const { - - if (CSVAccessLog.isEnabled()) { - if (NewCallSite != nullptr) { - llvm::StringRef NameRef = NewCallSite->getFunction()->getName(); - revng_log(CSVAccessLog, "caller: " << NameRef); - } else { - revng_log(CSVAccessLog, "caller: nullptr"); - } - } - - // Handle constants in a special way. Constants are kind of global values - // that can be used across different functions without properly propagating - // on the call graph across call sites. - // This has two consequences. - // 1) On the one hand we don't want to track their exact call site, because - // they can propagate independently of the call sites. Hence all constants - // are collected with an 'artificial' nullptr callsite (see the else). - // 2) On the other hand, when we cross root call sites and we find that one - // of the arguments is a constant, we want to mark it every time as a new - // visit. If we don't mark it as a new visit, the root call site will not be - // crossed, hence it will not be inserted in the `CrossedCallSites`, which - // is not what we want because it would lead to errors in computing the root - // call sites that are active for a given exploration. For this reason, if V - // is a constant and (NewCallSite != nullptr) we always say it's a new - // visit, because it means we're crossing a root call site towards a - // constant argument. - if (isa(V)) { - if (NewCallSite != nullptr) - return true; - else - NewCallSite = nullptr; - } - - const auto CallSiteOffsetIt = ValueCallSiteOffsets.find(V); - // If the ValueCallSiteOffsets does not contain V it's a new visit - if (CallSiteOffsetIt == ValueCallSiteOffsets.end()) - return true; - - // If the ValueCallSiteOffsets contains V we have already analyzed visited - // this value, but we don't know which call sites were contained in - // CrossedCallSites during the last visit. - const CallSiteOffsetMap &ACSOMap = CallSiteOffsetIt->second; - - // If NewCallSite is not nullptr, we are crossing a new callsite in the root - // function, so we start looking in the ValueCallSiteOffsets for an entry - // associated to NewCallSite - if (NewCallSite) { - // If we find that ValueCallSiteOffsets still does not contain an entry - // associated to NewCallSite this visit is considered new - if (!ACSOMap.contains(NewCallSite)) - return true; - } - - // If CrossedCallSites is not empty, we have crossed at least one call site - // in the root function, so we need to look in CrossedCallSites if there is - // a new call site to analyze - if (not CrossedCallSites.empty()) { - for (CallInst *Call : CrossedCallSites) { - // If we find a call site in CrossedCallSites for which the - // ValueCallSiteOffsets - // still does not contain a result for this value this visit is - // considered new - if (!ACSOMap.contains(Call)) - return true; - } - } else { - // If CrossedCallSites is empty, we haven't crossed any call site in the - // root function, so we look for nullptr. - if (!ACSOMap.contains(nullptr)) - return true; - } - - // If we reach this point the ValueCallSiteOffsets already contains an entry - // for V, and all the current CrossedCallSites have already been computed - // for that entry, so the visit is not new - return false; - } - - /// If it's a new visit, insert the call `RootCall` (associated with `U`) - /// in `CrossedCallSites` - /// - /// \param RootCall must be nullptr or a valid call instruction in root - /// \param U if RootCall is not `nullptr` this is a `Use` whose `User` must be - /// RootCall - /// \return `true` if it was a new visit, `false` otherwise. - /// - /// If this is the first visit, `RootCall` is inserted in `CrossedCallSites` - /// and the function returns `true`. - /// If this is not the first visit or `RootCall` it returns `false` - bool checkNewVisitAndInsertCrossedCallSite(CallInst *RootCall, const Use *U) { - revng_log(CSVAccessLog, "isNewVisitWithCallSite?"); - if (isNewVisitWithCallSite(U->get(), RootCall)) { - if (RootCall) { - revng_log(CSVAccessLog, "is RootCall"); - bool New = CrossedCallSites.insert(RootCall).second; - revng_assert(New); - } - revng_log(CSVAccessLog, "isNewVisitWithCallSite true"); - return true; - } - revng_log(CSVAccessLog, "isNewVisitWithCallSite false"); - return false; - } - - bool checkNewVisitAndInsertCurCrossedCallSite(const WorkItem &Item) { - CallInst *RootCallSite = getCurSourceRootCall(Item, RootFunction); - return checkNewVisitAndInsertCrossedCallSite(RootCallSite, - Item.currentSourceUse()); - } - - bool checkNewVisitAndInsertNextCrossedCallSite(const WorkItem &Item) { - CallInst *RootCallSite = getNextSourceRootCall(Item, RootFunction); - return checkNewVisitAndInsertCrossedCallSite(RootCallSite, - Item.nextSourceUse()); - } - - bool tryPush(WorkItem &&N) { - // If we're visiting the sources of an argument we are crossing a - // new call site, which might lead us into the root function. - // If it does, we want to register it in the CrossedCallSites - if (checkNewVisitAndInsertCurCrossedCallSite(N)) { - revng_log(CSVAccessLog, "Found!"); - push(std::move(N)); - return true; - } - revng_log(CSVAccessLog, "NOT Found!"); - return false; - } - - /// Selects the next source of `Item`, if possible, returning true on success. - bool selectNextSource(WorkItem &Item) { - if (Item.nextSourceUse()) { - tryRemoveCurCrossedCallSite(Item); - checkNewVisitAndInsertNextCrossedCallSite(Item); - auto NumSrcs = Item.getNumSources(); - auto NextSrcId = Item.getSourceIndex() + 1; - revng_assert(NextSrcId < NumSrcs); - Item.setSourceIndex(NextSrcId); - return true; - } - return false; - } - - void push(WorkItem &&Item) { - InExploration.insert(Item.val()); - WorkList.push_back(Item); - CSVAccessLog.indent(2); - } - - void pop() { - InExploration.erase(WorkList.back().val()); - WorkList.pop_back(); - CSVAccessLog.unindent(2); - } - - bool isInExploration(const Value *V) const { - return InExploration.contains(V); - } - - void computeOffsetsFromSources(const WorkItem &Item, bool IsLoad); - - template - void computeAggregatedOffsets(); -}; - -using CPUSAOA = CPUStateAccessOffsetAnalysis; - -void CPUSAOA::computeOffsetsFromSources(const WorkItem &Item, bool IsLoad) { - Value *ItemVal = Item.val(); - if (isa(ItemVal) or isa(ItemVal) - or isa(ItemVal)) { - - // These three cases represent points of convergence of information coming - // from different origins. - // - // For `PHINode` the information comes from the different branches of the - // phi. As an example the 'then' branch of an 'if' could compute an offsets, - // whereas the 'else' branch could compute a different offset. - // - // For `Argument` the different sources of information are all the actual - // arguments of all the calls to the function that are associated to the - // formal `Argument` that we're analyzing. An example is that if the - // function `int a(int b, int c)` if called in two places, such as `f(1,2)` - // and `f(3,4)`, we have that 1 and 3 are the sources of the argument `b`. - // - // For `CallInst` we are considering the propagation of values from the - // `return` instructions inside the callee to the call site. - // For example if we have: - // ``` - // int a(int b) { - // if (b) { - // int ret1 = b >> 2; - // return ret1; - // } else { - // int ret2 = b << 2; - // return ret2; - // } - // } - // ``` - // and `a` is called such as: - // ``` - // y = f(x); - // ... - // ``` - // the sources of the `CallInst` are `ret1` and `ret2`. - - revng_log(CSVAccessLog, "POP JOIN"); - - SmallVector SrcCallSiteOffsets; - SrcCallSiteOffsets.reserve(Item.getNumSources()); - std::map> CallSiteSrcIds; - - // This loop fills `SrcCallSiteOffsets` so that its n-th element will point - // to the `CallSiteOffsetMap` associated with the n-th source of the Value - // that we're considering. - WorkItem::size_type SI = 0; - for (const Use *Src : Item.sources()) { - Value *SrcVal = Src->get(); - revng_log(CSVAccessLog, - "SrcVal: " << SrcVal << " : " << dumpToString(SrcVal)); - const CallSiteOffsetMap &CallSiteOffset = ValueCallSiteOffsets.at(SrcVal); - - // The `CallSiteOffsetMap` associated with `SrcVal` is pushed back into - // `SrcCallSiteOffsets`, into position `SI`. - SrcCallSiteOffsets.push_back(&CallSiteOffset); - - // Then, this loop inserts the source index `SI` into the set of source - // indices associated with the call site `C.first` - for (const auto &C : CallSiteOffset) - CallSiteSrcIds[C.first].insert(SI); - ++SI; - } - - // Here `SrcCallSiteOffsets[i]` contains a pointer the the - // `CallSiteOffsetMap` associated with the source number `i` of the analyzed - // `Value`. - // Here, for a given call site `C` in `root` the value - // `CallSiteSrcIds.at(C)` is the set of source indices for which the value - // currently analyzed is reached from the call site `C`. - - auto Call = dyn_cast(ItemVal); - const Function *Callee = Call ? getCallee(Call) : nullptr; - if (Callee != nullptr and Callee->getIntrinsicID() == Intrinsic::memcpy) { - - // Separately handle Intrinsic::memcpy; - // Calls to Intrinsic::memcpy are a special case. - // Given that they don't generate any Value, they cannot be pushed on the - // worklist as a consequence of the backward exploration towards sources. - // For this reason, if we reach a point where we're trying to compute the - // offsets from the sources of a call to Intrinsic::memcpy it must be the - // memcpy where we started from and we don't really want to propagate from - // the return to the call site, but we want to compute the result and - // store it in the proper map. - - revng_log(CSVAccessLog, - "MAP Intrinsic::memcpy: " << ItemVal << " : " - << dumpToString(ItemVal)); - - revng_assert(isa(Call->getArgOperand(2))); - ValueCallSiteOffsetMap &VCSOffsets = IsLoad ? LoadCallSiteOffsets : - StoreCallSiteOffsets; - Value *PtrOp = IsLoad ? Call->getArgOperand(1) : Call->getArgOperand(0); - auto CSOff = std::make_pair(ItemVal, ValueCallSiteOffsets.at(PtrOp)); - bool New = VCSOffsets.insert(CSOff).second; - revng_assert(New); - - } else { - - // This loop iterates over each call instruction in `CallSiteSrcIds`, - // and, for all the source indices associated to that call site, it - // combines all of the `CSVOffsets` to compute the new `CSVOffsets` of the - // node that we're popping. - for (const auto &CallSrc : CallSiteSrcIds) { - std::optional New; - CallInst *TheCall = CallSrc.first; - for (const auto I : CallSrc.second) { - revng_log(CSVAccessLog, - "AT: " << TheCall << " : " << dumpToString(TheCall)); - const CSVOffsets &SrcOffset = SrcCallSiteOffsets[I]->at(TheCall); - if (New) { - New->combine(SrcOffset); - } else { - revng_log(CSVAccessLog, "NEW"); - New = SrcOffset; - } - revng_log(CSVAccessLog, "SrcOffsets : " << SrcOffset); - revng_log(CSVAccessLog, "New Offsets: " << *New); - } - revng_log(CSVAccessLog, - "MAP JOIN: " << ItemVal << " : " << dumpToString(ItemVal) - << "\n " << *New); - - // Insert the `New` in the `ValueCallSiteOffsets` - ValueCallSiteOffsets[ItemVal][TheCall] = std::move(*New); - revng_log(CSVAccessLog, - "CallSite: " << TheCall << " : " << dumpToString(TheCall) - << "\n " - << ValueCallSiteOffsets.at(ItemVal).at(TheCall)); - } - } - - tryRemoveCurCrossedCallSite(Item); - - } else if (auto *Instr = dyn_cast(ItemVal)) { - - revng_log(CSVAccessLog, "POP INST"); - - const auto OpCode = Instr->getOpcode(); - switch (OpCode) { - case Instruction::ZExt: - case Instruction::SExt: - case Instruction::Trunc: - case Instruction::PtrToInt: - case Instruction::IntToPtr: - case Instruction::BitCast: { - revng_assert(Item.getNumSources() == Instr->getNumOperands()); - revng_log(CSVAccessLog, - "MAP CAST: " << ItemVal << " : " << dumpToString(ItemVal)); - Value *Op = Instr->getOperand(0); - ValueCallSiteOffsets[ItemVal] = ValueCallSiteOffsets.at(Op); - } break; - case Instruction::Sub: - case Instruction::Add: { - revng_assert(Item.getNumSources() == Instr->getNumOperands()); - revng_log(CSVAccessLog, "Add/Sub"); - AddSubFolder.fold(Item, ValueCallSiteOffsets); - } break; - case Instruction::Shl: - case Instruction::AShr: - case Instruction::LShr: - case Instruction::Mul: - case Instruction::URem: - case Instruction::SRem: - case Instruction::SDiv: - case Instruction::UDiv: { - revng_assert(Item.getNumSources() == Instr->getNumOperands()); - revng_log(CSVAccessLog, "NumericFold"); - NumericFolder.fold(Item, ValueCallSiteOffsets); - } break; - case Instruction::GetElementPtr: { - revng_assert(Item.getNumSources() == Instr->getNumOperands()); - revng_log(CSVAccessLog, "GEP"); - GEPFolder.fold(Item, ValueCallSiteOffsets); - } break; - case Instruction::Load: { - revng_assert(Item.getNumSources() == Instr->getNumOperands()); - Value *AddressValue = cast(Instr)->getPointerOperand(); - auto LoadCSOff = std::make_pair(ItemVal, - ValueCallSiteOffsets.at(AddressValue)); - bool New = LoadCallSiteOffsets.insert(LoadCSOff).second; - if (CSVAccessLog.isEnabled()) { - revng_log(CSVAccessLog, "Load " << dumpToString(Instr)); - for (const auto &CS2O : LoadCSOff.second) { - revng_log(CSVAccessLog, "CallSite: "); - if (CS2O.first) - revng_log(CSVAccessLog, dumpToString(CS2O.first)); - else - revng_log(CSVAccessLog, "nullptr"); - revng_log(CSVAccessLog, CS2O.second); - } - } - revng_assert(New); - } break; - case Instruction::Store: { - revng_assert(Item.getNumSources() == 1); - Value *AddressValue = cast(Instr)->getPointerOperand(); - auto StoreCSOff = std::make_pair(ItemVal, - ValueCallSiteOffsets.at(AddressValue)); - bool New = StoreCallSiteOffsets.insert(StoreCSOff).second; - if (CSVAccessLog.isEnabled()) { - revng_log(CSVAccessLog, "Store " << dumpToString(Instr)); - for (const auto &CS2O : StoreCSOff.second) { - revng_log(CSVAccessLog, "CallSite: "); - if (CS2O.first) - revng_log(CSVAccessLog, dumpToString(CS2O.first)); - else - revng_log(CSVAccessLog, "nullptr"); - revng_log(CSVAccessLog, CS2O.second); - } - } - revng_assert(New); - } break; - default: - revng_abort(dumpToString(Instr).data()); - } - } else { - revng_abort(); - } -} - -void CPUSAOA::insertCallSiteOffset(Value *V, CSVOffsets &&Offset) { - revng_log(CSVAccessLog, "MAP INSERT: " << V); - // If CrossedCallSites is empty we haven't reached the root function during - // the backward exploration, so the only active call site is nullptr. - // The same holds if we're inserting the offset for a ConstantInt. The problem - // with constants is that they are not propagated through the call graph, but - // they are in a global context and they infect all the places where they're - // used independently of the call sites crossed during the exploration. For - // this reason, if we accumulate all the call sites for the constants we may - // end up in situation where on a given constant we have lots of call sites in - // the map, but all with the same CSVOffsets. This is bad for two reasons: - // 1) it increases the size of the map for no reason; - // 2) it potentially propagates wrong call sites where the constants are used. - // For this reason we used this workaround, to only insert null call sites for - // constants. - if (CrossedCallSites.empty() or isa(V)) { - ValueCallSiteOffsets[V][nullptr] = Offset; - revng_log(CSVAccessLog, "CallSite: nullptr\n " << Offset); - revng_assert(not isa(V) or Offset.isNumeric()); - } else { - // In all the other cases use the active set of crossed call sites - for (const auto &Call : CrossedCallSites) { - ValueCallSiteOffsets[V][Call] = Offset; - revng_log(CSVAccessLog, "CallSite: " << Call << "\n " << Offset); - } - } -} - -OptCSVOffsets -CPUSAOA::getOffsetsOrExploreSrc(Value *V, WorkItem &Item, bool IsLoad) const { - if (auto *Call = dyn_cast(V)) { - revng_log(CSVAccessLog, "CALL: " << dumpToString(Call)); - Item = WorkItem(Call, IsLoad, Lazy, LoadMDKind, StoreMDKind); - } else if (auto *Arg = dyn_cast(V)) { - revng_log(CSVAccessLog, "ARG: " << dumpToString(Arg)); - Item = WorkItem(Arg, ReachableFunctions, Lazy, LoadMDKind, StoreMDKind); - } else if (auto *Instr = dyn_cast(V)) { - revng_log(CSVAccessLog, "INST: " << dumpToString(Instr)); - const auto OpCode = Instr->getOpcode(); - switch (OpCode) { - case Instruction::Load: { - revng_log(CSVAccessLog, "LOAD"); - const auto *Load = cast(Instr); - const Value *Ptr = Load->getPointerOperand()->stripPointerCasts(); - if (const auto *CSV = dyn_cast(Ptr)) { - revng_log(CSVAccessLog, "GLOBAL"); - if (CSV == CPUStatePtr) { - revng_log(CSVAccessLog, "ENV"); - return CSVOffsets(CSVOffsets::Kind::KnownInPtr, 0); - } else { - revng_log(CSVAccessLog, "NOT-ENV"); - return CSVOffsets(CSVOffsets::Kind::Unknown); - } - } else { - revng_log(CSVAccessLog, "NOT-GLOBAL"); - return CSVOffsets(CSVOffsets::Kind::Unknown); - } - } - case Instruction::Alloca: - revng_log(CSVAccessLog, "ALLOCA"); - return CSVOffsets(CSVOffsets::Kind::Unknown); - case Instruction::Or: - case Instruction::And: - case Instruction::ICmp: - revng_log(CSVAccessLog, "CMP"); - return CSVOffsets(CSVOffsets::Kind::Unknown); - case Instruction::Store: - revng_abort(); - default: - break; - } - // If we reach this point the CSVOffsets of this instruction are not known - Item = WorkItem(Instr); - } else if (const auto *IntConst = dyn_cast(V)) { - int64_t Offset = IntConst->getSExtValue(); - revng_log(CSVAccessLog, "CONST: " << Offset); - return CSVOffsets(CSVOffsets::Kind::Numeric, Offset); - } else { - revng_abort(); - } - return OptCSVOffsets(); -} - -bool CPUSAOA::exploreImmediateSources(Value *V, bool IsLoad) { - // Try to get new unexplored sources for V. - WorkItem NewItem; - { - auto ConstKnownOffsets = getOffsetsOrExploreSrc(V, NewItem, IsLoad); - if (ConstKnownOffsets.has_value()) { - revng_log(CSVAccessLog, "ConstantOffset"); - - // If we reach this point, V only has a constant know CSVOffsets and does - // not really have sources that must be explored. In this case we can just - // insert the ConstKnownOffsets in the map and we're done. - insertCallSiteOffset(V, std::move(*ConstKnownOffsets)); - return false; - } - } - revng_log(CSVAccessLog, "New!: " << NewItem); - - Value *NewItemV = NewItem.val(); - if (isInExploration(NewItemV)) { - revng_log(CSVAccessLog, "IS RECURSIVE"); - - const ConstValuePtrSet &Tainted = TaintedAccesses.TaintedValues; - - if (const Argument *Arg = dyn_cast(NewItemV)) { - revng_assert(Tainted.contains(Arg)); - revng_assert(Arg->getArgNo() == 0); - const Function *Fun = Arg->getParent(); - revng_assert(CpuLoop != nullptr); - revng_assert(CpuLoop->arg_size()); - revng_assert(Arg->getType() == CpuLoop->arg_begin()->getType()); - - auto WLIt = WorkList.cbegin(); - auto WLEnd = WorkList.cend(); - bool FoundRecursion = false; - for (; WLIt != WLEnd; ++WLIt) { - if (Arg != WLIt->val()) - continue; - - FoundRecursion = true; - revng_log(CSVAccessLog, "Close recursion"); - - Value *CurSrcVal = WLIt->currentSourceValue(); - revng_assert(CurSrcVal != Arg); - CSVOffsets NewOffsets = CSVOffsets(CSVOffsets::Kind::KnownInPtr, 0); - insertCallSiteOffset(CurSrcVal, std::move(NewOffsets)); - - Value *NextSrcVal = WLIt->nextSourceValue(); - if (nullptr == NextSrcVal) - break; - - revng_log(CSVAccessLog, - "Has unresolved source: " << dumpToString(NextSrcVal)); - revng_assert(NextSrcVal != Arg); - - WorkItem::size_type NextSrcId = WLIt->getSourceIndex() + 1; - NewItem.setSourceIndex(NextSrcId); - if (tryPush(std::move(NewItem))) - return true; - } - revng_assert(FoundRecursion); - } else { - revng_assert(!Tainted.contains(NewItemV)); - for (const Use *U : NewItem.sources()) - if (not isa(U->get())) - insertCallSiteOffset(U->get(), CSVOffsets(CSVOffsets::Kind::Unknown)); - } - - } else { - - for (const auto &Group : llvm::enumerate(NewItem.sources())) { - revng_log(CSVAccessLog, "Src: " << dumpToString(Group.value()->get())); - revng_log(CSVAccessLog, "SrcId: " << Group.index()); - // Adjust the SourceIndex, to set the correct Source - auto SrcId = Group.index(); - NewItem.setSourceIndex(SrcId); - if (tryPush(std::move(NewItem))) - return true; - } - } - - // If we reach this point the current Value V only has sources that were - // already explored. - if (NewItem.getNumSources()) { - revng_log(CSVAccessLog, "DONE"); - computeOffsetsFromSources(NewItem, IsLoad); - } - return false; -} - -static bool callsBuiltinMemcpy(const Instruction *TheCall) { - const Function *Callee = getCallee(TheCall); - return Callee != nullptr and Callee->getIntrinsicID() == Intrinsic::memcpy; -} - -void CPUSAOA::analyzeAccess(Instruction *LoadOrStore, bool IsLoad) { - - // This analysis starts from the Instruction LoadOrStore and works in two - // alternate steps: - // 1) it iterates backward, looking for all the values that generate their - // pointer operands; - // 2) as soon as the backward exploration reaches the leaves (i.e. the initial - // values that are used as building blocks for the computation) it starts - // working forward. At each step of the forward propagation it tries to - // constant-fold the sources of the current value. If all the offsets of all - // the sources are known they are constant folded and the analysis keeps - // working forward. Otherwise, if there is a source that it still unexplored - // (i.e. it has no known offsets), the analysis starts working backward again, - // until also the unexplored source is resolved and can be constant folded. - - // Initialization - if (not callsBuiltinMemcpy(LoadOrStore)) - push(WorkItem(LoadOrStore)); - else - push(WorkItem(cast(LoadOrStore), - IsLoad, - Lazy, - LoadMDKind, - StoreMDKind)); - - while (not WorkList.empty()) { - const auto Size = WorkList.size(); - Value *CurSrcVal = WorkList.back().currentSourceValue(); - if (CSVAccessLog.isEnabled()) { - const auto *CurVal = WorkList.back().val(); - revng_log(CSVAccessLog, - "Val : " << CurVal << " : " << dumpToString(CurVal)); - revng_log(CSVAccessLog, - "Src : " << CurSrcVal << " : " << dumpToString(CurSrcVal)); - } - - // Explore CurSrcVal's immediate sources (going backward) - // If we the exploration succeeded we have something new on the WorkList and - // we want to keep exploring back - if (exploreImmediateSources(CurSrcVal, IsLoad)) - continue; - revng_log(CSVAccessLog, "not grown"); - - // If we didn't push anything, we are done exploring backward the current - // source and we want to explore backward the other sources of this value - if (Size == WorkList.size()) - if (selectNextSource(WorkList.back())) - continue; - - revng_log(CSVAccessLog, "Done"); - - // If we reach this point we have finished exploring all the sources of - // the item that is currently on top of the WorkList. - // The backward propagation is complete for now. - // We want to fold the results of the sources on the Item, store the - // result in the OffsetMap, and then pop the Item - do { - const WorkItem &Item = WorkList.back(); - if (CSVAccessLog.isEnabled()) { - const auto *Val = Item.val(); - revng_log(CSVAccessLog, - "TopItemVal: " << Val << " : " << dumpToString(Val)); - const auto *SrcVal = Item.currentSourceValue(); - revng_log(CSVAccessLog, - "CurSrc : " << SrcVal << " : " << dumpToString(SrcVal)); - } - - // Constant fold the finished value and pop it. - computeOffsetsFromSources(Item, IsLoad); - pop(); - } while (not WorkList.empty() - and WorkList.back().nextSourceValue() == nullptr); - - if (not WorkList.empty()) - selectNextSource(WorkList.back()); - } -} - -template -void CPUSAOA::computeAggregatedOffsets() { - const InstrPtrSet &Tainted = IsLoad ? TaintedAccesses.TaintedLoads : - TaintedAccesses.TaintedStores; - ValueCallSiteOffsetMap &AccessCSOffsets = IsLoad ? LoadCallSiteOffsets : - StoreCallSiteOffsets; - CallSiteOffsetMap &CallSiteOffsets = IsLoad ? CallSiteLoadOffsets : - CallSiteStoreOffsets; - AccessOffsetMap &AccessOffsets = IsLoad ? LoadOffsets : StoreOffsets; - - for (std::pair &ACSO : AccessCSOffsets) { - // This is the load/store that actually accesses the CPU State - Value *I = ACSO.first; - - auto DL = M.getDataLayout(); - - bool IsInstr = isa(I); - bool IsCorrectAccessType = IsLoad ? isa(I) : isa(I); - bool IsCallToBuiltinMemcpy = callsBuiltinMemcpy(dyn_cast(I)); - revng_assert(IsInstr and (IsCorrectAccessType or IsCallToBuiltinMemcpy)); - auto *Instr = dyn_cast(I); - revng_assert(Tainted.contains(Instr)); - - int64_t AccessSize; - if (IsCallToBuiltinMemcpy) { - auto Call = cast(I); - auto SizeParam = cast(Call->getArgOperand(2)); - AccessSize = SizeParam->getSExtValue(); - } else if (IsLoad) { - auto *Load = cast(Instr); - AccessSize = DL.getTypeAllocSize(Load->getType()); - } else { - auto Store = cast(Instr); - AccessSize = DL.getTypeAllocSize(Store->getValueOperand()->getType()); - } - revng_assert(AccessSize != 0); - - CallSiteOffsetMap &CallSiteMap = ACSO.second; - for (std::pair &CSO : CallSiteMap) { - CallInst *const Call = CSO.first; - CSVOffsets &O = CSO.second; - revng_assert(O.isPtr()); - - bool Inserted; - - // Compute AccessOffsets, i.e. the set of offsets accessed by each access - { - AccessOffsetMap::iterator AccessOffsetIt; - auto Offset = std::make_pair(Instr, O); - std::tie(AccessOffsetIt, Inserted) = AccessOffsets.insert(Offset); - if (not Inserted) - AccessOffsetIt->second.combine(O); - } - - // Compute CallSiteOffsets, i.e. the set of offsets that might be accessed - // from a given call in root. - // This is a little more tricky than computing AccessOffsets, since here, - // when we collapse on the call site, we lose the information on the - // specific instruction that caused a given offset to be computed, hence - // also losing the size of the access. For this reason here we have to - // take into account the sizes of all the accesses. - { - OptCSVOffsets New; - if (not O.hasOffsetSet()) { - New = O; - } else { - revng_assert(O.size()); - std::set FineGrainedOffsets; - // Now compute the fine-grained offsets - for (const int64_t Coarse : O) { - int64_t Refined = Coarse; - int64_t End = Coarse + AccessSize; - while (Refined < End) { - unsigned InternalOffset = 0; - GlobalVariable *AccessedVar = nullptr; - std::tie(AccessedVar, - InternalOffset) = Variables->getByEnvOffset(Refined); - int64_t SizeAtOffset = 0; - if (AccessedVar != nullptr) { - Type *AccessedTy = AccessedVar->getValueType(); - SizeAtOffset = DL.getTypeAllocSize(AccessedTy) - InternalOffset; - revng_assert(SizeAtOffset > 0); - FineGrainedOffsets.insert(Refined - InternalOffset); - CSVAccessLog << "Value: " << I << DoLog; - CSVAccessLog << "Insert Refined: " << Refined << DoLog; - } else { - // Skip padding one byte at a time, without adding offsets - SizeAtOffset = 1; - } - revng_assert(SizeAtOffset != 0); - Refined += SizeAtOffset; - } - } - New = CSVOffsets(O.getKind(), FineGrainedOffsets); - } - // Finally insert them or combine them - CallSiteOffsetMap::iterator CallOffsetIt; - std::tie(CallOffsetIt, - Inserted) = CallSiteOffsets.insert({ Call, *New }); - if (not Inserted) - CallOffsetIt->second.combine(*New); - } - } - } -} - -bool CPUSAOA::run() { - - // Analyze load and store - for (Instruction *I : TaintedAccesses.TaintedLoads) - analyzeAccess(I, true); - for (Instruction *I : TaintedAccesses.TaintedStores) - analyzeAccess(I, false); - - if (CSVAccessLog.isEnabled()) { - TaintLog << "== ACCESS ANALYSIS RESULTS ==\n"; - TaintLog << "== Loads ==\n"; - for (Instruction *LoadOrStore : TaintedAccesses.TaintedLoads) { - TaintLog << "INSTRUCTION: " << LoadOrStore << DoLog; - TaintLog << dumpToString(LoadOrStore) << DoLog; - TaintLog.indent(4); - for (const auto &CSO : LoadCallSiteOffsets.at(LoadOrStore)) { - TaintLog << "CallSite: " << CSO.first << '\n'; - if (CSO.first != nullptr) - TaintLog << dumpToString(CSO.first); - TaintLog << DoLog; - TaintLog << CSO.second << '\n'; - } - TaintLog.unindent(4); - TaintLog << DoLog; - } - TaintLog << "== Stores ==\n"; - for (Instruction *LoadOrStore : TaintedAccesses.TaintedStores) { - TaintLog << "INSTRUCTION: " << LoadOrStore << DoLog; - TaintLog << dumpToString(LoadOrStore) << DoLog; - TaintLog.indent(4); - for (const auto &CSO : StoreCallSiteOffsets.at(LoadOrStore)) { - TaintLog << "CallSite: " << CSO.first << '\n'; - if (CSO.first != nullptr) - TaintLog << dumpToString(CSO.first); - TaintLog << DoLog; - TaintLog << CSO.second << '\n'; - } - TaintLog.unindent(4); - TaintLog << DoLog; - } - TaintLog << DoLog; - } - - // ValueCallSiteOffset is not needed anymore here, because it's only used - // across different calls to analyzeAccess to optimize the runtime avoiding - // multiple iterations on the same Values. - ValueCallSiteOffsets = {}; - - // Aggregate the results: - // - from LoadCallSiteOffsets to CallSiteLoadOffsets and LoadOffsets - // - from StoreCallSiteOffsets to CallSiteStoreOffsets and StoreOffsets - computeAggregatedOffsets(); - computeAggregatedOffsets(); - - cleanup(); - - return not TaintedAccesses.empty(); -} - -class CPUStateAccessFixer { - -private: - using Pair = AccessOffsetMap::value_type; - -private: - // A reference to the analyzed Module - const Module &M; - - VariableManager *Variables = nullptr; - - // References to the maps that were filled by CPUStateAccessAnalysis. - // Every map maps an Instruction to the CSVOffset representing all the - // possible offsets that are accessed by that Instruction, being it either a - // load, a store or a call to Intrinsic::memcpy. - // We hold two separate maps, one for loads and one for stores, so that calls - // to memcpy can be in both maps, with different associated offsets. - const AccessOffsetMap &CSVLoadOffsetMap; - const AccessOffsetMap &CSVStoreOffsetMap; - - std::vector InstructionsToRemove; - -public: - CPUStateAccessFixer(const Module &Mod, - VariableManager *V, - const AccessOffsetMap &L, - const AccessOffsetMap &S) : - M(Mod), - Variables(V), - CSVLoadOffsetMap(L), - CSVStoreOffsetMap(S), - DL(Mod.getDataLayout()), - EnvStructSize(DL.getTypeAllocSize(V->getCPUStateType())), - Builder(Mod.getContext()), - Int64Ty(IntegerType::getInt64Ty(M.getContext())), - SizeOfEnv(ConstantInt::get(Int64Ty, APInt(64, EnvStructSize, true))), - Zero(ConstantInt::get(Int64Ty, APInt(64, 0, true))), - CPUStatePtr(Mod.getGlobalVariable("env")) {} - -public: - bool run(); - -private: - template - std::tuple - setupOutEnvAccess(Instruction *AccessToFix); - - template - void correctCPUStateAccesses(); - - void setupLoadInEnv(Instruction *LoadToFix, - int64_t EnvOffset, - SwitchInst *Switch, - BasicBlock *NextBB, - PHINode *Phi); - - void setupStoreInEnv(Instruction *LoadToFix, - int64_t EnvOffset, - SwitchInst *Switch, - BasicBlock *NextBB); - - template - void fixAccess(const Pair &IOff); - -private: - const DataLayout &DL; - int64_t EnvStructSize; - revng::NonDebugInfoCheckingIRBuilder Builder; - Type *Int64Ty = nullptr; - Constant *SizeOfEnv = nullptr; - Constant *Zero = nullptr; - Value *CPUStatePtr = nullptr; -}; - -static Value *getLoadAddressValue(Instruction *I) { - auto OpCode = I->getOpcode(); - Value *Address = nullptr; - switch (OpCode) { - case Instruction::Load: { - Address = cast(I)->getPointerOperand(); - } break; - case Instruction::Call: { - auto *Call = cast(I); - Function *Callee = getCallee(Call); - // We only support memcpys where the last parameter is constant - revng_assert(Callee != nullptr - and (Callee->getIntrinsicID() == Intrinsic::memcpy - and isa(Call->getArgOperand(2)))); - Address = Call->getArgOperand(1); - } break; - default: - revng_abort(); - } - return Address; -} - -static Type *getLoadedType(Instruction *I) { - if (auto Load = dyn_cast(I)) { - return Load->getType(); - } - return nullptr; -} - -static Value *getStoreAddressValue(Instruction *I) { - auto OpCode = I->getOpcode(); - Value *Address = nullptr; - switch (OpCode) { - case Instruction::Store: { - Address = cast(I)->getPointerOperand(); - } break; - case Instruction::Call: { - auto *Call = cast(I); - Function *Callee = getCallee(Call); - // We only support memcpys where the last parameter is constant - revng_assert(Callee != nullptr - and (Callee->getIntrinsicID() == Intrinsic::memcpy - and isa(Call->getArgOperand(2)))); - Address = Call->getArgOperand(0); - } break; - default: - revng_abort(); - } - return Address; -} - -static Type *getStoredType(Instruction *I) { - if (auto *Store = dyn_cast(I)) { - return Store->getValueOperand()->getType(); - } - return nullptr; -} - -static ConstantInt *getConstantOffset(Type *Int64Ty, int64_t O) { - auto *EnvOffsetConst = ConstantInt::get(Int64Ty, APInt(64, O, true)); - return cast(EnvOffsetConst); -} - -template -std::tuple -CPUStateAccessFixer::setupOutEnvAccess(Instruction *AccessToFix) { - LLVMContext &Context = M.getContext(); - BasicBlock *AccessToFixBB = AccessToFix->getParent(); - Function *F = AccessToFixBB->getParent(); - auto InstrIt = AccessToFix->getIterator(); - revng_assert(InstrIt != AccessToFixBB->end()); - revng_assert(std::next(InstrIt) != AccessToFixBB->end()); - // Create a new block NextBB and move there all the instructions after - // the access - BasicBlock *NextBB = AccessToFixBB->splitBasicBlock(InstrIt); - AccessToFixBB->getTerminator()->eraseFromParent(); - revng_assert(not NextBB->empty()); - - // Create a new block OutAccessBB only for accesses outside env, and - // clone the accessing instruction in there. This clone of the - // accessing instruction in the OutAccessBB will be leaved untouched - // by the substitution performed later. - BasicBlock *OutAccessBB = BasicBlock::Create(Context, "OutAccess", F); - Builder.SetInsertPoint(OutAccessBB); - BranchInst *OutToNextBranchInst = Builder.CreateBr(NextBB); - Instruction *OutAccess = AccessToFix->clone(); - OutAccess->insertBefore(OutToNextBranchInst); - - // Create a new block InAccessBB only for accesses inside env (if any). - // The accessing instruction is cloned into the new InAccessBB - // and we insert a branch instruction to the NextBB. - BasicBlock *InAccessBB = BasicBlock::Create(Context, "InAccess", F); - Builder.SetInsertPoint(InAccessBB); - BranchInst *InToNextBranchInst = Builder.CreateBr(NextBB); - Instruction *InAccessToFix = AccessToFix->clone(); - InAccessToFix->insertBefore(InToNextBranchInst); - - // Create a conditional branch to jump to InAccessBB if the access is - // going to be in env, or to OutAccessBB if the access is going to be - // out of env - Value *Address = IsLoad ? getLoadAddressValue(AccessToFix) : - getStoreAddressValue(AccessToFix); - - Builder.SetInsertPoint(AccessToFixBB); - Value *OffsetValue = Builder.CreatePtrToInt(Address, Int64Ty); - Value *GEZero = Builder.CreateICmpSGE(OffsetValue, Zero); - Value *LTSizeOf = Builder.CreateICmpSLT(OffsetValue, SizeOfEnv); - Value *IsInCSV = Builder.CreateOr(GEZero, LTSizeOf); - Builder.CreateCondBr(IsInCSV, InAccessBB, OutAccessBB); - - if (IsLoad) { - Type *LoadedType = getLoadedType(AccessToFix); - if (LoadedType != nullptr) { - Builder.SetInsertPoint(&NextBB->front()); - PHINode *PN = Builder.CreatePHI(LoadedType, 2); - AccessToFix->replaceAllUsesWith(PN); - PN->addIncoming(OutAccess, OutAccessBB); - PN->addIncoming(InAccessToFix, InAccessBB); - } - return std::make_tuple(InAccessToFix, LoadedType, OffsetValue); - } - return std::make_tuple(InAccessToFix, nullptr, OffsetValue); -} - -void CPUStateAccessFixer::setupLoadInEnv(Instruction *LoadToFix, - int64_t EnvOffset, - SwitchInst *Switch, - BasicBlock *NextBB, - PHINode *Phi) { - LLVMContext &Context = M.getContext(); - Function *F = LoadToFix->getFunction(); - auto *OffsetConstInt = getConstantOffset(Int64Ty, EnvOffset); - BasicBlock *CaseBlock = BasicBlock::Create(Context, {}, F); - Builder.SetInsertPoint(CaseBlock); - BranchInst *Break = Builder.CreateBr(NextBB); - - Instruction *Clone = LoadToFix->clone(); - Clone->insertBefore(Break); - - bool Ok = false; - Builder.SetInsertPoint(Clone); - switch (LoadToFix->getOpcode()) { - - case Instruction::Load: { - Type *OriginalLoadedType = Clone->getType(); - unsigned Size = DL.getTypeAllocSize(OriginalLoadedType); - revng_assert(Size != 0); - auto *Loaded = Variables->loadFromEnvOffset(Builder, Size, EnvOffset); - Ok = Loaded != nullptr; - if (Ok) { - Type *LoadedType = Loaded->getType(); - if (LoadedType != OriginalLoadedType) { - unsigned LoadedSize = DL.getTypeAllocSize(LoadedType); - revng_assert(LoadedSize == Size); - Loaded = Builder.CreateIntToPtr(Loaded, OriginalLoadedType); - } - Switch->addCase(OffsetConstInt, CaseBlock); - // Add an incoming edge for the PHI after the switch if necessary. - if (Phi != nullptr) - Phi->addIncoming(Loaded, CaseBlock); - Clone->replaceAllUsesWith(Loaded); - InstructionsToRemove.push_back(Clone); - } else { - eraseFromParent(CaseBlock); - CaseBlock = nullptr; // Prevent this from being used - } - } break; - - case Instruction::Call: { - CallInst *Call = cast(Clone); - Ok = Variables->memcpyAtEnvOffset(Builder, Call, EnvOffset, true); - if (Ok) { - InstructionsToRemove.push_back(Clone); - Switch->addCase(OffsetConstInt, CaseBlock); - } else { - eraseFromParent(CaseBlock); - CaseBlock = nullptr; // Prevent this from being used - } - } break; - - default: - revng_abort(); - } -} - -void CPUStateAccessFixer::setupStoreInEnv(Instruction *StoreToFix, - int64_t EnvOffset, - SwitchInst *Switch, - BasicBlock *NextBB) { - LLVMContext &Context = M.getContext(); - Function *F = StoreToFix->getFunction(); - auto *OffsetConstInt = getConstantOffset(Int64Ty, EnvOffset); - BasicBlock *CaseBlock = BasicBlock::Create(Context, {}); - CaseBlock->insertInto(F); - Builder.SetInsertPoint(CaseBlock); - BranchInst *Break = Builder.CreateBr(NextBB); - - Instruction *Clone = StoreToFix->clone(); - Clone->insertBefore(Break); - - bool Ok = false; - Builder.SetInsertPoint(Clone); - switch (StoreToFix->getOpcode()) { - - case Instruction::Store: { - auto *Store = cast(Clone); - auto *V = Store->getValueOperand(); - unsigned Size = DL.getTypeAllocSize(V->getType()); - revng_assert(Size != 0); - Ok = Variables->storeToEnvOffset(Builder, Size, EnvOffset, V).has_value(); - } break; - - case Instruction::Call: { - CallInst *Call = cast(Clone); - Ok = Variables->memcpyAtEnvOffset(Builder, Call, EnvOffset, false); - } break; - - default: - revng_abort(); - } - - if (Ok) { - InstructionsToRemove.push_back(Clone); - Switch->addCase(OffsetConstInt, CaseBlock); - } else { - eraseFromParent(CaseBlock); - } -} - -template -void CPUStateAccessFixer::correctCPUStateAccesses() { - auto &CSVAccessOffsetMap = IsLoad ? CSVLoadOffsetMap : CSVStoreOffsetMap; - auto &OtherCSVAccessOffsetMap = IsLoad ? CSVStoreOffsetMap : CSVLoadOffsetMap; - - if (IsLoad) - FixAccessLog << "######## Fixing Loads ########" << DoLog; - else - FixAccessLog << "######## Fixing Stores ########" << DoLog; - - Type *CharTy = IntegerType::getInt8Ty(M.getContext()); - for (const Pair &IOff : CSVAccessOffsetMap) { - Instruction *const Instr = IOff.first; - auto It = OtherCSVAccessOffsetMap.find(Instr); - if (It != OtherCSVAccessOffsetMap.end()) { - revng_log(FixAccessLog, "Is memcpy"); - if (IsLoad) { - revng_log(FixAccessLog, "Must be fixed NOW!"); - // Decompose memcpy from env to env into two separate memcpy, the first - // to do the load, the second to do the store - auto *Call = cast(Instr); - Function *Memcpy = getCallee(Call); - revng_assert(Memcpy != nullptr - and (Memcpy->getIntrinsicID() == Intrinsic::memcpy - and isa(Call->getArgOperand(2)))); - - Value *MemcpySize = Call->getArgOperand(2); - Value *MemcpySrc = Call->getArgOperand(1); - Value *MemcpyDst = Call->getArgOperand(0); - - Function *F = Instr->getFunction(); - Builder.SetInsertPoint(&*F->getEntryBlock().begin()); - AllocaInst *TmpBuffer = Builder.CreateAlloca(CharTy, MemcpySize); - revng_log(FixAccessLog, - "Created ALLOCA: " << TmpBuffer << " : " - << dumpToString(TmpBuffer)); - - Builder.SetInsertPoint(Instr); - auto TmpAlign = MaybeAlign(TmpBuffer->getAlign()); - auto AlignOne = MaybeAlign(1); - CallInst *MemcpyLoad = Builder.CreateMemCpy(TmpBuffer, - TmpAlign, - MemcpySrc, - AlignOne, - MemcpySize); - revng_log(FixAccessLog, - "Created LOAD: " << MemcpyLoad << " : " - << dumpToString(MemcpyLoad)); - - CallInst *MemcpyStore = Builder.CreateMemCpy(MemcpyDst, - AlignOne, - TmpBuffer, - TmpAlign, - MemcpySize); - revng_log(FixAccessLog, - "Created STORE: " << MemcpyStore << " : " - << dumpToString(MemcpyStore)); - - fixAccess({ MemcpyLoad, IOff.second }); - fixAccess({ MemcpyStore, It->second }); - - revng_log(FixAccessLog, - "Queuing for erasure memcpy: " << Instr << " : " - << dumpToString(Instr)); - InstructionsToRemove.push_back(Instr); - } // else do nothing because we fix it only once with loads - continue; - } - fixAccess(IOff); - } -} - -template -void CPUStateAccessFixer::fixAccess(const Pair &IOff) { - Instruction *const Instr = IOff.first; - const CSVOffsets &Offsets = IOff.second; - CSVOffsets::Kind OKind = Offsets.getKind(); - FixAccessLog << "Fixing access: " << Instr - << "\nCSVOffsets Kind: " << CSVOffsets::toString(OKind) << DoLog; - revng_assert(CSVOffsets::isPtr(OKind)); - Function *F = Instr->getFunction(); - Instruction *AccessToFix = Instr; - - switch (OKind) { - default: - case CSVOffsets::Kind::Unknown: - case CSVOffsets::Kind::Numeric: - revng_abort(); - case CSVOffsets::Kind::OutAndKnownInPtr: - case CSVOffsets::Kind::UnknownInPtr: - case CSVOffsets::Kind::OutAndUnknownInPtr: - case CSVOffsets::Kind::KnownInPtr: { - - revng_log(FixAccessLog, "Before: " << dumpToString(F)); - - // This is necessary to get the correct debug info. - // Setting the insert point to an Instruction also updates the Builder - // to use its debug info until the insert point is set to a new - // instruction. - // Given that in the rest of the code we mostly use - // SetInsertPoint(BasicBlock *), which does not reset the debug info, we - // want to do it now, otherwise we might end up using the wrong debug - // info from an instruction of a previous iteration of this loop. - Builder.SetInsertPoint(Instr); - - Value *Address = nullptr; - Type *LoadedType = nullptr; // This is not used if IsLoad is false - Type *StoredType = nullptr; // This is not used if IsLoad is true - if (IsLoad) { - Address = getLoadAddressValue(Instr); - LoadedType = getLoadedType(Instr); - } else { - Address = getStoreAddressValue(Instr); - StoredType = getStoredType(Instr); - } - - Value *OffsetValue = nullptr; - if (CSVOffsets::isInOutPtr(OKind)) - std::tie(AccessToFix, - LoadedType, - OffsetValue) = setupOutEnvAccess(Instr); - - revng_assert(AccessToFix != nullptr); - LLVMContext &Context = M.getContext(); - QuickMetadata QMD(Context); - - if (IsLoad) { - if (LoadedType != nullptr and LoadedType->isPointerTy()) { - FixAccessLog << "REPLACE!" << DoLog; - Constant *NullPtr = Constant::getNullValue(LoadedType); - AccessToFix->replaceAllUsesWith(NullPtr); - break; // out from the big switch to the verify and cleanup code - } - // TODO: Handle memcpy, not necessary for now - } else { - if (StoredType != nullptr and StoredType->isPointerTy()) { - FixAccessLog << "REPLACE!" << DoLog; - break; // out from the big switch to the verify and cleanup code - } - // TODO: Handle memcpy, not necessary for now - } - - // filter out cases where a switch is not necessary - if (Offsets.size() == 1) { - int64_t Offset = *Offsets.begin(); - - Instruction *Clone = AccessToFix->clone(); - Clone->insertAfter(AccessToFix); - AccessToFix->replaceAllUsesWith(Clone); - - Builder.SetInsertPoint(Clone); - bool Ok = false; - switch (AccessToFix->getOpcode()) { - - case Instruction::Load: { - if (IsLoad) { - Type *OriginalLoadedType = Clone->getType(); - unsigned Size = DL.getTypeAllocSize(OriginalLoadedType); - revng_assert(Size != 0); - auto *Loaded = Variables->loadFromEnvOffset(Builder, Size, Offset); - Ok = Loaded != nullptr; - if (Ok) { - Type *LoadedType = Loaded->getType(); - if (LoadedType != OriginalLoadedType) { - unsigned LoadedSize = DL.getTypeAllocSize(LoadedType); - revng_assert(LoadedSize == Size); - Loaded = Builder.CreateIntToPtr(Loaded, OriginalLoadedType); - } - Clone->replaceAllUsesWith(Loaded); - } - } else { - revng_abort(); - } - } break; - - case Instruction::Store: { - if (not IsLoad) { - auto *Store = cast(Clone); - auto *V = Store->getValueOperand(); - unsigned Size = DL.getTypeAllocSize(V->getType()); - revng_assert(Size != 0); - Ok = Variables->storeToEnvOffset(Builder, Size, Offset, V) - .has_value(); - } else { - revng_abort(); - } - } break; - - case Instruction::Call: { - CallInst *Call = cast(Clone); - Ok = Variables->memcpyAtEnvOffset(Builder, Call, Offset, IsLoad); - } break; - - default: - revng_abort(); - } - - if (not Ok) { - Builder.SetInsertPoint(Clone); - CallInst &CallAbort = emitAbort(Builder, ""); - auto InvalidMDKind = Context.getMDKindID("revng.csaa.invalid.unique.in." - "access"); - CallAbort.setMetadata(InvalidMDKind, QMD.tuple((uint32_t) 0)); - } else { - InstructionsToRemove.push_back(Clone); - } - - break; // out of the big switch to the verify and cleanup code - } - - // Create a new block NextBB, after the Switch, and move there all the - // instructions after the access - BasicBlock *AccessToFixBB = AccessToFix->getParent(); - auto InstrIt = AccessToFix->getIterator(); - revng_assert(InstrIt != AccessToFixBB->end()); - revng_assert(std::next(InstrIt) != AccessToFixBB->end()); - BasicBlock *NextBB = AccessToFixBB->splitBasicBlock(std::next(InstrIt)); - AccessToFixBB->getTerminator()->eraseFromParent(); - revng_assert(not NextBB->empty()); - revng_assert(std::next(InstrIt) == AccessToFixBB->end()); - // If we're processing loads, add a PHI in NextBB if necessary - PHINode *Phi = nullptr; - if (IsLoad) { - if (LoadedType != nullptr) { - Builder.SetInsertPoint(&NextBB->front()); - Phi = Builder.CreatePHI(LoadedType, Offsets.size()); - AccessToFix->replaceAllUsesWith(Phi); - } - } - - // Create the default BB for the switch, calling revng_abort() - BasicBlock *Default = BasicBlock::Create(Context, {}, F); - Builder.SetInsertPoint(Default); - CallInst &CallAbort = emitAbort(Builder, ""); - auto UnexpectedInMDKind = Context.getMDKindID("revng.csaa.unexpected.in." - "access"); - CallAbort.setMetadata(UnexpectedInMDKind, QMD.tuple((uint32_t) 0)); - - // Create the offset value to use as a variable for the switch if - // necessary - Builder.SetInsertPoint(AccessToFixBB); - if (OffsetValue == nullptr) - OffsetValue = Builder.CreatePtrToInt(Address, Int64Ty); - revng_assert(OffsetValue != nullptr); - - if (CSVOffsets::isUnknownInPtr(OKind)) { - - if (FixAccessLog.isEnabled()) { - ++NumUnknown; - - std::string Name = F->getName().str(); - FunToNumUnknown[Name]++; - FunToUnknowns[Name].insert(dumpToString(AccessToFix)); - } - - SwitchInst *SwitchOffset = Builder.CreateSwitch(OffsetValue, - Default, - EnvStructSize); - for (int64_t CurrEnvOff = 0; CurrEnvOff < EnvStructSize; ++CurrEnvOff) { - if (IsLoad) - setupLoadInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB, Phi); - else - setupStoreInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB); - } - revng_assert(SwitchOffset->getNumCases() > 0); - break; // out from the switch, to the verify and cleanup code - } - - revng_assert(Offsets.size() > 0); - SwitchInst *SwitchOffset = Builder.CreateSwitch(OffsetValue, - Default, - Offsets.size()); - for (const int64_t CurrEnvOff : Offsets) { - if (IsLoad) - setupLoadInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB, Phi); - else - setupStoreInEnv(AccessToFix, CurrEnvOff, SwitchOffset, NextBB); - } - - if (IsLoad) { - if (Phi != nullptr and Phi->getNumIncomingValues() == 0) { - Builder.SetInsertPoint(Phi); - - CallInst &CallAbort = emitAbort(Builder, ""); - auto NeverValidInMDKind = Context.getMDKindID("revng.csaa.never.valid." - "in.load"); - CallAbort.setMetadata(NeverValidInMDKind, QMD.tuple((uint32_t) 0)); - - Instruction *DisabledInLoad = AccessToFix->clone(); - DisabledInLoad->insertBefore(Phi); - Phi->replaceAllUsesWith(DisabledInLoad); - InstructionsToRemove.push_back(Phi); - } - } - } break; - } - - // Verify the transformation and cleanup the access to fix - revng_log(FixAccessLog, "After: " << dumpToString(F)); - if (Instr != AccessToFix) { - if (FixAccessLog.isEnabled()) { - FixAccessLog << "Queuing for erasure AccessToFix: " << AccessToFix - << DoLog; - FixAccessLog << dumpToString(AccessToFix) << DoLog; - } - InstructionsToRemove.push_back(AccessToFix); - } - if (FixAccessLog.isEnabled()) { - FixAccessLog << "Queuing for erasure Instr: " << Instr << DoLog; - FixAccessLog << dumpToString(Instr) << DoLog; - } - InstructionsToRemove.push_back(Instr); -} - -bool CPUStateAccessFixer::run() { - if (CPUStatePtr == nullptr) - return false; - - // Fix loads - correctCPUStateAccesses(); - revng::verify(&M); - - // Fix stores - correctCPUStateAccesses(); - revng::verify(&M); - - // Remove fixed accesses - for (Instruction *Instr : InstructionsToRemove) - eraseFromParent(Instr); - InstructionsToRemove.clear(); - - if (FixAccessLog.isEnabled()) { - FixAccessLog << "Num Unknowns: " << NumUnknown << DoLog; - - for (const auto &Fun2Num : FunToNumUnknown) - FixAccessLog << Fun2Num.first << ": " << Fun2Num.second << DoLog; - - for (const auto &Fun2Unknowns : FunToUnknowns) - for (const auto &U : Fun2Unknowns.second) - FixAccessLog << Fun2Unknowns.first << ": " << U << DoLog; - } - return true; -} - -class CPUStateAccessAnalysis { - -private: - const bool Lazy; - // A reference to the analyzed Module - Module &M; - - // A reference to the associated VariableManager - VariableManager *Variables = nullptr; - - // References to the maps that will be filled by this analysis. - // Every map maps an Instruction to the CSVOffset representing all the - // possible offsets that are accessed by that Instruction, being it either a - // load, a store or a call to Intrinsic::memcpy. - // We hold two separate maps, one for loads and one for stores, so that calls - // to memcpy can be in both maps, with different associated offsets. - AccessOffsetMap CSVLoadOffsetMap; - AccessOffsetMap CSVStoreOffsetMap; - - const unsigned LoadMDKind; - const unsigned StoreMDKind; - - // Helpers - const DataLayout &DL; - Type *Int64Ty = nullptr; - Value *CPUStatePtr = nullptr; - -public: - CPUStateAccessAnalysis(Module &Mod, VariableManager *V, const bool IsLazy) : - Lazy(IsLazy), - M(Mod), - Variables(V), - CSVLoadOffsetMap(), - CSVStoreOffsetMap(), - LoadMDKind(Mod.getContext().getMDKindID("revng.csvaccess.offsets.load")), - StoreMDKind(Mod.getContext().getMDKindID("revng.csvaccess.offsets.store")), - DL(Mod.getDataLayout()), - Int64Ty(llvm::IntegerType::getInt64Ty(Mod.getContext())), - CPUStatePtr(Mod.getGlobalVariable("env")) {} - -public: - bool run(); - -private: - bool forceEmptyMetadata(Function *RootFunction) const; -}; - -static void addAccessMetadata(const CallSiteOffsetMap &OffsetMap, - VariableManager *Variables, - QuickMetadata &QMD, - unsigned MDKind) { - for (auto &AccessOffsets : OffsetMap) { - CallInst *const CallSite = AccessOffsets.first; - if (CallSite == nullptr) - continue; - const CSVOffsets &Offsets = AccessOffsets.second; - revng_assert(Offsets.isPtr()); - - ConstantAsMetadata *UnknownAccess = nullptr; - MDTuple *AccessedVariablesTuple = nullptr; - SmallVector OffsetMetadata; - OffsetMetadata.reserve(Offsets.size()); - std::set AccessedVars; - if (Offsets.isUnknownInPtr()) { - CSVAccessLog << "Unknown access to CSV" << DoLog; - UnknownAccess = QMD.get((uint32_t) 1); - AccessedVariablesTuple = QMD.tuple(OffsetMetadata); - } else { - UnknownAccess = QMD.get((uint32_t) 0); - for (const int64_t O : Offsets) { - CSVAccessLog << "CallSite: " << CallSite << DoLog; - CSVAccessLog << "Refined: " << O << DoLog; - GlobalVariable *AccessedVar = Variables->getByEnvOffset(O).first; - bool NewlyInserted = AccessedVars.insert(AccessedVar).second; - if (NewlyInserted) { - OffsetMetadata.push_back(QMD.get(AccessedVar)); - } - CSVAccessLog << "Accessed Var: " << AccessedVar - << " Offset: " << Variables->getByEnvOffset(O).second - << DoLog; - } - AccessedVariablesTuple = QMD.tuple(OffsetMetadata); - } - SmallVector AccessMetadata = { UnknownAccess, - AccessedVariablesTuple }; - CallSite->setMetadata(MDKind, QMD.tuple(AccessMetadata)); - } -} - -bool CPUStateAccessAnalysis::forceEmptyMetadata(Function *RootFunction) const { - LLVMContext &Context = M.getContext(); - QuickMetadata QMD(Context); - bool Changed = false; - for (BasicBlock &BB : *RootFunction) { - for (Instruction &I : BB) { - if (isCallToHelper(&I)) { - if (I.getMetadata(LoadMDKind) == nullptr) { - I.setMetadata(LoadMDKind, - MDTuple::get(Context, - { QMD.get((uint32_t) 0), - MDTuple::get(Context, {}) })); - Changed = true; - } - if (I.getMetadata(StoreMDKind) == nullptr) { - I.setMetadata(StoreMDKind, - MDTuple::get(Context, - { QMD.get((uint32_t) 0), - MDTuple::get(Context, {}) })); - Changed = true; - } - } - } - } - return Changed; -} - -bool CPUStateAccessAnalysis::run() { - - if (CPUStatePtr == nullptr) - return false; - - // Get the root Function - Function *RootFunction = M.getFunction("root"); - revng_assert(RootFunction); - - // Preprocessing: detect all the functions that are directly reachable from - // the RootFunction - auto ReachedFunctions = computeDirectlyReachableFunctions(RootFunction, - LoadMDKind, - StoreMDKind, - Lazy); - if (CSVAccessLog.isEnabled()) { - CSVAccessLog << "====== Reachable Functions ======"; - for (const Function *F : ReachedFunctions) - CSVAccessLog << "\n" << F << ": " << F->getName().str(); - CSVAccessLog << DoLog; - } - - // Start with a forward taint analysis, to detect all the tainted Values, - // and all the tainted loads and stores. - CSVAccessLog << "Before Taint Analysis" << DoLog; - const auto TaintResults = forwardTaintAnalysis(&M, - CPUStatePtr, - ReachedFunctions, - LoadMDKind, - StoreMDKind, - Lazy); - CSVAccessLog << "After Taint Analysis" << DoLog; - - // If there are no tainted loads and stores we don't need to run the CPUSAOA. - if (TaintResults.TaintedLoads.empty() - and TaintResults.TaintedStores.empty()) { - CSVAccessLog << "No tainted loads nor stores" << DoLog; - bool Forced = forceEmptyMetadata(RootFunction); - return Forced or (not Lazy and not TaintResults.IllegalCalls.empty()); - } - - if (TaintLog.isEnabled()) { - revng_log(TaintLog, "==== Tainted Loads ===="); - for (const Instruction *I : TaintResults.TaintedLoads) { - revng_log(TaintLog, "In Function: " << I->getFunction()->getName()); - revng_log(TaintLog, I << " : " << dumpToString(I)); - } - revng_log(TaintLog, "==== Tainted Stores ===="); - for (const Instruction *I : TaintResults.TaintedStores) { - revng_log(TaintLog, "In Function: " << I->getFunction()->getName()); - revng_log(TaintLog, I << " : " << dumpToString(I)); - } - revng_log(TaintLog, "==== Illegal Calls ===="); - for (const Instruction *I : TaintResults.IllegalCalls) { - revng_log(TaintLog, "In Function: " << I->getFunction()->getName()); - revng_log(TaintLog, I << " : " << dumpToString(I)); - } - revng_log(TaintLog, "======================="); - } - - CallSiteOffsetMap CallSiteLoadOffset; - CallSiteOffsetMap CallSiteStoreOffset; - auto AccessOffsetAnalysis = CPUSAOA(M, - CPUStatePtr, - RootFunction, - ReachedFunctions, - TaintResults, - Lazy, - LoadMDKind, - StoreMDKind, - Variables, - CSVLoadOffsetMap, - CSVStoreOffsetMap, - CallSiteLoadOffset, - CallSiteStoreOffset); - bool Found = AccessOffsetAnalysis.run(); - - if (Found) { - QuickMetadata QMD(M.getContext()); - addAccessMetadata(CallSiteLoadOffset, Variables, QMD, LoadMDKind); - addAccessMetadata(CallSiteStoreOffset, Variables, QMD, StoreMDKind); - } - - if (not Lazy) { - CPUStateAccessFixer CSVAccessFixer(M, - Variables, - CSVLoadOffsetMap, - CSVStoreOffsetMap); - CSVAccessFixer.run(); - } - - bool Forced = forceEmptyMetadata(RootFunction); - return Forced or Found - or (not Lazy and not TaintResults.IllegalCalls.empty()); -} - -bool CPUStateAccessAnalysisPass::runOnModule(Module &Mod) { - CPUStateAccessAnalysis AccessAnalysis(Mod, Variables, Lazy); - return AccessAnalysis.run(); -} - -char CPUStateAccessAnalysisPass::ID = 0; - -using RegisterCPUSAAP = RegisterPass; -static RegisterCPUSAAP - X("cpustate-access-analysis", "CPUState Access Analysis Pass", false, false); diff --git a/lib/Lift/CodeGenerator.cpp b/lib/Lift/CodeGenerator.cpp index b43acb28e..6bd1e2ba4 100644 --- a/lib/Lift/CodeGenerator.cpp +++ b/lib/Lift/CodeGenerator.cpp @@ -17,12 +17,16 @@ #include #include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/ExecutionEngine/RuntimeDyld.h" #include "llvm/IR/CFG.h" #include "llvm/IR/DiagnosticPrinter.h" +#include "llvm/IR/Instruction.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/MDBuilder.h" +#include "llvm/IR/Metadata.h" #include "llvm/IR/Module.h" #include "llvm/IR/Verifier.h" #include "llvm/IRReader/IRReader.h" @@ -31,6 +35,7 @@ #include "llvm/Support/Progress.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/raw_os_ostream.h" +#include "llvm/Transforms/IPO.h" #include "llvm/Transforms/InstCombine/InstCombine.h" #include "llvm/Transforms/Scalar.h" #include "llvm/Transforms/Utils.h" @@ -48,21 +53,15 @@ #include "revng/Model/RawBinaryView.h" #include "revng/Support/CommandLine.h" #include "revng/Support/Debug.h" +#include "revng/Support/IRHelpers.h" #include "CodeGenerator.h" #include "ExternalJumpsHandler.h" #include "InstructionTranslator.h" #include "JumpTargetManager.h" -#include "PTCInterface.h" -// This name corresponds to a function in `libtinycode`. -RegisterIRHelper CPULoopHelper("cpu_loop"); - -// This name corresponds to a function in `libtinycode`. -RegisterIRHelper RevngAbortHelper("cpu_loop_exit"); - -// This name is not present after `drop-root`. -RegisterIRHelper InitializeEnv("initialize_env"); +RegisterIRHelper CPULoopExitHelper("cpu_loop_exit"); +RegisterIRHelper InitializeEnvHelper("helper_initialize_env"); using namespace llvm; @@ -70,11 +69,11 @@ using std::make_pair; using std::string; // Register all the arguments -static cl::opt RecordPTC("record-ptc", - cl::desc("create metadata for PTC"), +static cl::opt RecordTCG("record-tcg", + cl::desc("create metadata for TCG"), cl::cat(MainCategory)); -static Logger<> PTCLog("ptc"); +static Logger<> LibTcgLog("libtcg"); static Logger<> Log("lift"); template @@ -140,19 +139,6 @@ public: // Outline the destructor for the sake of privacy in the header CodeGenerator::~CodeGenerator() = default; -static std::unique_ptr parseIR(StringRef Path, LLVMContext &Context) { - std::unique_ptr Result; - SMDiagnostic Errors; - Result = parseIRFile(Path, Errors, Context); - - if (Result.get() == nullptr) { - Errors.print("revng", dbgs()); - revng_abort(); - } - - return Result; -} - CodeGenerator::CodeGenerator(const RawBinaryView &RawBinary, llvm::Module *TheModule, const TupleTree &Model, @@ -165,10 +151,15 @@ CodeGenerator::CodeGenerator(const RawBinaryView &RawBinary, Model(Model), TargetArchitecture(TargetArchitecture) { - OriginalInstrMDKind = Context.getMDKindID("oi"); - PTCInstrMDKind = Context.getMDKindID("pi"); + LibTcgInstrMDKind = Context.getMDKindID("pi"); - HelpersModule = parseIR(Helpers, Context); + HelpersModule = parseIR(Context, Helpers); + revng_assert(HelpersModule->getGlobalVariable("cpu_loop_exiting") != nullptr); + + legacy::PassManager OptimizingPM; + OptimizingPM.add(createSROAPass()); + OptimizingPM.add(createInstSimplifyLegacyPass()); + OptimizingPM.run(*HelpersModule); TheModule->setDataLayout(HelpersModule->getDataLayout()); @@ -189,7 +180,7 @@ CodeGenerator::CodeGenerator(const RawBinaryView &RawBinary, FunctionTags::Exceptional.addTo(&F); } - EarlyLinkedModule = parseIR(EarlyLinked, Context); + EarlyLinkedModule = parseIR(Context, EarlyLinked); for (llvm::Function &F : *EarlyLinkedModule) { if (F.isIntrinsic()) continue; @@ -210,21 +201,6 @@ CodeGenerator::CodeGenerator(const RawBinaryView &RawBinary, for (auto &[Segment, Data] : RawBinary.segments()) { // If it's executable register it as a valid code area if (Segment.IsExecutable()) { - // We ignore possible p_filesz-p_memsz mismatches, zeros wouldn't be - // useful code anyway - uint64_t Size = Segment.VirtualSize(); - revng_log(Log, - "mmap'ing segment starting at " - << Segment.StartAddress().toString() << " with size 0x" - << Size); - bool Success = ptc.mmap(Segment.StartAddress().address(), - static_cast(Data.data()), - Size); - if (not Success) { - revng_log(Log, "Couldn't mmap segment!"); - continue; - } - bool Found = false; MetaAddress End = Segment.pagesRange().second; revng_assert(End.isValid() and End.address() % 4096 == 0); @@ -239,454 +215,17 @@ CodeGenerator::CodeGenerator(const RawBinaryView &RawBinary, if (not Found) { revng_check(Segment.endAddress().address() != 0); NoMoreCodeBoundaries.insert(Segment.endAddress()); - using namespace model::Architecture; - auto Architecture = Model->Architecture(); - auto BasicBlockEndingPattern = getBasicBlockEndingPattern(Architecture); - ptc.mmap(End.address(), - BasicBlockEndingPattern.data(), - BasicBlockEndingPattern.size()); } } } } -static BasicBlock *replaceFunction(Function *ToReplace) { - MetadataBackup SavedMetadata(ToReplace); - - ToReplace->setLinkage(GlobalValue::InternalLinkage); - ToReplace->dropAllReferences(); - - SavedMetadata.restoreIn(ToReplace); - - return BasicBlock::Create(ToReplace->getParent()->getContext(), - "", - ToReplace); -} - -static void replaceFunctionWithRet(Function *ToReplace, uint64_t Result) { - if (ToReplace == nullptr) - return; - - BasicBlock *Body = replaceFunction(ToReplace); - Value *ResultValue = nullptr; - - if (ToReplace->getReturnType()->isVoidTy()) { - revng_assert(Result == 0); - ResultValue = nullptr; - } else if (ToReplace->getReturnType()->isIntegerTy()) { - auto *ReturnType = cast(ToReplace->getReturnType()); - ResultValue = ConstantInt::get(ReturnType, Result, false); - } else { - revng_unreachable("No-op functions can only return void or an integer " - "type"); - } - - ReturnInst::Create(ToReplace->getParent()->getContext(), ResultValue, Body); -} - -class CpuLoopFunctionPass : public llvm::ModulePass { -private: - intptr_t ExceptionIndexOffset; - -public: - static char ID; - - CpuLoopFunctionPass() : llvm::ModulePass(ID), ExceptionIndexOffset(0) {} - - CpuLoopFunctionPass(intptr_t ExceptionIndexOffset) : - llvm::ModulePass(ID), ExceptionIndexOffset(ExceptionIndexOffset) {} - - void getAnalysisUsage(llvm::AnalysisUsage &AU) const override; - - bool runOnModule(llvm::Module &M) override; -}; - -char CpuLoopFunctionPass::ID = 0; - -using RegisterCLF = RegisterPass; -static RegisterCLF Y("cpu-loop", "cpu_loop FunctionPass", false, false); - -void CpuLoopFunctionPass::getAnalysisUsage(llvm::AnalysisUsage &AU) const { - AU.addRequired(); -} - -template -auto findUnique(Range &&TheRange, UnaryPredicate Predicate) - -> decltype(*TheRange.begin()) { - - const auto Begin = TheRange.begin(); - const auto End = TheRange.end(); - - auto It = std::find_if(Begin, End, Predicate); - auto Result = It; - revng_assert(Result != End); - revng_assert(std::find_if(++It, End, Predicate) == End); - - return *Result; -} - -template -auto findUnique(Range &&TheRange) -> decltype(*TheRange.begin()) { - - const auto Begin = TheRange.begin(); - const auto End = TheRange.end(); - - auto Result = Begin; - revng_assert(Begin != End && ++Result == End); - - return *Begin; -} - -bool CpuLoopFunctionPass::runOnModule(Module &M) { - Function &F = *getIRHelper("cpu_loop", M); - - // cpu_loop must return void - revng_assert(F.getReturnType()->isVoidTy()); - - // Part 1: remove the backedge of the main infinite loop - const LoopInfo &LI = getAnalysis(F).getLoopInfo(); - const Loop *OutermostLoop = findUnique(LI); - - BasicBlock *Header = OutermostLoop->getHeader(); - - // Check that the header has only one predecessor inside the loop - auto IsInLoop = [&OutermostLoop](BasicBlock *Predecessor) { - return OutermostLoop->contains(Predecessor); - }; - BasicBlock *Footer = findUnique(predecessors(Header), IsInLoop); - - // Assert on the type of the last instruction (branch or brcond) - revng_assert(Footer->end() != Footer->begin()); - Instruction *LastInstruction = &*--Footer->end(); - revng_assert(isa(LastInstruction)); - - // Remove the last instruction and replace it with a ret - eraseFromParent(LastInstruction); - ReturnInst::Create(F.getParent()->getContext(), Footer); - - // Part 2: replace the call to cpu_*_exec with exception_index - auto IsCpuExec = [](Function &TheFunction) { - StringRef Name = TheFunction.getName(); - return Name.startswith("cpu_") && Name.endswith("_exec"); - }; - Function &CpuExec = findUnique(F.getParent()->functions(), IsCpuExec); - - User *CallUser = findUnique(CpuExec.users(), [&F](User *TheUser) { - auto *TheInstruction = dyn_cast(TheUser); - - if (TheInstruction == nullptr) - return false; - - return TheInstruction->getParent()->getParent() == &F; - }); - - auto *Call = cast(CallUser); - revng_assert(getCalledFunction(Call) == &CpuExec); - Value *CPUState = Call->getArgOperand(0); - Type *TargetType = CpuExec.getReturnType(); - - revng::NonDebugInfoCheckingIRBuilder Builder(Call); - - Type *IntPtrTy = Builder.getIntPtrTy(M.getDataLayout()); - Value *CPUIntPtr = Builder.CreatePtrToInt(CPUState, IntPtrTy); - using CI = ConstantInt; - auto Offset = CI::get(IntPtrTy, ExceptionIndexOffset); - Value *ExceptionIndexIntPtr = Builder.CreateAdd(CPUIntPtr, Offset); - Value *ExceptionIndexPtr = Builder.CreateIntToPtr(ExceptionIndexIntPtr, - TargetType->getPointerTo()); - Value *ExceptionIndex = Builder.CreateLoad(TargetType, ExceptionIndexPtr); - Call->replaceAllUsesWith(ExceptionIndex); - eraseFromParent(Call); - - return true; -} - -class CpuLoopExitPass : public llvm::ModulePass { -public: - static char ID; - - CpuLoopExitPass() : llvm::ModulePass(ID), VM(nullptr) {} - CpuLoopExitPass(VariableManager *VM) : llvm::ModulePass(ID), VM(VM) {} - - bool runOnModule(llvm::Module &M) override; - -private: - VariableManager *VM = nullptr; -}; - -char CpuLoopExitPass::ID = 0; - -using RegisterCLE = RegisterPass; -static RegisterCLE Z("cpu-loop-exit", "cpu_loop_exit Pass", false, false); - -static void purgeNoReturn(Function *F) { - auto &Context = F->getParent()->getContext(); - - if (F->hasFnAttribute(Attribute::NoReturn)) - F->removeFnAttr(Attribute::NoReturn); - - for (User *U : F->users()) - if (auto *Call = dyn_cast(U)) - if (Call->hasFnAttr(Attribute::NoReturn)) { - auto OldAttr = Call->getAttributes(); - auto NewAttr = OldAttr.removeFnAttribute(Context, Attribute::NoReturn); - Call->setAttributes(NewAttr); - } -} - -static ReturnInst *createRet(Instruction *Position) { - Function *F = Position->getParent()->getParent(); - purgeNoReturn(F); - - Type *ReturnType = F->getFunctionType()->getReturnType(); - if (ReturnType->isVoidTy()) { - return ReturnInst::Create(F->getParent()->getContext(), nullptr, Position); - } else if (ReturnType->isIntegerTy()) { - auto *Zero = ConstantInt::get(static_cast(ReturnType), 0); - return ReturnInst::Create(F->getParent()->getContext(), Zero, Position); - } else { - revng_abort("Return type not supported"); - } - - return nullptr; -} - -/// Find all calls to cpu_loop_exit and replace them with: -/// -/// * call cpu_loop -/// * set cpu_loop_exiting = true -/// * return -/// -/// Then look for all the callers of the function calling cpu_loop_exit and make -/// them check whether they should return immediately (cpu_loop_exiting == true) -/// or not. -/// Then when we reach the root function, set cpu_loop_exiting to false after -/// the call. -bool CpuLoopExitPass::runOnModule(llvm::Module &M) { - LLVMContext &Context = M.getContext(); - Function *CpuLoopExit = getIRHelper("cpu_loop_exit", M); - - // Nothing to do here - if (CpuLoopExit == nullptr) - return false; - - revng_assert(VM->hasEnv()); - - purgeNoReturn(CpuLoopExit); - - Function *CpuLoop = getIRHelper("cpu_loop", M); - IntegerType *BoolType = Type::getInt1Ty(Context); - std::set FixedCallers; - GlobalVariable *CpuLoopExitingVariable = nullptr; - CpuLoopExitingVariable = new GlobalVariable(M, - BoolType, - false, - GlobalValue::CommonLinkage, - ConstantInt::getFalse(BoolType), - StringRef("cpu_loop_exiting")); - - revng_assert(CpuLoop != nullptr); - - std::queue CpuLoopExitUsers; - for (User *TheUser : CpuLoopExit->users()) - CpuLoopExitUsers.push(TheUser); - - while (!CpuLoopExitUsers.empty()) { - auto *Call = cast(CpuLoopExitUsers.front()); - CpuLoopExitUsers.pop(); - revng_assert(getCalledFunction(Call) == CpuLoopExit); - - // Call cpu_loop - auto *FirstArgTy = CpuLoop->getFunctionType()->getParamType(0); - auto *EnvPtr = VM->cpuStateToEnv(Call->getArgOperand(0), Call); - - auto *CallCpuLoop = CallInst::Create(CpuLoop, { EnvPtr }, "", Call); - - // In recent versions of LLVM you can no longer inject a CallInst in a - // Function with debug location if the call itself has not a debug location - // as well, otherwise module verification will fail - CallCpuLoop->setDebugLoc(Call->getDebugLoc()); - - // Set cpu_loop_exiting to true - new StoreInst(ConstantInt::getTrue(BoolType), CpuLoopExitingVariable, Call); - - // Return immediately - createRet(Call); - auto *Unreach = cast(&*++Call->getIterator()); - eraseFromParent(Unreach); - - Function *Caller = Call->getParent()->getParent(); - - // Remove the call to cpu_loop_exit - eraseFromParent(Call); - - if (!FixedCallers.contains(Caller)) { - FixedCallers.insert(Caller); - - std::queue WorkList; - WorkList.push(Caller); - - while (!WorkList.empty()) { - Value *F = WorkList.front(); - WorkList.pop(); - - for (User *RecUser : F->users()) { - auto *RecCall = dyn_cast(RecUser); - if (RecCall == nullptr) { - auto *Cast = dyn_cast(RecUser); - revng_assert(Cast != nullptr, "Unexpected user"); - revng_assert(Cast->getOperand(0) == F && Cast->isCast()); - WorkList.push(Cast); - continue; - } - - Function *RecCaller = RecCall->getParent()->getParent(); - - // TODO: make this more reliable than using function name - // If the caller is a QEMU helper function make it check - // cpu_loop_exiting and if it's true, make it return - - // Split BB - BasicBlock *OldBB = RecCall->getParent(); - BasicBlock::iterator SplitPoint = ++RecCall->getIterator(); - revng_assert(SplitPoint != OldBB->end()); - BasicBlock *NewBB = OldBB->splitBasicBlock(SplitPoint); - - // Add a BB with a ret - BasicBlock *QuitBB = BasicBlock::Create(Context, - "cpu_loop_exit_return", - RecCaller, - NewBB); - UnreachableInst *Temp = new UnreachableInst(Context, QuitBB); - createRet(Temp); - eraseFromParent(Temp); - - // Check value of cpu_loop_exiting - auto *Branch = cast(&*++(RecCall->getIterator())); - auto *PointeeTy = CpuLoopExitingVariable->getValueType(); - auto *Compare = new ICmpInst(Branch, - CmpInst::ICMP_EQ, - new LoadInst(PointeeTy, - CpuLoopExitingVariable, - "", - Branch), - ConstantInt::getTrue(BoolType)); - - BranchInst::Create(QuitBB, NewBB, Compare, Branch); - eraseFromParent(Branch); - - // Add to the work list only if it hasn't been fixed already - if (!FixedCallers.contains(RecCaller)) { - FixedCallers.insert(RecCaller); - WorkList.push(RecCaller); - } - } - } - } - } - - return true; -} - -void CodeGenerator::translate(optional RawVirtualAddress) { +void CodeGenerator::translate(LibTcg &LibTcg, + std::optional RawVirtualAddress) { using FT = FunctionType; Task T(12, "Translation"); - // Prepare the helper modules by transforming the cpu_loop function and - // running SROA - T.advance("Prepare helpers module", true); - legacy::PassManager CpuLoopPM; - CpuLoopPM.add(new LoopInfoWrapperPass()); - CpuLoopPM.add(new CpuLoopFunctionPass(ptc.exception_index)); - CpuLoopPM.add(createSROAPass()); - CpuLoopPM.run(*HelpersModule); - - // Drop the main - eraseFromParent(HelpersModule->getFunction("main")); - - // From syscall.c - new GlobalVariable(*TheModule, - Type::getInt32Ty(Context), - false, - GlobalValue::CommonLinkage, - ConstantInt::get(Type::getInt32Ty(Context), 0), - StringRef("do_strace")); - - // - // Handle some specific QEMU functions as no-ops or abort - // - - // Transform in no op - static constexpr auto - NoOpFunctionNames = make_array("cpu_dump_state", - "cpu_exit", - "end_exclusive" - "fprintf", - "mmap_lock", - "mmap_unlock", - "pthread_cond_broadcast", - "pthread_mutex_unlock", - "pthread_mutex_lock", - "pthread_cond_wait", - "pthread_cond_signal", - "process_pending_signals", - "qemu_log_mask", - "qemu_thread_atexit_init", - "start_exclusive"); - for (auto Name : NoOpFunctionNames) - replaceFunctionWithRet(HelpersModule->getFunction(Name), 0); - - // Transform in abort - - // do_arm_semihosting: we don't care about semihosting - // EmulateAll: requires access to the opcode - static constexpr auto - AbortFunctionNames = make_array("cpu_restore_state", - "cpu_mips_exec", - "gdb_handlesig", - "queue_signal", - // syscall.c - "do_ioctl_dm", - "print_syscall", - "print_syscall_ret", - // ARM cpu_loop - "cpu_abort", - "do_arm_semihosting", - "EmulateAll"); - for (auto Name : AbortFunctionNames) { - Function *OldFunc = HelpersModule->getFunction(Name); - if (OldFunc != nullptr) { - llvm::DebugLoc DLocation; - if (not OldFunc->empty()) - DLocation = OldFunc->getEntryBlock().getTerminator()->getDebugLoc(); - - revng::NonDebugInfoCheckingIRBuilder Builder(replaceFunction(OldFunc), - DLocation); - emitAbort(Builder, - llvm::Twine("Abort instead of calling `") + Name + "`", - std::move(DLocation)); - } - } - - replaceFunctionWithRet(HelpersModule->getFunction("page_check_range"), 1); - replaceFunctionWithRet(HelpersModule->getFunction("page_get_flags"), - 0xffffffff); - - // - // Record globals for marking them as internal after linking - // - std::vector HelperGlobals; - for (GlobalVariable &GV : HelpersModule->globals()) - if (GV.hasName()) - HelperGlobals.push_back(GV.getName().str()); - - std::vector HelperFunctions; - for (Function &F : HelpersModule->functions()) - if (F.hasName() and F.getName() != "target_set_brk" - and F.getName() != "syscall_init") - HelperFunctions.push_back(F.getName().str()); - // // Link helpers module into the main module // @@ -695,20 +234,6 @@ void CodeGenerator::translate(optional RawVirtualAddress) { bool Result = TheLinker.linkInModule(std::move(HelpersModule)); revng_assert(not Result, "Linking failed"); - // - // Mark as internal all the imported globals - // - for (StringRef GlobalName : HelperGlobals) - if (not GlobalName.startswith("llvm.")) - if (auto *GV = TheModule->getGlobalVariable(GlobalName)) - if (not GV->isDeclaration()) - GV->setLinkage(GlobalValue::InternalLinkage); - - for (StringRef FunctionName : HelperFunctions) - if (auto *F = TheModule->getFunction(FunctionName)) - if (not F->isDeclaration() and not F->isIntrinsic()) - F->setLinkage(GlobalValue::InternalLinkage); - // // Create the VariableManager // @@ -718,65 +243,41 @@ void CodeGenerator::translate(optional RawVirtualAddress) { TargetIsLittleEndian = isLittleEndian(TargetArchitecture); } - // TODO: this not very robust. We should have a function with a sensible name - // taking as argument ${ARCH}CPU so that we can easily identify the - // struct. - std::string CPUStructName = (Twine("struct.") + ptc.cpu_struct_name).str(); - auto *CPUStruct = StructType::getTypeByName(TheModule->getContext(), - CPUStructName); - revng_assert(CPUStruct != nullptr); + const auto &ArchInfo = LibTcg.archInfo(); VariableManager Variables(*TheModule, TargetIsLittleEndian, - CPUStruct, - ptc.env_offset); - auto CreateCPUStateAccessAnalysisPass = [&Variables]() { - return new CPUStateAccessAnalysisPass(&Variables, true); - }; - - { - legacy::PassManager PM; - PM.add(new CpuLoopExitPass(&Variables)); - PM.run(*TheModule); - } - - std::set CpuLoopExitingUsers; - GlobalVariable *CpuLoopExiting = TheModule->getGlobalVariable("cpu_loop_" - "exiting"); - revng_assert(CpuLoopExiting != nullptr); - for (User *U : CpuLoopExiting->users()) - if (auto *I = dyn_cast(U)) - CpuLoopExitingUsers.insert(I->getParent()->getParent()); - + ArchInfo.env_offset, + LibTcg.envPointer(), + LibTcg.globalNames()); // // Create well-known CSVs // auto SP = model::Architecture::getStackPointer(Model->Architecture()); - std::string SPName = model::Register::getCSVName(SP).str(); - GlobalVariable *SPReg = Variables.getByEnvOffset(ptc.sp, SPName).first; + std::string SPName = model::Register::getCSVName(SP); + GlobalVariable *SPReg = Variables.getByEnvOffset(ArchInfo.sp).first; using PCHOwner = std::unique_ptr; - auto Factory = [&Variables](PCAffectingCSV::Values CSVID, - llvm::StringRef Name) -> GlobalVariable * { + auto Factory = [&Variables, + &ArchInfo](PCAffectingCSV::Values CSVID) -> GlobalVariable * { intptr_t Offset = 0; switch (CSVID) { case PCAffectingCSV::PC: - Offset = ptc.pc; + Offset = ArchInfo.pc; break; case PCAffectingCSV::IsThumb: - Offset = ptc.is_thumb; + Offset = ArchInfo.is_thumb; break; default: revng_abort(); } - return Variables.getByEnvOffset(Offset, Name.str()).first; + return Variables.getByEnvOffset(Offset).first; }; - auto Architecture = toLLVMArchitecture(Model->Architecture()); - PCHOwner PCH = ProgramCounterHandler::create(Architecture, + PCHOwner PCH = ProgramCounterHandler::create(Model->Architecture(), TheModule, Factory); @@ -786,15 +287,15 @@ void CodeGenerator::translate(optional RawVirtualAddress) { auto *MainType = FT::get(Builder.getVoidTy(), { SPReg->getValueType() }, false); - auto *MainFunction = Function::Create(MainType, + auto *RootFunction = Function::Create(MainType, Function::ExternalLinkage, "root", TheModule); - FunctionTags::Root.addTo(MainFunction); + FunctionTags::Root.addTo(RootFunction); // Create the first basic block and create a placeholder for variable // allocations - BasicBlock *Entry = BasicBlock::Create(Context, "entrypoint", MainFunction); + BasicBlock *Entry = BasicBlock::Create(Context, "entrypoint", RootFunction); Builder.SetInsertPoint(Entry); // We need to remember this instruction so we can later insert a call here. @@ -803,7 +304,7 @@ void CodeGenerator::translate(optional RawVirtualAddress) { // After the translation we will and use this information to create a call to // a helper function. // TODO: we need a more elegant solution here - auto *Delimiter = Builder.CreateStore(&*MainFunction->arg_begin(), SPReg); + auto *Delimiter = Builder.CreateStore(&*RootFunction->arg_begin(), SPReg); Variables.setAllocaInsertPoint(Delimiter); auto *InitEnvInsertPoint = Delimiter; @@ -819,11 +320,7 @@ void CodeGenerator::translate(optional RawVirtualAddress) { } // Create an instance of JumpTargetManager - JumpTargetManager JumpTargets(MainFunction, - PCH.get(), - CreateCPUStateAccessAnalysisPass, - Model, - RawBinary); + JumpTargetManager JumpTargets(RootFunction, PCH.get(), Model, RawBinary); MetaAddress VirtualAddress = MetaAddress::invalid(); if (RawVirtualAddress) { @@ -864,8 +361,8 @@ void CodeGenerator::translate(optional RawVirtualAddress) { T.advance("Lifting code", true); Task LiftTask({}, "Lifting"); LiftTask.advance("Initial address peeking", false); - - InstructionTranslator Translator(Builder, + InstructionTranslator Translator(LibTcg, + Builder, Variables, JumpTargets, Blocks, @@ -874,7 +371,14 @@ void CodeGenerator::translate(optional RawVirtualAddress) { std::tie(VirtualAddress, Entry) = JumpTargets.peek(); + auto MaybeData = RawBinary.getFromAddressOn(VirtualAddress); + revng_assert(MaybeData); + llvm::ArrayRef CodeBuffer = *MaybeData; + MetaAddress CodeBufferStartAddress = VirtualAddress; + while (Entry != nullptr) { + CodeBuffer = RawBinary.getFromAddressOn(VirtualAddress).value(); + LiftTask.advance(VirtualAddress.toString(), true); Task TranslateTask(3, "Translate"); @@ -885,162 +389,142 @@ void CodeGenerator::translate(optional RawVirtualAddress) { // TODO: what if create a new instance of an InstructionTranslator here? Translator.reset(); + uint32_t TranslateFlags = 0; + if (VirtualAddress.type() == MetaAddressType::Code_arm_thumb) { + TranslateFlags |= LIBTCG_TRANSLATE_ARM_THUMB; + } + + auto TranslationBlock = LibTcg.translateBlock(CodeBuffer.data(), + CodeBuffer.size(), + VirtualAddress.address(), + TranslateFlags); + // TODO: rename this type - PTCInstructionListPtr InstructionList(new PTCInstructionList); - uint64_t ConsumedSize = 0; - - PTCCodeType Type = PTC_CODE_REGULAR; - - switch (VirtualAddress.type()) { - case MetaAddressType::Invalid: - revng_abort(); - - case MetaAddressType::Code_arm_thumb: - Type = PTC_CODE_ARM_THUMB; - break; - - default: - Type = PTC_CODE_REGULAR; - break; - } - - revng_log(Log, "Translating " << VirtualAddress.toString()); - ConsumedSize = ptc.translate(VirtualAddress.address(), - Type, - InstructionList.get()); - - if (ConsumedSize == 0) { - Translator.emitNewPCCall(Builder, VirtualAddress, 1, nullptr); - emitAbort(Builder, "", Entry->getTerminator()->getDebugLoc()); - - // Obtain a new program counter to translate - TranslateTask.complete(); - LiftTask.advance("Peek new address", true); - std::tie(VirtualAddress, Entry) = JumpTargets.peek(); - - continue; - } - - // Check whether we ended up in an unmapped page - MetaAddress AbortAt = MetaAddress::invalid(); - MetaAddress LastByte = VirtualAddress.toGeneric() + (ConsumedSize - 1); - if (VirtualAddress.pageStart() != LastByte.pageStart()) { - MetaAddress NextPage = VirtualAddress.nextPageStart(); - if (NoMoreCodeBoundaries.contains(NextPage)) - AbortAt = NextPage; - } + const size_t ConsumedSize = TranslationBlock->size_in_bytes; + revng_assert(ConsumedSize > 0); SmallSet ToIgnore; - ToIgnore = Translator.preprocess(InstructionList.get()); + // Handles writes to btarget, represents branching for microblaze/mips/cris + ToIgnore = Translator.preprocess(*TranslationBlock); - if (PTCLog.isEnabled()) { - std::stringstream Stream; - dumpTranslation(VirtualAddress, Stream, InstructionList.get()); - PTCLog << Stream.str() << DoLog; + if (LibTcgLog.isEnabled()) { + static std::array DumpBuf{ 0 }; + LibTcgLog << "Translation starting from " << VirtualAddress.toGeneric() + << " (size: " << ConsumedSize << " bytes)" << DoLog; + LoggerIndent<> Indent(LibTcgLog); + LibTcgLog.indent(); + for (size_t I = 0; I < TranslationBlock->instruction_count; ++I) { + auto Opcode = TranslationBlock->list[I].opcode; + bool IsInstructionStart = Opcode == LIBTCG_op_insn_start; + if (IsInstructionStart) + LibTcgLog.unindent(); + LibTcg.dumpInstructionToBuffer(&TranslationBlock->list[I], + DumpBuf.data(), + DumpBuf.size()); + LibTcgLog << StringRef(DumpBuf.data()).trim(); + if (ToIgnore.contains(I)) + LibTcgLog << " (ignored)"; + LibTcgLog << DoLog; + if (IsInstructionStart) + LibTcgLog.indent(); + } + LibTcgLog.unindent(); } - Variables.newFunction(InstructionList.get()); - unsigned J = 0; - MDNode *MDOriginalInstr = nullptr; + Variables.newTranslationBlock(); bool StopTranslation = false; MetaAddress PC = VirtualAddress; MetaAddress NextPC = MetaAddress::invalid(); MetaAddress EndPC = VirtualAddress + ConsumedSize; - const auto InstructionCount = InstructionList->instruction_count; + const auto InstructionCount = TranslationBlock->instruction_count; using IT = InstructionTranslator; IT::TranslationResult Result; - TranslateTask.advance("Translate to LLVM IR", true); + unsigned J = 0; - Task TranslateToLLVMTask(InstructionCount + 1, "Translate to LLVM IR"); - TranslateToLLVMTask.advance("", true); - - // Handle the first PTC_INSTRUCTION_op_debug_insn_start + // Handle the first LIBTCG_op_insn_start { - PTCInstruction *NextInstruction = nullptr; + LibTcgInstruction *NextInstruction = nullptr; for (unsigned K = 1; K < InstructionCount; K++) { - PTCInstruction *I = &InstructionList->instructions[K]; - if (I->opc == PTC_INSTRUCTION_op_debug_insn_start - && !ToIgnore.contains(K)) { + LibTcgInstruction *I = &TranslationBlock->list[K]; + if (I->opcode == LIBTCG_op_insn_start && ToIgnore.count(K) == 0) { NextInstruction = I; break; } } - PTCInstruction *Instruction = &InstructionList->instructions[J]; - std::tie(Result, - MDOriginalInstr, - PC, - NextPC) = Translator.newInstruction(Instruction, - NextInstruction, - VirtualAddress, - EndPC, - true, - AbortAt); - - if (Result == InstructionTranslator::Abort) { - StopTranslation = true; - emitAbort(Builder, "", Entry->getTerminator()->getDebugLoc()); - } - + LibTcgInstruction *Instruction = &TranslationBlock->list[J]; + std::tie(Result, PC, NextPC) = Translator.newInstruction(Instruction, + NextInstruction, + VirtualAddress, + EndPC, + true); J++; } + unsigned SinceInstructionStart = 0; + // TODO: shall we move this whole loop in InstructionTranslator? for (; J < InstructionCount && !StopTranslation; J++) { - TranslateToLLVMTask.advance("", true); - if (ToIgnore.contains(J)) + if (ToIgnore.count(J) != 0) continue; - PTCInstruction Instruction = InstructionList->instructions[J]; - PTCOpcode Opcode = Instruction.opc; + LibTcgInstruction *Instruction = &TranslationBlock->list[J]; + auto Opcode = Instruction->opcode; Blocks.clear(); Blocks.push_back(Builder.GetInsertBlock()); + ++SinceInstructionStart; + switch (Opcode) { - case PTC_INSTRUCTION_op_discard: + case LIBTCG_op_discard: // Instructions we don't even consider break; - case PTC_INSTRUCTION_op_debug_insn_start: { + case LIBTCG_op_insn_start: { + SinceInstructionStart = 0; + // Find next instruction, if there is one - PTCInstruction *NextInstruction = nullptr; + LibTcgInstruction *NextInstruction = nullptr; for (unsigned K = J + 1; K < InstructionCount; K++) { - PTCInstruction *I = &InstructionList->instructions[K]; - if (I->opc == PTC_INSTRUCTION_op_debug_insn_start - && !ToIgnore.contains(K)) { + LibTcgInstruction *I = &TranslationBlock->list[K]; + if (I->opcode == LIBTCG_op_insn_start && ToIgnore.count(K) == 0) { NextInstruction = I; break; } } std::tie(Result, - MDOriginalInstr, PC, - NextPC) = Translator.newInstruction(&Instruction, + NextPC) = Translator.newInstruction(Instruction, NextInstruction, VirtualAddress, EndPC, - false, - AbortAt); + false); } break; - case PTC_INSTRUCTION_op_call: { - Result = Translator.translateCall(&Instruction); + case LIBTCG_op_call: { + Result = Translator.translateCall(Instruction, + PC, + SinceInstructionStart); // Sometimes libtinycode terminates a basic block with a call, in this // case force a fallthrough - auto &IL = InstructionList; - if (J == IL->instruction_count - 1) { + if (J == TranslationBlock->instruction_count - 1) { BasicBlock *Target = JumpTargets.registerJT(EndPC, JTReason::PostHelper); - Builder.CreateBr(¬Null(Target)); + if (Target != nullptr) { + Builder.CreateBr(¬Null(Target)); + } else { + emitAbort(Builder, ""); + } } - } break; - default: - Result = Translator.translate(&Instruction, PC, NextPC); + Result = Translator.translate(Instruction, + PC, + SinceInstructionStart, + NextPC); break; } @@ -1057,46 +541,47 @@ void CodeGenerator::translate(optional RawVirtualAddress) { break; } - // Create a new metadata referencing the PTC instruction we have just + // Create a new metadata referencing the TCG instruction we have just // translated - MDNode *MDPTCInstr = nullptr; - if (RecordPTC) { - std::stringstream PTCStringStream; - dumpInstruction(PTCStringStream, InstructionList.get(), J); - std::string PTCString = PTCStringStream.str() + "\n"; - MDString *MDPTCString = MDString::get(Context, PTCString); - MDPTCInstr = MDNode::getDistinct(Context, MDPTCString); + MDNode *MDLibTcgInstr = nullptr; + if (RecordTCG) { + static std::array DumpBuf{ 0 }; + LibTcg.dumpInstructionToBuffer(&TranslationBlock->list[J], + DumpBuf.data(), + DumpBuf.size()); + + // Eh not very nice to strlen in construction of the StringRef, + // maybe we can get the length from the LibTcg call above? + StringRef Str{ DumpBuf.data() }; + MDString *MDLibTcgString = MDString::get(Context, Str); + MDLibTcgInstr = MDNode::getDistinct(Context, MDLibTcgString); } // Set metadata for all the new instructions for (BasicBlock *Block : Blocks) { BasicBlock::iterator I = Block->end(); while (I != Block->begin() && !(--I)->hasMetadata()) { - if (MDOriginalInstr != nullptr) - I->setMetadata(OriginalInstrMDKind, MDOriginalInstr); - if (MDPTCInstr != nullptr) - I->setMetadata(PTCInstrMDKind, MDPTCInstr); + if (MDLibTcgInstr != nullptr) + I->setMetadata(LibTcgInstrMDKind, MDLibTcgInstr); } } } // End loop over instructions - TranslateToLLVMTask.complete(); - + TranslateTask.complete(); TranslateTask.advance("Finalization", true); // We might have a leftover block, probably due to the block created after // the last call to exit_tb auto *LastBlock = Builder.GetInsertBlock(); - if (LastBlock->empty()) + if (LastBlock->empty()) { eraseFromParent(LastBlock); - else if (!LastBlock->rbegin()->isTerminator()) { + } else if (!LastBlock->rbegin()->isTerminator()) { // Something went wrong, probably a mistranslation Builder.CreateUnreachable(); } Translator.registerDirectJumps(); - // Obtain a new program counter to translate TranslateTask.complete(); LiftTask.advance("Peek new address", true); @@ -1110,7 +595,7 @@ void CodeGenerator::translate(optional RawVirtualAddress) { // Reorder basic blocks in RPOT T.advance("Reordering basic blocks", true); { - BasicBlock *Entry = &MainFunction->getEntryBlock(); + BasicBlock *Entry = &RootFunction->getEntryBlock(); ReversePostOrderTraversal RPOT(Entry); std::set SortedBasicBlocksSet; std::vector SortedBasicBlocks; @@ -1120,54 +605,57 @@ void CodeGenerator::translate(optional RawVirtualAddress) { } std::vector Unreachable; - for (BasicBlock &BB : *MainFunction) + for (BasicBlock &BB : *RootFunction) if (!SortedBasicBlocksSet.contains(&BB)) Unreachable.push_back(&BB); - auto Size = MainFunction->size(); + auto Size = RootFunction->size(); for (unsigned I = 0; I < Size; ++I) - MainFunction->begin()->removeFromParent(); + RootFunction->begin()->removeFromParent(); for (BasicBlock *BB : SortedBasicBlocks) - MainFunction->insert(MainFunction->end(), BB); + RootFunction->insert(RootFunction->end(), BB); for (BasicBlock *BB : Unreachable) - MainFunction->insert(MainFunction->end(), BB); + RootFunction->insert(RootFunction->end(), BB); } - // - // At this point we have all the code, add store false to cpu_loop_exiting in - // root - // T.advance("IR finalization", true); + + // Remove the "helpers_list" variable, whose purpose is to keep alive helpers + // who would otherwise get DCE'd away due to their linkage. + // At this point we know which ones we want, and we're OK with the dead ones + // to be dropped. + TheModule->getGlobalVariable("helpers_list")->eraseFromParent(); + + // + // Look for calls to functions that might exit and reset cpu_loop_exiting + // + auto *CpuLoopExiting = TheModule->getGlobalVariable("cpu_loop_exiting", true); auto *BoolType = CpuLoopExiting->getValueType(); - std::queue WorkList; - for (Function *Helper : CpuLoopExitingUsers) - for (User *U : Helper->users()) - WorkList.push(U); + for (BasicBlock &BB : *RootFunction) { + for (Instruction &I : BB) { + auto *Call = dyn_cast(&I); + if (Call == nullptr) + continue; - while (not WorkList.empty()) { - User *U = WorkList.front(); - WorkList.pop(); + auto *Callee = getCalledFunction(Call); + if (Callee == nullptr or not Callee->hasMetadata("revng.cpu_loop_exits")) + continue; - if (auto *CE = dyn_cast(U)) { - if (CE->isCast()) - for (User *UCE : CE->users()) - WorkList.push(UCE); - } else if (auto *Call = dyn_cast(U)) { - if (Call->getParent()->getParent() == MainFunction) { - new StoreInst(ConstantInt::getFalse(BoolType), - CpuLoopExiting, - Call->getNextNode()); - } + new StoreInst(ConstantInt::getFalse(BoolType), + CpuLoopExiting, + Call->getNextNode()); + // TODO: are we guaranteed to have a check for the PC and go back to the + // dispatcher here if there's a mismatch? } } // Add a call to the function to initialize the CPUState, if present. // This is important on x86 architecture. // We only add the call after the Linker has imported the - // initialize_env function from the helpers, because the declaration + // helper_initialize_env function from the helpers, because the declaration // imported before with importHelperFunctionDeclaration() only has // stub types and injecting the CallInst earlier would break - if (Function *InitEnv = getIRHelper("initialize_env", *TheModule)) { + if (Function *InitEnv = getIRHelper("helper_initialize_env", *TheModule)) { revng_assert(not InitEnv->getFunctionType()->isVarArg()); revng_assert(InitEnv->getFunctionType()->getNumParams() == 1); auto *CPUStateType = InitEnv->getFunctionType()->getParamType(0); @@ -1183,32 +671,26 @@ void CodeGenerator::translate(optional RawVirtualAddress) { Translator.finalizeNewPCMarkers(); T.advance("Optimize lifted IR"); - // SROA must run before InstCombine because in this way InstCombine has many - // more elementary operations to combine - legacy::PassManager PreInstCombinePM; - PreInstCombinePM.add(createSROAPass()); - PreInstCombinePM.run(*TheModule); - // InstCombine must run before CPUStateAccessAnalysis (CSAA) because, if it - // runs after it, it removes all the useful metadata attached by CSAA. legacy::FunctionPassManager InstCombinePM(&*TheModule); - InstCombinePM.add(createInstructionCombiningPass(1)); + InstCombinePM.add(createSROAPass()); + InstCombinePM.add(createInstructionCombiningPass()); + InstCombinePM.add(createDeadCodeEliminationPass()); InstCombinePM.doInitialization(); - InstCombinePM.run(*MainFunction); + InstCombinePM.run(*RootFunction); InstCombinePM.doFinalization(); legacy::PassManager PostInstCombinePM; PostInstCombinePM.add(new LoadModelWrapperPass(Model)); - PostInstCombinePM.add(new CPUStateAccessAnalysisPass(&Variables, false)); - PostInstCombinePM.add(createDeadCodeEliminationPass()); PostInstCombinePM.add(new PruneRetSuccessors); + PostInstCombinePM.add(createGlobalDCEPass()); PostInstCombinePM.run(*TheModule); T.advance("Finalize jump targets", true); JumpTargets.finalizeJumpTargets(); T.advance("Purge dead code", true); - EliminateUnreachableBlocks(*MainFunction, nullptr, false); + EliminateUnreachableBlocks(*RootFunction, nullptr, false); T.advance("Create revng.jt.reason", true); JumpTargets.createJTReasonMD(); @@ -1216,7 +698,7 @@ void CodeGenerator::translate(optional RawVirtualAddress) { T.advance("Finalization", true); ExternalJumpsHandler JumpOutHandler(*Model, JumpTargets.dispatcher(), - *MainFunction, + *RootFunction, PCH.get()); JumpOutHandler.createExternalJumpsHandler(); diff --git a/lib/Lift/CodeGenerator.h b/lib/Lift/CodeGenerator.h index 140d668cf..8b58e2b4a 100644 --- a/lib/Lift/CodeGenerator.h +++ b/lib/Lift/CodeGenerator.h @@ -10,6 +10,7 @@ #include "llvm/ADT/ArrayRef.h" +#include "revng/Lift/LibTcg.h" #include "revng/Model/Binary.h" #include "revng/Model/RawBinaryView.h" @@ -26,6 +27,8 @@ class DataLayout; }; // namespace llvm +struct LibTcgInterface; + /// Translator from binary code to LLVM IR. class CodeGenerator { public: @@ -44,7 +47,7 @@ public: /// Creates an LLVM function for the code in the specified memory area. /// /// \param VirtualAddress the address from where the translation should start. - void translate(std::optional RawVirtualAddress); + void translate(LibTcg &LibTcg, std::optional RawVirtualAddress); private: const RawBinaryView &RawBinary; @@ -54,8 +57,7 @@ private: std::unique_ptr EarlyLinkedModule; const TupleTree &Model; - unsigned OriginalInstrMDKind; - unsigned PTCInstrMDKind; + unsigned LibTcgInstrMDKind; std::string FunctionListPath; diff --git a/lib/Lift/DropHelperCallsPass.h b/lib/Lift/DropHelperCallsPass.h index ba7e22d40..33cf1cbe7 100644 --- a/lib/Lift/DropHelperCallsPass.h +++ b/lib/Lift/DropHelperCallsPass.h @@ -10,7 +10,7 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/PassManager.h" -#include "revng/Support/IRBuilder.h" +#include "revng/Model/FunctionTags.h" #include "revng/Support/IRHelpers.h" using CSVToAllocaMap = llvm::DenseMap RecordASM("record-asm", - cl::desc("create metadata for assembly"), - cl::cat(MainCategory)); +static Logger<> Log("instruction-translator"); using IT = InstructionTranslator; -namespace PTC { - -template -class InstructionImpl; - -enum ArgumentType { - In, - Out, - Const -}; - -template -using RAI = RandomAccessIterator; - -template -class InstructionArgumentsIterator - : public RAI, false> { - -public: - using base = RandomAccessIterator; - - InstructionArgumentsIterator & - operator=(const InstructionArgumentsIterator &R) { - base::operator=(R); - TheInstruction = R.TheInstruction; - return *this; - } - - InstructionArgumentsIterator(const InstructionArgumentsIterator &R) : - base(R), TheInstruction(R.TheInstruction) {} - - InstructionArgumentsIterator(const InstructionArgumentsIterator &R, - unsigned Index) : - base(Index), TheInstruction(R.TheInstruction) {} - - InstructionArgumentsIterator(PTCInstruction *TheInstruction, unsigned Index) : - base(Index), TheInstruction(TheInstruction) {} - - bool isCompatible(const InstructionArgumentsIterator &R) const { - return TheInstruction == R.TheInstruction; - } - -public: - uint64_t get(unsigned Index) const; - -private: - PTCInstruction *TheInstruction = nullptr; -}; - -template<> -inline uint64_t -InstructionArgumentsIterator::get(unsigned Index) const { - return ptc_call_instruction_in_arg(&ptc, TheInstruction, Index); +static uint64_t pc(LibTcgInstruction *Instr) { + revng_assert(Instr->opcode == LIBTCG_op_insn_start); + uint64_t PC = Instr->constant_args[0].constant; + if (Instr->nb_cargs > 1) + PC |= Instr->constant_args[1].constant << 32; + return PC; } -template<> -inline uint64_t -InstructionArgumentsIterator::get(unsigned Index) const { - return ptc_call_instruction_const_arg(&ptc, TheInstruction, Index); -} - -template<> -inline uint64_t -InstructionArgumentsIterator::get(unsigned Index) const { - return ptc_call_instruction_out_arg(&ptc, TheInstruction, Index); -} - -template<> -inline uint64_t -InstructionArgumentsIterator::get(unsigned Index) const { - return ptc_instruction_in_arg(&ptc, TheInstruction, Index); -} - -template<> -inline uint64_t -InstructionArgumentsIterator::get(unsigned Index) const { - return ptc_instruction_const_arg(&ptc, TheInstruction, Index); -} - -template<> -inline uint64_t -InstructionArgumentsIterator::get(unsigned Index) const { - return ptc_instruction_out_arg(&ptc, TheInstruction, Index); -} - -template -class InstructionImpl { -private: - template - using arguments = InstructionArgumentsIterator; - -public: - InstructionImpl(PTCInstruction *TheInstruction) : - TheInstruction(TheInstruction), - InArguments(arguments(TheInstruction, 0), - arguments(TheInstruction, inArgCount())), - ConstArguments(arguments(TheInstruction, 0), - arguments(TheInstruction, constArgCount())), - OutArguments(arguments(TheInstruction, 0), - arguments(TheInstruction, outArgCount())) {} - - PTCOpcode opcode() const { return TheInstruction->opc; } - - std::string helperName() const { - revng_assert(IsCall); - PTCHelperDef *Helper = ptc_find_helper(&ptc, ConstArguments[0]); - revng_assert(Helper != nullptr && Helper->name != nullptr); - return std::string(Helper->name); - } - - uint64_t pc() const { - revng_assert(opcode() == PTC_INSTRUCTION_op_debug_insn_start); - uint64_t PC = ConstArguments[0]; - if (ConstArguments.size() > 1) - PC |= ConstArguments[1] << 32; - return PC; - } - -private: - PTCInstruction *TheInstruction = nullptr; - -public: - const Range> InArguments; - const Range> ConstArguments; - const Range> OutArguments; - -private: - unsigned inArgCount() const; - unsigned constArgCount() const; - unsigned outArgCount() const; -}; - -using Instruction = InstructionImpl; -using CallInstruction = InstructionImpl; - -template<> -inline unsigned CallInstruction::inArgCount() const { - return ptc_call_instruction_in_arg_count(&ptc, TheInstruction); -} - -template<> -inline unsigned Instruction::inArgCount() const { - return ptc_instruction_in_arg_count(&ptc, TheInstruction); -} - -template<> -inline unsigned CallInstruction::constArgCount() const { - return ptc_call_instruction_const_arg_count(&ptc, TheInstruction); -} - -template<> -inline unsigned Instruction::constArgCount() const { - return ptc_instruction_const_arg_count(&ptc, TheInstruction); -} - -template<> -inline unsigned CallInstruction::outArgCount() const { - return ptc_call_instruction_out_arg_count(&ptc, TheInstruction); -} - -template<> -inline unsigned Instruction::outArgCount() const { - return ptc_instruction_out_arg_count(&ptc, TheInstruction); -} - -} // namespace PTC - -/// Converts a PTC condition into an LLVM predicate +/// Converts a libtcg condition into an LLVM predicate /// -/// \param Condition the input PTC condition. +/// \param Condition the input libtcg condition. /// /// \return the corresponding LLVM predicate. -static CmpInst::Predicate conditionToPredicate(PTCCondition Condition) { + +static CmpInst::Predicate conditionToPredicate(LibTcgCond Condition) { switch (Condition) { - case PTC_COND_NEVER: - // TODO: this is probably wrong - return CmpInst::FCMP_FALSE; - case PTC_COND_ALWAYS: - // TODO: this is probably wrong - return CmpInst::FCMP_TRUE; - case PTC_COND_EQ: + case LIBTCG_COND_EQ: return CmpInst::ICMP_EQ; - case PTC_COND_NE: + case LIBTCG_COND_NE: return CmpInst::ICMP_NE; - case PTC_COND_LT: + case LIBTCG_COND_LT: return CmpInst::ICMP_SLT; - case PTC_COND_GE: + case LIBTCG_COND_GE: return CmpInst::ICMP_SGE; - case PTC_COND_LE: + case LIBTCG_COND_LE: return CmpInst::ICMP_SLE; - case PTC_COND_GT: + case LIBTCG_COND_GT: return CmpInst::ICMP_SGT; - case PTC_COND_LTU: + case LIBTCG_COND_LTU: return CmpInst::ICMP_ULT; - case PTC_COND_GEU: + case LIBTCG_COND_GEU: return CmpInst::ICMP_UGE; - case PTC_COND_LEU: + case LIBTCG_COND_LEU: return CmpInst::ICMP_ULE; - case PTC_COND_GTU: + case LIBTCG_COND_GTU: return CmpInst::ICMP_UGT; default: - revng_unreachable("Unknown comparison operator"); + revng_abort("Unknown libtcg condition"); } } -/// Obtains the LLVM binary operation corresponding to the specified PTC opcode. +/// Obtains the LLVM binary operation corresponding to the specified libtcg +/// opcode. /// -/// \param Opcode the PTC opcode. +/// \param Opcode the libtcg opcode. /// /// \return the LLVM binary operation matching opcode. -static Instruction::BinaryOps opcodeToBinaryOp(PTCOpcode Opcode) { +static Instruction::BinaryOps opcodeToBinaryOp(LibTcgOpcode Opcode) { switch (Opcode) { - case PTC_INSTRUCTION_op_add_i32: - case PTC_INSTRUCTION_op_add_i64: - case PTC_INSTRUCTION_op_add2_i32: - case PTC_INSTRUCTION_op_add2_i64: + case LIBTCG_op_add_i32: + case LIBTCG_op_add_i64: + case LIBTCG_op_add2_i32: + case LIBTCG_op_add2_i64: return Instruction::Add; - case PTC_INSTRUCTION_op_sub_i32: - case PTC_INSTRUCTION_op_sub_i64: - case PTC_INSTRUCTION_op_sub2_i32: - case PTC_INSTRUCTION_op_sub2_i64: + case LIBTCG_op_sub_i32: + case LIBTCG_op_sub_i64: + case LIBTCG_op_sub2_i32: + case LIBTCG_op_sub2_i64: return Instruction::Sub; - case PTC_INSTRUCTION_op_mul_i32: - case PTC_INSTRUCTION_op_mul_i64: + case LIBTCG_op_mul_i32: + case LIBTCG_op_mul_i64: return Instruction::Mul; - case PTC_INSTRUCTION_op_div_i32: - case PTC_INSTRUCTION_op_div_i64: + case LIBTCG_op_div_i32: + case LIBTCG_op_div_i64: return Instruction::SDiv; - case PTC_INSTRUCTION_op_divu_i32: - case PTC_INSTRUCTION_op_divu_i64: + case LIBTCG_op_divu_i32: + case LIBTCG_op_divu_i64: return Instruction::UDiv; - case PTC_INSTRUCTION_op_rem_i32: - case PTC_INSTRUCTION_op_rem_i64: + case LIBTCG_op_rem_i32: + case LIBTCG_op_rem_i64: return Instruction::SRem; - case PTC_INSTRUCTION_op_remu_i32: - case PTC_INSTRUCTION_op_remu_i64: + case LIBTCG_op_remu_i32: + case LIBTCG_op_remu_i64: return Instruction::URem; - case PTC_INSTRUCTION_op_and_i32: - case PTC_INSTRUCTION_op_and_i64: + case LIBTCG_op_and_i32: + case LIBTCG_op_and_i64: return Instruction::And; - case PTC_INSTRUCTION_op_or_i32: - case PTC_INSTRUCTION_op_or_i64: + case LIBTCG_op_or_i32: + case LIBTCG_op_or_i64: return Instruction::Or; - case PTC_INSTRUCTION_op_xor_i32: - case PTC_INSTRUCTION_op_xor_i64: + case LIBTCG_op_xor_i32: + case LIBTCG_op_xor_i64: return Instruction::Xor; - case PTC_INSTRUCTION_op_shl_i32: - case PTC_INSTRUCTION_op_shl_i64: + case LIBTCG_op_shl_i32: + case LIBTCG_op_shl_i64: return Instruction::Shl; - case PTC_INSTRUCTION_op_shr_i32: - case PTC_INSTRUCTION_op_shr_i64: + case LIBTCG_op_shr_i32: + case LIBTCG_op_shr_i64: return Instruction::LShr; - case PTC_INSTRUCTION_op_sar_i32: - case PTC_INSTRUCTION_op_sar_i64: + case LIBTCG_op_sar_i32: + case LIBTCG_op_sar_i64: return Instruction::AShr; default: - revng_unreachable("PTC opcode is not a binary operator"); + revng_unreachable("libtcg opcode is not a binary operator"); } } @@ -311,168 +147,224 @@ static uint64_t getMaxValue(unsigned Bits) { else if (Bits == 64) return 0xffffffffffffffff; else - revng_unreachable("Not the number of bits in a integer type"); + revng_unreachable("Not the number of bits in an integer type"); } /// Maps an opcode the corresponding input and output register size. /// /// \return the size, in bits, of the registers used by the opcode. -static unsigned getRegisterSize(unsigned Opcode) { +static unsigned getRegisterSize(LibTcg &LibTcg, LibTcgOpcode Opcode) { switch (Opcode) { - case PTC_INSTRUCTION_op_add2_i32: - case PTC_INSTRUCTION_op_add_i32: - case PTC_INSTRUCTION_op_andc_i32: - case PTC_INSTRUCTION_op_and_i32: - case PTC_INSTRUCTION_op_brcond2_i32: - case PTC_INSTRUCTION_op_brcond_i32: - case PTC_INSTRUCTION_op_bswap16_i32: - case PTC_INSTRUCTION_op_bswap32_i32: - case PTC_INSTRUCTION_op_deposit_i32: - case PTC_INSTRUCTION_op_div2_i32: - case PTC_INSTRUCTION_op_div_i32: - case PTC_INSTRUCTION_op_divu2_i32: - case PTC_INSTRUCTION_op_divu_i32: - case PTC_INSTRUCTION_op_eqv_i32: - case PTC_INSTRUCTION_op_ext16s_i32: - case PTC_INSTRUCTION_op_ext16u_i32: - case PTC_INSTRUCTION_op_ext8s_i32: - case PTC_INSTRUCTION_op_ext8u_i32: - case PTC_INSTRUCTION_op_ld16s_i32: - case PTC_INSTRUCTION_op_ld16u_i32: - case PTC_INSTRUCTION_op_ld8s_i32: - case PTC_INSTRUCTION_op_ld8u_i32: - case PTC_INSTRUCTION_op_ld_i32: - case PTC_INSTRUCTION_op_movcond_i32: - case PTC_INSTRUCTION_op_mov_i32: - case PTC_INSTRUCTION_op_movi_i32: - case PTC_INSTRUCTION_op_mul_i32: - case PTC_INSTRUCTION_op_muls2_i32: - case PTC_INSTRUCTION_op_mulsh_i32: - case PTC_INSTRUCTION_op_mulu2_i32: - case PTC_INSTRUCTION_op_muluh_i32: - case PTC_INSTRUCTION_op_nand_i32: - case PTC_INSTRUCTION_op_neg_i32: - case PTC_INSTRUCTION_op_nor_i32: - case PTC_INSTRUCTION_op_not_i32: - case PTC_INSTRUCTION_op_orc_i32: - case PTC_INSTRUCTION_op_or_i32: - case PTC_INSTRUCTION_op_qemu_ld_i32: - case PTC_INSTRUCTION_op_qemu_st_i32: - case PTC_INSTRUCTION_op_rem_i32: - case PTC_INSTRUCTION_op_remu_i32: - case PTC_INSTRUCTION_op_rotl_i32: - case PTC_INSTRUCTION_op_rotr_i32: - case PTC_INSTRUCTION_op_sar_i32: - case PTC_INSTRUCTION_op_setcond2_i32: - case PTC_INSTRUCTION_op_setcond_i32: - case PTC_INSTRUCTION_op_shl_i32: - case PTC_INSTRUCTION_op_shr_i32: - case PTC_INSTRUCTION_op_st16_i32: - case PTC_INSTRUCTION_op_st8_i32: - case PTC_INSTRUCTION_op_st_i32: - case PTC_INSTRUCTION_op_sub2_i32: - case PTC_INSTRUCTION_op_sub_i32: - case PTC_INSTRUCTION_op_trunc_shr_i32: - case PTC_INSTRUCTION_op_xor_i32: + case LIBTCG_op_add2_i32: + case LIBTCG_op_add_i32: + case LIBTCG_op_andc_i32: + case LIBTCG_op_and_i32: + case LIBTCG_op_brcond2_i32: + case LIBTCG_op_brcond_i32: + case LIBTCG_op_bswap16_i32: + case LIBTCG_op_bswap32_i32: + case LIBTCG_op_deposit_i32: + case LIBTCG_op_div2_i32: + case LIBTCG_op_div_i32: + case LIBTCG_op_divu2_i32: + case LIBTCG_op_divu_i32: + case LIBTCG_op_eqv_i32: + case LIBTCG_op_ext16s_i32: + case LIBTCG_op_ext16u_i32: + case LIBTCG_op_ext8s_i32: + case LIBTCG_op_ext8u_i32: + case LIBTCG_op_extrl_i64_i32: + case LIBTCG_op_extrh_i64_i32: + case LIBTCG_op_ld16s_i32: + case LIBTCG_op_ld16u_i32: + case LIBTCG_op_ld8s_i32: + case LIBTCG_op_ld8u_i32: + case LIBTCG_op_ld_i32: + case LIBTCG_op_movcond_i32: + case LIBTCG_op_mov_i32: + case LIBTCG_op_mul_i32: + case LIBTCG_op_muls2_i32: + case LIBTCG_op_mulsh_i32: + case LIBTCG_op_mulu2_i32: + case LIBTCG_op_muluh_i32: + case LIBTCG_op_nand_i32: + case LIBTCG_op_neg_i32: + case LIBTCG_op_nor_i32: + case LIBTCG_op_not_i32: + case LIBTCG_op_orc_i32: + case LIBTCG_op_or_i32: + case LIBTCG_op_qemu_ld_a32_i32: + case LIBTCG_op_qemu_ld_a64_i32: + case LIBTCG_op_qemu_st_a32_i32: + case LIBTCG_op_qemu_st_a64_i32: + case LIBTCG_op_rem_i32: + case LIBTCG_op_remu_i32: + case LIBTCG_op_rotl_i32: + case LIBTCG_op_rotr_i32: + case LIBTCG_op_sar_i32: + case LIBTCG_op_setcond2_i32: + case LIBTCG_op_setcond_i32: + case LIBTCG_op_negsetcond_i32: + case LIBTCG_op_shl_i32: + case LIBTCG_op_shr_i32: + case LIBTCG_op_st16_i32: + case LIBTCG_op_st8_i32: + case LIBTCG_op_st_i32: + case LIBTCG_op_sub2_i32: + case LIBTCG_op_sub_i32: + case LIBTCG_op_xor_i32: + case LIBTCG_op_extract_i32: + case LIBTCG_op_sextract_i32: + case LIBTCG_op_extract2_i32: + case LIBTCG_op_clz_i32: + case LIBTCG_op_ctz_i32: return 32; - case PTC_INSTRUCTION_op_add2_i64: - case PTC_INSTRUCTION_op_add_i64: - case PTC_INSTRUCTION_op_andc_i64: - case PTC_INSTRUCTION_op_and_i64: - case PTC_INSTRUCTION_op_brcond_i64: - case PTC_INSTRUCTION_op_bswap16_i64: - case PTC_INSTRUCTION_op_bswap32_i64: - case PTC_INSTRUCTION_op_bswap64_i64: - case PTC_INSTRUCTION_op_deposit_i64: - case PTC_INSTRUCTION_op_div2_i64: - case PTC_INSTRUCTION_op_div_i64: - case PTC_INSTRUCTION_op_divu2_i64: - case PTC_INSTRUCTION_op_divu_i64: - case PTC_INSTRUCTION_op_eqv_i64: - case PTC_INSTRUCTION_op_ext16s_i64: - case PTC_INSTRUCTION_op_ext16u_i64: - case PTC_INSTRUCTION_op_ext32s_i64: - case PTC_INSTRUCTION_op_ext32u_i64: - case PTC_INSTRUCTION_op_ext8s_i64: - case PTC_INSTRUCTION_op_ext8u_i64: - case PTC_INSTRUCTION_op_ld16s_i64: - case PTC_INSTRUCTION_op_ld16u_i64: - case PTC_INSTRUCTION_op_ld32s_i64: - case PTC_INSTRUCTION_op_ld32u_i64: - case PTC_INSTRUCTION_op_ld8s_i64: - case PTC_INSTRUCTION_op_ld8u_i64: - case PTC_INSTRUCTION_op_ld_i64: - case PTC_INSTRUCTION_op_movcond_i64: - case PTC_INSTRUCTION_op_mov_i64: - case PTC_INSTRUCTION_op_movi_i64: - case PTC_INSTRUCTION_op_mul_i64: - case PTC_INSTRUCTION_op_muls2_i64: - case PTC_INSTRUCTION_op_mulsh_i64: - case PTC_INSTRUCTION_op_mulu2_i64: - case PTC_INSTRUCTION_op_muluh_i64: - case PTC_INSTRUCTION_op_nand_i64: - case PTC_INSTRUCTION_op_neg_i64: - case PTC_INSTRUCTION_op_nor_i64: - case PTC_INSTRUCTION_op_not_i64: - case PTC_INSTRUCTION_op_orc_i64: - case PTC_INSTRUCTION_op_or_i64: - case PTC_INSTRUCTION_op_qemu_ld_i64: - case PTC_INSTRUCTION_op_qemu_st_i64: - case PTC_INSTRUCTION_op_rem_i64: - case PTC_INSTRUCTION_op_remu_i64: - case PTC_INSTRUCTION_op_rotl_i64: - case PTC_INSTRUCTION_op_rotr_i64: - case PTC_INSTRUCTION_op_sar_i64: - case PTC_INSTRUCTION_op_setcond_i64: - case PTC_INSTRUCTION_op_shl_i64: - case PTC_INSTRUCTION_op_shr_i64: - case PTC_INSTRUCTION_op_st16_i64: - case PTC_INSTRUCTION_op_st32_i64: - case PTC_INSTRUCTION_op_st8_i64: - case PTC_INSTRUCTION_op_st_i64: - case PTC_INSTRUCTION_op_sub2_i64: - case PTC_INSTRUCTION_op_sub_i64: - case PTC_INSTRUCTION_op_xor_i64: + case LIBTCG_op_add2_i64: + case LIBTCG_op_add_i64: + case LIBTCG_op_andc_i64: + case LIBTCG_op_and_i64: + case LIBTCG_op_brcond_i64: + case LIBTCG_op_bswap16_i64: + case LIBTCG_op_bswap32_i64: + case LIBTCG_op_bswap64_i64: + case LIBTCG_op_deposit_i64: + case LIBTCG_op_div2_i64: + case LIBTCG_op_div_i64: + case LIBTCG_op_divu2_i64: + case LIBTCG_op_divu_i64: + case LIBTCG_op_eqv_i64: + case LIBTCG_op_ext16s_i64: + case LIBTCG_op_ext16u_i64: + case LIBTCG_op_ext_i32_i64: + case LIBTCG_op_extu_i32_i64: + case LIBTCG_op_ext32s_i64: + case LIBTCG_op_ext32u_i64: + case LIBTCG_op_ext8s_i64: + case LIBTCG_op_ext8u_i64: + case LIBTCG_op_ld16s_i64: + case LIBTCG_op_ld16u_i64: + case LIBTCG_op_ld32s_i64: + case LIBTCG_op_ld32u_i64: + case LIBTCG_op_ld8s_i64: + case LIBTCG_op_ld8u_i64: + case LIBTCG_op_ld_i64: + case LIBTCG_op_movcond_i64: + case LIBTCG_op_mov_i64: + case LIBTCG_op_mul_i64: + case LIBTCG_op_muls2_i64: + case LIBTCG_op_mulsh_i64: + case LIBTCG_op_mulu2_i64: + case LIBTCG_op_muluh_i64: + case LIBTCG_op_nand_i64: + case LIBTCG_op_neg_i64: + case LIBTCG_op_nor_i64: + case LIBTCG_op_not_i64: + case LIBTCG_op_orc_i64: + case LIBTCG_op_or_i64: + case LIBTCG_op_qemu_ld_a32_i64: + case LIBTCG_op_qemu_ld_a64_i64: + case LIBTCG_op_qemu_st_a32_i64: + case LIBTCG_op_qemu_st_a64_i64: + case LIBTCG_op_rem_i64: + case LIBTCG_op_remu_i64: + case LIBTCG_op_rotl_i64: + case LIBTCG_op_rotr_i64: + case LIBTCG_op_sar_i64: + case LIBTCG_op_setcond_i64: + case LIBTCG_op_negsetcond_i64: + case LIBTCG_op_shl_i64: + case LIBTCG_op_shr_i64: + case LIBTCG_op_st16_i64: + case LIBTCG_op_st32_i64: + case LIBTCG_op_st8_i64: + case LIBTCG_op_st_i64: + case LIBTCG_op_sub2_i64: + case LIBTCG_op_sub_i64: + case LIBTCG_op_xor_i64: + case LIBTCG_op_clz_i64: + case LIBTCG_op_ctz_i64: + case LIBTCG_op_extract2_i64: + case LIBTCG_op_extract_i64: + case LIBTCG_op_sextract_i64: return 64; - case PTC_INSTRUCTION_op_br: - case PTC_INSTRUCTION_op_call: - case PTC_INSTRUCTION_op_debug_insn_start: - case PTC_INSTRUCTION_op_discard: - case PTC_INSTRUCTION_op_exit_tb: - case PTC_INSTRUCTION_op_goto_tb: - case PTC_INSTRUCTION_op_set_label: + case LIBTCG_op_br: + case LIBTCG_op_call: + case LIBTCG_op_insn_start: + case LIBTCG_op_discard: + case LIBTCG_op_exit_tb: + case LIBTCG_op_goto_tb: + case LIBTCG_op_goto_ptr: + case LIBTCG_op_set_label: return 0; - default: - revng_unreachable("Unexpected opcode"); + default: { + // For debugging purposes printing the actual opcode + // really helps. + std::stringstream ErrSS; + ErrSS << "Unexpected libtcg opcode [" << Opcode + << "]: " << LibTcg.instructionName(Opcode); + revng_unreachable(ErrSS.str().c_str()); } + } +} + +static Value *genDeposit(revng::IRBuilder &Builder, + unsigned RegisterSize, + Value *Into, + Value *From, + Value *Offset, + Value *Length) { + revng_assert(isa(Offset)); + uint64_t ConstOffset = cast(Offset)->getLimitedValue(); + if (ConstOffset == RegisterSize) + return Into; + + revng_assert(isa(Length)); + uint64_t ConstLength = cast(Length)->getLimitedValue(); + + uint64_t Bits = 0; + // Thou shall not << 32 + if (ConstLength == RegisterSize) + Bits = getMaxValue(RegisterSize); + else + Bits = (1UL << ConstLength) - 1; + + // result = (t1 & ~(bits << position)) | ((t2 & bits) << position) + uint64_t BaseMask = ~(Bits << ConstOffset); + Value *MaskedBase = Builder.CreateAnd(Into, BaseMask); + Value *Deposit = Builder.CreateAnd(From, Bits); + Value *ShiftedDeposit = Builder.CreateShl(Deposit, ConstOffset); + Value *Result = Builder.CreateOr(MaskedBase, ShiftedDeposit); + + return Result; } /// Create a compare instruction given a comparison operator and the operands /// /// \param Builder the builder to use to create the instruction. -/// \param RawCondition the PTC condition. +/// \param Condition the libtcg condition. /// \param FirstOperand the first operand of the comparison. /// \param SecondOperand the second operand of the comparison. /// /// \return a compare instruction. template static Value *createICmp(T &Builder, - uint64_t RawCondition, + LibTcgCond Condition, Value *FirstOperand, Value *SecondOperand) { - PTCCondition Condition = static_cast(RawCondition); return Builder.CreateICmp(conditionToPredicate(Condition), FirstOperand, SecondOperand); } using LBM = IT::LabeledBlocksMap; -IT::InstructionTranslator(revng::IRBuilder &Builder, +IT::InstructionTranslator(class LibTcg &LibTcg, + revng::IRBuilder &Builder, VariableManager &Variables, JumpTargetManager &JumpTargets, std::vector Blocks, bool EndianessMismatch, ProgramCounterHandler *PCH) : + LibTcg(LibTcg), Builder(Builder), Variables(Variables), JumpTargets(JumpTargets), @@ -499,7 +391,6 @@ IT::InstructionTranslator(revng::IRBuilder &Builder, Type::getInt64Ty(Context), Type::getInt32Ty(Context), Type::getInt32Ty(Context), - Type::getInt8PtrTy(Context), Type::getInt8PtrTy(Context) }, true); NewPCMarker = createIRHelper("newpc", @@ -552,34 +443,32 @@ void IT::finalizeNewPCMarkers() { eraseFromParent(Call); } -SmallSet IT::preprocess(PTCInstructionList *InstructionList) { +SmallSet IT::preprocess(const LibTcgTranslationBlock &TB) { SmallSet Result; - for (unsigned I = 0; I < InstructionList->instruction_count; I++) { - PTCInstruction &Instruction = InstructionList->instructions[I]; - switch (Instruction.opc) { - case PTC_INSTRUCTION_op_movi_i32: - case PTC_INSTRUCTION_op_movi_i64: - case PTC_INSTRUCTION_op_mov_i32: - case PTC_INSTRUCTION_op_mov_i64: + for (unsigned I = 0; I < TB.instruction_count; ++I) { + LibTcgInstruction &Instruction = TB.list[I]; + switch (Instruction.opcode) { + case LIBTCG_op_mov_i32: + case LIBTCG_op_mov_i64: break; default: continue; } - const PTC::Instruction TheInstruction(&Instruction); - unsigned OutArg = TheInstruction.OutArguments[0]; - PTCTemp *Temporary = ptc_temp_get(InstructionList, OutArg); + LibTcgArgument Argument = Instruction.output_args[0]; + revng_assert(Argument.kind == LIBTCG_ARG_TEMP); + LibTcgTemp *Temp = Argument.temp; - if (!ptc_temp_is_global(InstructionList, OutArg)) + if (Temp->kind != LIBTCG_TEMP_GLOBAL) continue; - if (0 != strcmp("btarget", Temporary->name)) + if (strcmp("btarget", Temp->name) != 0) continue; - for (unsigned J = I + 1; J < InstructionList->instruction_count; J++) { - unsigned Opcode = InstructionList->instructions[J].opc; - if (Opcode == PTC_INSTRUCTION_op_debug_insn_start) + for (unsigned J = I + 1; J < TB.instruction_count; ++J) { + LibTcgOpcode Opcode = TB.list[J].opcode; + if (Opcode == LIBTCG_op_insn_start) Result.insert(J); } @@ -591,81 +480,51 @@ SmallSet IT::preprocess(PTCInstructionList *InstructionList) { CallInst *IT::emitNewPCCall(revng::IRBuilder &Builder, MetaAddress PC, - uint64_t Size, - Value *String) const { + uint64_t Size) const { PointerType *Int8PtrTy = getStringPtrType(TheModule.getContext()); auto *Int8NullPtr = ConstantPointerNull::get(Int8PtrTy); std::vector Args = { BasicBlockID(PC).toValue(&TheModule), Builder.getInt64(Size), Builder.getInt32(-1), Builder.getInt32(0), - String != nullptr ? String : Int8NullPtr, Int8NullPtr }; - // Insert a call to NewPCMarker capturing all the local temporaries - // This prevents SROA from transforming them in SSA values, which is bad - // in case we have to split a basic block - for (AllocaInst *Local : Variables.locals()) - Args.push_back(Local); + // Insert a call to NewPCMarker capturing all the currently live temporaries + // which might be alive across an instruction boundary. This prevents SROA + // from transforming them in SSA values, which is bad in case we have to + // split a basic block + for (AllocaInst *V : Variables.getLiveVariables()) + Args.push_back(V); return Builder.CreateCall(NewPCMarker, Args); } -std::tuple -IT::newInstruction(PTCInstruction *Instr, - PTCInstruction *Next, +std::tuple +IT::newInstruction(LibTcgInstruction *Instr, + LibTcgInstruction *Next, MetaAddress StartPC, MetaAddress EndPC, - bool IsFirst, - MetaAddress AbortAt) { - using R = std::tuple; + bool IsFirst) { + using R = std::tuple; revng_assert(Instr != nullptr); LLVMContext &Context = TheModule.getContext(); - const PTC::Instruction TheInstruction(Instr); // A new original instruction, let's create a new metadata node // referencing it for all the next instructions to come - MetaAddress PC = StartPC.replaceAddress(TheInstruction.pc()); + MetaAddress PC = StartPC.replaceAddress(pc(Instr)); // Prevent translation of non-executable code if (not JumpTargets.isExecutableAddress(PC)) - return R{ Abort, nullptr, MetaAddress::invalid(), MetaAddress::invalid() }; + return R{ Abort, MetaAddress::invalid(), MetaAddress::invalid() }; // Compute NextPC MetaAddress NextPC = MetaAddress::invalid(); if (Next != nullptr) - NextPC = StartPC.replaceAddress(PTC::Instruction(Next).pc()); + NextPC = StartPC.replaceAddress(pc(Next)); else NextPC = EndPC; - PointerType *Int8PtrTy = getStringPtrType(Context); - - if (AbortAt.isValid() and NextPC.addressGreaterThan(AbortAt)) { - emitNewPCCall(Builder, PC, 1, ConstantPointerNull::get(Int8PtrTy)); - return R{ Abort, nullptr, MetaAddress::invalid(), MetaAddress::invalid() }; - } - - MDNode *MDOriginalInstr = nullptr; - Constant *String = nullptr; - if (RecordASM) { - std::stringstream OriginalStringStream; - revng_assert(NextPC - PC); - disassemble(OriginalStringStream, PC, *(NextPC - PC)); - std::string OriginalString = OriginalStringStream.str(); - - // We don't deduplicate this string since performing a lookup each time is - // increasingly expensive and we should have relatively few collisions - std::string AddressName = JumpTargets.nameForAddress(PC); - String = getUniqueString(&TheModule, OriginalString); - - auto *MDOriginalString = ConstantAsMetadata::get(String); - auto *MDPC = ConstantAsMetadata::get(PC.toValue(&TheModule)); - MDOriginalInstr = MDNode::get(Context, { MDOriginalString, MDPC }); - } else { - String = ConstantPointerNull::get(Int8PtrTy); - } - if (!IsFirst) { // Check if this PC already has a block and use it bool ShouldContinue; @@ -679,15 +538,15 @@ IT::newInstruction(PTCInstruction *Instr, Builder.SetInsertPoint(DivergeTo); } else { // The block contains already translated code, early exit - return R{ Stop, MDOriginalInstr, PC, NextPC }; + return R{ Stop, PC, NextPC }; } } } - Variables.newBasicBlock(); + // Variables.newBasicBlock(); revng_assert(NextPC - PC); - auto *Call = emitNewPCCall(Builder, PC, *(NextPC - PC), String); + auto *Call = emitNewPCCall(Builder, PC, *(NextPC - PC)); if (!IsFirst) { // Inform the JumpTargetManager about the new PC we met @@ -698,16 +557,16 @@ IT::newInstruction(PTCInstruction *Instr, JumpTargets.registerInstruction(PC, Call); } - return R{ Success, MDOriginalInstr, PC, NextPC }; + return R{ Success, PC, NextPC }; } -IT::TranslationResult IT::translateCall(PTCInstruction *Instr) { - const PTC::CallInstruction TheCall(Instr); - +IT::TranslationResult IT::translateCall(LibTcgInstruction *Instruction, + MetaAddress PC, + unsigned SinceInstructionStart) { std::vector InArgs; - for (uint64_t TemporaryId : TheCall.InArguments) { - auto *Load = Variables.load(Builder, TemporaryId); + for (uint8_t I = 0; I < Instruction->nb_iargs; ++I) { + auto *Load = Variables.load(Builder, &Instruction->input_args[I]); if (Load == nullptr) return Abort; InArgs.push_back(Load); @@ -717,85 +576,139 @@ IT::TranslationResult IT::translateCall(PTCInstruction *Instr) { auto ValueTypes = llvm::map_range(InArgs, GetValueType); std::vector InArgsType(ValueTypes.begin(), ValueTypes.end()); - // TODO: handle multiple return arguments - revng_assert(TheCall.OutArguments.size() <= 1); + LibTcgHelperInfo Info = LibTcg.helperInfo(Instruction); + std::string HelperName = "helper_" + std::string(Info.func_name); + Function *Helper = TheModule.getFunction(HelperName); + revng_assert(Helper != nullptr); - Value *ResultDestination = nullptr; - Type *ResultType = nullptr; + FunctionTags::Helper.addTo(Helper); - if (TheCall.OutArguments.size() != 0) { - ResultDestination = Variables.getOrCreate(TheCall.OutArguments[0]); - if (ResultDestination == nullptr) - return Abort; - ResultType = getVariableType(ResultDestination); - } else { - ResultType = Builder.getVoidTy(); + // Emit a call to the helper + FunctionType *HelperType = Helper->getFunctionType(); + for (unsigned I = 0; I < InArgs.size(); ++I) { + Type *FormalArgumentType = HelperType->getFunctionParamType(I); + InArgs[I] = Builder.CreateBitOrPointerCast(InArgs[I], FormalArgumentType); + } + CallInst *Result = Builder.CreateCall(Helper, InArgs); + + // Handle return values and perform sanity checks + Type *ReturnType = HelperType->getReturnType(); + switch (Instruction->nb_oargs) { + case 0: + revng_assert(ReturnType->isVoidTy()); + break; + + case 1: { + revng_assert(ReturnType->isIntegerTy() or ReturnType->isPointerTy()); + Value *ResultDestination = Variables + .getOrCreate(&Instruction->output_args[0]); + revng_assert(ResultDestination != nullptr); + Builder.CreateStore(Result, ResultDestination); + } break; + + default: { + auto *ReturnStruct = cast(ReturnType); + revng_assert(ReturnStruct->getNumElements() == Instruction->nb_oargs); + + for (unsigned I = 0; I < Instruction->nb_oargs; ++I) { + Value *ResultDestination = Variables + .getOrCreate(&Instruction->output_args[I]); + revng_assert(ResultDestination != nullptr); + Builder.CreateStore(Builder.CreateExtractValue(Result, I), + ResultDestination); + } + } break; } - auto *CalleeType = FunctionType::get(ResultType, - ArrayRef(InArgsType), - false); - - std::string HelperName = "helper_" + TheCall.helperName(); - FunctionCallee FDecl = TheModule.getOrInsertFunction(HelperName, CalleeType); - - FunctionTags::Helper.addTo(cast(skipCasts(FDecl.getCallee()))); - - CallInst *Result = Builder.CreateCall(FDecl, InArgs); - - if (TheCall.OutArguments.size() != 0) - Builder.CreateStore(Result, ResultDestination); + if (Info.func_flags & LIBTCG_CALL_NO_RETURN) { + handleExitTB(); + } return Success; } -IT::TranslationResult -IT::translate(PTCInstruction *Instr, MetaAddress PC, MetaAddress NextPC) { - const PTC::Instruction TheInstruction(Instr); - +IT::TranslationResult IT::translate(LibTcgInstruction *Instr, + MetaAddress PC, + unsigned SinceInstructionStart, + MetaAddress NextPC) { std::vector InArgs; - for (uint64_t TemporaryId : TheInstruction.InArguments) { - auto *Load = Variables.load(Builder, TemporaryId); - if (Load == nullptr) + for (unsigned I = 0; I < Instr->nb_iargs; ++I) { + auto *Load = Variables.load(Builder, &Instr->input_args[I]); + + if (Load == nullptr) { + revng_log(Log, + "Aborting translation of instruction #" + << SinceInstructionStart << " in " << PC + << " due to input argument " << I << "."); return Abort; + } + InArgs.push_back(Load); } - auto ConstArgs = TheInstruction.ConstArguments; - LastPC = PC; - auto Result = translateOpcode(TheInstruction.opcode(), - ConstArgs.toVector(), - InArgs); + // TODO: constant args are not widely used. Consider accessing constant_args + // directly in translateOpcode where needed. + std::vector ConstArgs; + { + unsigned RegisterSize = getRegisterSize(LibTcg, Instr->opcode); + Type *RegisterType = nullptr; + if (RegisterSize == 32) + RegisterType = Builder.getInt32Ty(); + else if (RegisterSize == 64 or RegisterSize == 0) + RegisterType = Builder.getInt64Ty(); + else if (RegisterSize != 0) + revng_unreachable("Unexpected register size"); - // Check if there was an error while translating the instruction - if (!Result) { - // TODO: log - llvm::consumeError(Result.takeError()); - return Abort; + for (unsigned I = 0; I < Instr->nb_cargs; ++I) { + if (Instr->constant_args[I].kind == LIBTCG_ARG_CONSTANT) { + InArgs.push_back(ConstantInt::get(RegisterType, + Instr->constant_args[I].constant)); + } else { + ConstArgs.push_back(Instr->constant_args[I]); + } + } } - size_t OutSize = TheInstruction.OutArguments.size(); - revng_assert(Result->size() == OutSize); + LastPC = PC; + auto Result = translateOpcode(Instr->opcode, ConstArgs, InArgs); + + revng_assert(Result.size() == Instr->nb_oargs); // TODO: use ZipIterator here - for (unsigned I = 0; I < Result->size(); I++) { - auto *Destination = Variables.getOrCreate(TheInstruction.OutArguments[I]); - if (Destination == nullptr) - return Abort; + for (unsigned I = 0; I < Result.size(); I++) { + auto *Destination = Variables.getOrCreate(&Instr->output_args[I]); - auto *Store = Builder.CreateStore(Result.get()[I], Destination); + if (Destination == nullptr) { + revng_log(Log, + "Aborting translation of instruction #" + << SinceInstructionStart << " in " << PC + << " due to output argument " << I << "."); + return Abort; + } + + auto *Store = Builder.CreateStore(Result[I], Destination); if (PCH->affectsPC(Store)) { // This is a PC-related store PCH->handleStore(Builder, Store); - } else { - // If we're writing somewhere an immediate, register it for exploration - if (auto *Constant = dyn_cast(Store->getValueOperand())) { - MetaAddress Address = JumpTargets.fromPC(Constant->getLimitedValue()); - if (Address.isValid() and PC != Address and JumpTargets.isPC(Address) - and not JumpTargets.hasJT(Address)) { - JumpTargets.registerSimpleLiteral(Address); - } + } + + Value *StoredValue = Store->getValueOperand(); + SmallVector Constants; + if (auto *Constant = dyn_cast(StoredValue)) { + Constants.push_back(Constant); + } else if (auto *Select = dyn_cast(StoredValue)) { + if (auto *Constant = dyn_cast(Select->getTrueValue())) + Constants.push_back(Constant); + if (auto *Constant = dyn_cast(Select->getFalseValue())) + Constants.push_back(Constant); + } + + for (auto *Constant : Constants) { + MetaAddress Address = JumpTargets.fromPC(Constant->getLimitedValue()); + if (Address.isValid() and PC != Address and JumpTargets.isPC(Address) + and not JumpTargets.hasJT(Address)) { + JumpTargets.registerSimpleLiteral(Address); } } } @@ -816,12 +729,59 @@ void IT::registerDirectJumps() { ExitBlocks.clear(); } -llvm::Expected> -IT::translateOpcode(PTCOpcode Opcode, - std::vector ConstArguments, +int64_t IT::getEnvOffset(Instruction &I, int64_t Offset) const { + Value *Pointer = nullptr; + if (auto *Load = dyn_cast(&I)) + Pointer = Load->getPointerOperand(); + else if (auto *Store = dyn_cast(&I)) + Pointer = Store->getPointerOperand(); + + // Check if we're loading from env directly + if (Variables.isEnv(Pointer)) + return Offset; + + // Handle simple alloc + auto *Alloca = dyn_cast(Pointer); + revng_assert(Alloca != nullptr); + // Look for the last store there + bool AddendFound = false; + BasicBlock *Current = Builder.GetInsertBlock(); + for (Instruction &I : llvm::make_range(Current->rbegin(), Current->rend())) { + if (auto *Store = dyn_cast(&I)) { + Value *StorePointer = Store->getPointerOperand(); + + // Only accept store to allocas + revng_check(isa(StorePointer) + or isa(StorePointer)); + + // Check if we found a store targeting our alloca + if (StorePointer == Alloca) { + // Extract base and addend + auto *Add = cast(Store->getValueOperand()); + revng_check(Add->getOpcode() == llvm::Instruction::Add); + revng_check(isa(Add->getOperand(1))); + Pointer = Add->getOperand(0); + revng_assert(Variables.isEnv(Pointer)); + Offset += cast(Add->getOperand(1))->getLimitedValue(); + return Offset; + } + } else if (isa(&I)) { + // Abort in case we find a call + revng_abort(); + } else { + // Skip over instructions without side effects + } + } + + revng_abort(); +} + +std::vector +IT::translateOpcode(LibTcgOpcode Opcode, + std::vector ConstArguments, std::vector InArguments) { LLVMContext &Context = TheModule.getContext(); - unsigned RegisterSize = getRegisterSize(Opcode); + unsigned RegisterSize = getRegisterSize(LibTcg, Opcode); Type *RegisterType = nullptr; if (RegisterSize == 32) RegisterType = Builder.getInt32Ty(); @@ -830,62 +790,78 @@ IT::translateOpcode(PTCOpcode Opcode, else if (RegisterSize != 0) revng_unreachable("Unexpected register size"); - using v = std::vector; switch (Opcode) { - case PTC_INSTRUCTION_op_movi_i32: - case PTC_INSTRUCTION_op_movi_i64: - return v{ ConstantInt::get(RegisterType, ConstArguments[0]) }; - case PTC_INSTRUCTION_op_discard: + case LIBTCG_op_discard: // Let's overwrite the discarded temporary with a 0 - return v{ ConstantInt::get(RegisterType, 0) }; - case PTC_INSTRUCTION_op_mov_i32: - case PTC_INSTRUCTION_op_mov_i64: - return v{ Builder.CreateTrunc(InArguments[0], RegisterType) }; - case PTC_INSTRUCTION_op_setcond_i32: - case PTC_INSTRUCTION_op_setcond_i64: { + return { ConstantInt::get(RegisterType, 0) }; + case LIBTCG_op_mov_i32: + case LIBTCG_op_mov_i64: + if (auto *Constant = dyn_cast(InArguments[0])) { + return { Constant }; + } else { + return { Builder.CreateTrunc(InArguments[0], RegisterType) }; + } + case LIBTCG_op_setcond_i32: + case LIBTCG_op_setcond_i64: { + revng_assert(ConstArguments.size() > 0 + and ConstArguments[0].kind == LIBTCG_ARG_COND); Value *Compare = createICmp(Builder, - ConstArguments[0], + ConstArguments[0].cond, InArguments[0], InArguments[1]); - // TODO: convert single-bit registers to i1 - return v{ Builder.CreateZExt(Compare, RegisterType) }; + return { Builder.CreateZExt(Compare, RegisterType) }; } - case PTC_INSTRUCTION_op_movcond_i32: // Resist the fallthrough temptation - case PTC_INSTRUCTION_op_movcond_i64: { + case LIBTCG_op_negsetcond_i32: + case LIBTCG_op_negsetcond_i64: { + revng_assert(ConstArguments.size() > 0 + and ConstArguments[0].kind == LIBTCG_ARG_COND); Value *Compare = createICmp(Builder, - ConstArguments[0], + ConstArguments[0].cond, + InArguments[0], + InArguments[1]); + auto *Zero = ConstantInt::get(RegisterType, 0); + Value *Result = Builder.CreateZExt(Compare, RegisterType); + return { Builder.CreateSub(Zero, Result) }; + } + case LIBTCG_op_movcond_i32: // Resist the fallthrough temptation + case LIBTCG_op_movcond_i64: { + revng_assert(ConstArguments[0].kind == LIBTCG_ARG_COND); + Value *Compare = createICmp(Builder, + ConstArguments[0].cond, InArguments[0], InArguments[1]); Value *Select = Builder.CreateSelect(Compare, InArguments[2], InArguments[3]); - return v{ Select }; + return { Select }; } - case PTC_INSTRUCTION_op_qemu_ld_i32: - case PTC_INSTRUCTION_op_qemu_ld_i64: - case PTC_INSTRUCTION_op_qemu_st_i32: - case PTC_INSTRUCTION_op_qemu_st_i64: { - PTCLoadStoreArg MemoryAccess; - MemoryAccess = ptc.parse_load_store_arg(ConstArguments[0]); - - // What are we supposed to do in this case? - revng_assert(MemoryAccess.access_type != PTC_MEMORY_ACCESS_UNKNOWN); + case LIBTCG_op_qemu_ld_a32_i32: + case LIBTCG_op_qemu_ld_a64_i32: + case LIBTCG_op_qemu_ld_a32_i64: + case LIBTCG_op_qemu_ld_a64_i64: + case LIBTCG_op_qemu_st_a32_i32: + case LIBTCG_op_qemu_st_a64_i32: + case LIBTCG_op_qemu_st_a32_i64: + case LIBTCG_op_qemu_st_a64_i64: { + revng_assert(ConstArguments[0].kind == LIBTCG_ARG_MEM_OP_INDEX); + LibTcgMemOp MemoryOp = ConstArguments[0].mem_op_index.op; unsigned Alignment = 1; // Load size IntegerType *MemoryType = nullptr; - switch (ptc_get_memory_access_size(MemoryAccess.type)) { - case PTC_MO_8: + auto MemoryOpSize = static_cast(MemoryOp & LIBTCG_MO_SIZE); + switch (MemoryOpSize) { + case LIBTCG_MO_8: MemoryType = Builder.getInt8Ty(); break; - case PTC_MO_16: + case LIBTCG_MO_16: MemoryType = Builder.getInt16Ty(); break; - case PTC_MO_32: + case LIBTCG_MO_32: MemoryType = Builder.getInt32Ty(); break; - case PTC_MO_64: + case LIBTCG_MO_64: MemoryType = Builder.getInt64Ty(); break; default: @@ -901,11 +877,14 @@ IT::translateOpcode(PTCOpcode Opcode, Intrinsic::bswap, { MemoryType }); - bool SignExtend = ptc_is_sign_extended_load(MemoryAccess.type); + // Is the memory op a sign extended load? + bool SignExtend = (MemoryOp & LIBTCG_MO_SIGN) != 0; Value *Pointer = nullptr; - if (Opcode == PTC_INSTRUCTION_op_qemu_ld_i32 - || Opcode == PTC_INSTRUCTION_op_qemu_ld_i64) { + if (Opcode == LIBTCG_op_qemu_ld_a32_i32 + or Opcode == LIBTCG_op_qemu_ld_a64_i32 + or Opcode == LIBTCG_op_qemu_ld_a32_i64 + or Opcode == LIBTCG_op_qemu_ld_a64_i64) { Pointer = Builder.CreateIntToPtr(InArguments[0], MemoryType->getPointerTo()); @@ -918,12 +897,14 @@ IT::translateOpcode(PTCOpcode Opcode, Loaded = Builder.CreateCall(BSwapFunction, Load); if (SignExtend) - return v{ Builder.CreateSExt(Loaded, RegisterType) }; + return { Builder.CreateSExt(Loaded, RegisterType) }; else - return v{ Builder.CreateZExt(Loaded, RegisterType) }; + return { Builder.CreateZExt(Loaded, RegisterType) }; - } else if (Opcode == PTC_INSTRUCTION_op_qemu_st_i32 - || Opcode == PTC_INSTRUCTION_op_qemu_st_i64) { + } else if (Opcode == LIBTCG_op_qemu_st_a32_i32 + or Opcode == LIBTCG_op_qemu_st_a64_i32 + or Opcode == LIBTCG_op_qemu_st_a32_i64 + or Opcode == LIBTCG_op_qemu_st_a64_i64) { Pointer = Builder.CreateIntToPtr(InArguments[1], MemoryType->getPointerTo()); @@ -932,49 +913,53 @@ IT::translateOpcode(PTCOpcode Opcode, if (BSwapFunction != nullptr) Value = Builder.CreateCall(BSwapFunction, Value); - Builder.CreateAlignedStore(Value, Pointer, MaybeAlign(Alignment)); + auto *Store = Builder.CreateAlignedStore(Value, + Pointer, + MaybeAlign(Alignment)); - return v{}; + // If we're writing somewhere an immediate, register it for exploration + if (auto *Constant = dyn_cast(Store->getValueOperand())) { + MetaAddress Address = JumpTargets.fromPC(Constant->getLimitedValue()); + if (Address.isValid() and JumpTargets.isPC(Address) + and not JumpTargets.hasJT(Address)) { + JumpTargets.registerSimpleLiteral(Address); + } + } + + return {}; } else { revng_unreachable("Unknown load type"); } } - case PTC_INSTRUCTION_op_ld8u_i32: - case PTC_INSTRUCTION_op_ld8s_i32: - case PTC_INSTRUCTION_op_ld16u_i32: - case PTC_INSTRUCTION_op_ld16s_i32: - case PTC_INSTRUCTION_op_ld_i32: - case PTC_INSTRUCTION_op_ld8u_i64: - case PTC_INSTRUCTION_op_ld8s_i64: - case PTC_INSTRUCTION_op_ld16u_i64: - case PTC_INSTRUCTION_op_ld16s_i64: - case PTC_INSTRUCTION_op_ld32u_i64: - case PTC_INSTRUCTION_op_ld32s_i64: - case PTC_INSTRUCTION_op_ld_i64: { - Value *Base = dyn_cast(InArguments[0])->getPointerOperand(); - if (Base == nullptr || !Variables.isEnv(Base)) { - // TODO: emit warning - return llvm::createStringError(std::errc::invalid_argument, - "Invalid argument"); - } - - bool Signed; + case LIBTCG_op_ld8u_i32: + case LIBTCG_op_ld8s_i32: + case LIBTCG_op_ld16u_i32: + case LIBTCG_op_ld16s_i32: + case LIBTCG_op_ld_i32: + case LIBTCG_op_ld8u_i64: + case LIBTCG_op_ld8s_i64: + case LIBTCG_op_ld16u_i64: + case LIBTCG_op_ld16s_i64: + case LIBTCG_op_ld32u_i64: + case LIBTCG_op_ld32s_i64: + case LIBTCG_op_ld_i64: { + bool Signed = false; switch (Opcode) { - case PTC_INSTRUCTION_op_ld_i32: - case PTC_INSTRUCTION_op_ld_i64: + case LIBTCG_op_ld_i32: + case LIBTCG_op_ld_i64: - case PTC_INSTRUCTION_op_ld8u_i32: - case PTC_INSTRUCTION_op_ld16u_i32: - case PTC_INSTRUCTION_op_ld8u_i64: - case PTC_INSTRUCTION_op_ld16u_i64: - case PTC_INSTRUCTION_op_ld32u_i64: + case LIBTCG_op_ld8u_i32: + case LIBTCG_op_ld16u_i32: + case LIBTCG_op_ld8u_i64: + case LIBTCG_op_ld16u_i64: + case LIBTCG_op_ld32u_i64: Signed = false; break; - case PTC_INSTRUCTION_op_ld8s_i32: - case PTC_INSTRUCTION_op_ld16s_i32: - case PTC_INSTRUCTION_op_ld8s_i64: - case PTC_INSTRUCTION_op_ld16s_i64: - case PTC_INSTRUCTION_op_ld32s_i64: + case LIBTCG_op_ld8s_i32: + case LIBTCG_op_ld16s_i32: + case LIBTCG_op_ld8s_i64: + case LIBTCG_op_ld16s_i64: + case LIBTCG_op_ld32s_i64: Signed = true; break; default: @@ -983,129 +968,131 @@ IT::translateOpcode(PTCOpcode Opcode, unsigned LoadSize; switch (Opcode) { - case PTC_INSTRUCTION_op_ld8u_i32: - case PTC_INSTRUCTION_op_ld8s_i32: - case PTC_INSTRUCTION_op_ld8u_i64: - case PTC_INSTRUCTION_op_ld8s_i64: + case LIBTCG_op_ld8u_i32: + case LIBTCG_op_ld8s_i32: + case LIBTCG_op_ld8u_i64: + case LIBTCG_op_ld8s_i64: LoadSize = 1; break; - case PTC_INSTRUCTION_op_ld16u_i32: - case PTC_INSTRUCTION_op_ld16s_i32: - case PTC_INSTRUCTION_op_ld16u_i64: - case PTC_INSTRUCTION_op_ld16s_i64: + case LIBTCG_op_ld16u_i32: + case LIBTCG_op_ld16s_i32: + case LIBTCG_op_ld16u_i64: + case LIBTCG_op_ld16s_i64: LoadSize = 2; break; - case PTC_INSTRUCTION_op_ld_i32: - case PTC_INSTRUCTION_op_ld32u_i64: - case PTC_INSTRUCTION_op_ld32s_i64: + case LIBTCG_op_ld_i32: + case LIBTCG_op_ld32u_i64: + case LIBTCG_op_ld32s_i64: LoadSize = 4; break; - case PTC_INSTRUCTION_op_ld_i64: + case LIBTCG_op_ld_i64: LoadSize = 8; break; default: revng_unreachable("Unexpected opcode"); } - Value *Result = Variables.loadFromEnvOffset(Builder, - LoadSize, - ConstArguments[0]); + auto *Base = dyn_cast(InArguments[0]); + int64_t Offset = cast(InArguments[1])->getLimitedValue(); + Offset = getEnvOffset(*Base, Offset); + + Value *Result = Variables.loadFromEnvOffset(Builder, LoadSize, Offset); revng_assert(Result != nullptr); // Zero/sign extend in the target dimension if (Signed) - return v{ Builder.CreateSExt(Result, RegisterType) }; + return { Builder.CreateSExt(Result, RegisterType) }; else - return v{ Builder.CreateZExt(Result, RegisterType) }; + return { Builder.CreateZExt(Result, RegisterType) }; } - case PTC_INSTRUCTION_op_st8_i32: - case PTC_INSTRUCTION_op_st16_i32: - case PTC_INSTRUCTION_op_st_i32: - case PTC_INSTRUCTION_op_st8_i64: - case PTC_INSTRUCTION_op_st16_i64: - case PTC_INSTRUCTION_op_st32_i64: - case PTC_INSTRUCTION_op_st_i64: { + case LIBTCG_op_st8_i32: + case LIBTCG_op_st16_i32: + case LIBTCG_op_st_i32: + case LIBTCG_op_st8_i64: + case LIBTCG_op_st16_i64: + case LIBTCG_op_st32_i64: + case LIBTCG_op_st_i64: { unsigned StoreSize; switch (Opcode) { - case PTC_INSTRUCTION_op_st8_i32: - case PTC_INSTRUCTION_op_st8_i64: + case LIBTCG_op_st8_i32: + case LIBTCG_op_st8_i64: StoreSize = 1; break; - case PTC_INSTRUCTION_op_st16_i32: - case PTC_INSTRUCTION_op_st16_i64: + case LIBTCG_op_st16_i32: + case LIBTCG_op_st16_i64: StoreSize = 2; break; - case PTC_INSTRUCTION_op_st_i32: - case PTC_INSTRUCTION_op_st32_i64: + case LIBTCG_op_st_i32: + case LIBTCG_op_st32_i64: StoreSize = 4; break; - case PTC_INSTRUCTION_op_st_i64: + case LIBTCG_op_st_i64: StoreSize = 8; break; default: revng_unreachable("Unexpected opcode"); } - Value *Base = dyn_cast(InArguments[1])->getPointerOperand(); - if (Base == nullptr || !Variables.isEnv(Base)) { - // TODO: emit warning - return llvm::createStringError(std::errc::invalid_argument, - "Invalid argument"); - } + // For host stores, right now we handle a couple of simple situations. + // TODO: the more appropriate thing to do would be to leave these as memory + // accesses relative to env, eventually run SROA and *then* promote + // them to CSV accesses. + auto *Load = cast(InArguments[1]); + int64_t Offset = cast(InArguments[2])->getLimitedValue(); + Offset = getEnvOffset(*Load, Offset); + revng_assert(isa(InArguments[2])); auto Result = Variables.storeToEnvOffset(Builder, StoreSize, - ConstArguments[0], + Offset, InArguments[0]); PCH->handleStore(Builder, *Result); - return v{}; + return {}; } - case PTC_INSTRUCTION_op_add_i32: - case PTC_INSTRUCTION_op_sub_i32: - case PTC_INSTRUCTION_op_mul_i32: - case PTC_INSTRUCTION_op_div_i32: - case PTC_INSTRUCTION_op_divu_i32: - case PTC_INSTRUCTION_op_rem_i32: - case PTC_INSTRUCTION_op_remu_i32: - case PTC_INSTRUCTION_op_and_i32: - case PTC_INSTRUCTION_op_or_i32: - case PTC_INSTRUCTION_op_xor_i32: - case PTC_INSTRUCTION_op_shl_i32: - case PTC_INSTRUCTION_op_shr_i32: - case PTC_INSTRUCTION_op_sar_i32: - case PTC_INSTRUCTION_op_add_i64: - case PTC_INSTRUCTION_op_sub_i64: - case PTC_INSTRUCTION_op_mul_i64: - case PTC_INSTRUCTION_op_div_i64: - case PTC_INSTRUCTION_op_divu_i64: - case PTC_INSTRUCTION_op_rem_i64: - case PTC_INSTRUCTION_op_remu_i64: - case PTC_INSTRUCTION_op_and_i64: - case PTC_INSTRUCTION_op_or_i64: - case PTC_INSTRUCTION_op_xor_i64: - case PTC_INSTRUCTION_op_shl_i64: - case PTC_INSTRUCTION_op_shr_i64: - case PTC_INSTRUCTION_op_sar_i64: { + case LIBTCG_op_add_i32: + case LIBTCG_op_sub_i32: + case LIBTCG_op_mul_i32: + case LIBTCG_op_div_i32: + case LIBTCG_op_divu_i32: + case LIBTCG_op_rem_i32: + case LIBTCG_op_remu_i32: + case LIBTCG_op_and_i32: + case LIBTCG_op_or_i32: + case LIBTCG_op_xor_i32: + case LIBTCG_op_shl_i32: + case LIBTCG_op_shr_i32: + case LIBTCG_op_sar_i32: + case LIBTCG_op_add_i64: + case LIBTCG_op_sub_i64: + case LIBTCG_op_mul_i64: + case LIBTCG_op_div_i64: + case LIBTCG_op_divu_i64: + case LIBTCG_op_rem_i64: + case LIBTCG_op_remu_i64: + case LIBTCG_op_and_i64: + case LIBTCG_op_or_i64: + case LIBTCG_op_xor_i64: + case LIBTCG_op_shl_i64: + case LIBTCG_op_shr_i64: + case LIBTCG_op_sar_i64: { // TODO: assert on sizes? Instruction::BinaryOps BinaryOp = opcodeToBinaryOp(Opcode); Value *Operation = Builder.CreateBinOp(BinaryOp, InArguments[0], InArguments[1]); - return v{ Operation }; + return { Operation }; } - case PTC_INSTRUCTION_op_div2_i32: - case PTC_INSTRUCTION_op_divu2_i32: - case PTC_INSTRUCTION_op_div2_i64: - case PTC_INSTRUCTION_op_divu2_i64: { + case LIBTCG_op_div2_i32: + case LIBTCG_op_divu2_i32: + case LIBTCG_op_div2_i64: + case LIBTCG_op_divu2_i64: { Instruction::BinaryOps DivisionOp, RemainderOp; - if (Opcode == PTC_INSTRUCTION_op_div2_i32 - || Opcode == PTC_INSTRUCTION_op_div2_i64) { + if (Opcode == LIBTCG_op_div2_i32 or Opcode == LIBTCG_op_div2_i64) { DivisionOp = Instruction::SDiv; RemainderOp = Instruction::SRem; - } else if (Opcode == PTC_INSTRUCTION_op_divu2_i32 - || Opcode == PTC_INSTRUCTION_op_divu2_i64) { + } else if (Opcode == LIBTCG_op_divu2_i32 or Opcode == LIBTCG_op_divu2_i64) { DivisionOp = Instruction::UDiv; RemainderOp = Instruction::URem; } else { @@ -1120,21 +1107,19 @@ IT::translateOpcode(PTCOpcode Opcode, Value *Remainder = Builder.CreateBinOp(RemainderOp, InArguments[0], InArguments[2]); - return v{ Division, Remainder }; + return { Division, Remainder }; } - case PTC_INSTRUCTION_op_rotr_i32: - case PTC_INSTRUCTION_op_rotr_i64: - case PTC_INSTRUCTION_op_rotl_i32: - case PTC_INSTRUCTION_op_rotl_i64: { + case LIBTCG_op_rotr_i32: + case LIBTCG_op_rotr_i64: + case LIBTCG_op_rotl_i32: + case LIBTCG_op_rotl_i64: { Value *Bits = ConstantInt::get(RegisterType, RegisterSize); Instruction::BinaryOps FirstShiftOp, SecondShiftOp; - if (Opcode == PTC_INSTRUCTION_op_rotl_i32 - || Opcode == PTC_INSTRUCTION_op_rotl_i64) { + if (Opcode == LIBTCG_op_rotl_i32 or Opcode == LIBTCG_op_rotl_i64) { FirstShiftOp = Instruction::Shl; SecondShiftOp = Instruction::LShr; - } else if (Opcode == PTC_INSTRUCTION_op_rotr_i32 - || Opcode == PTC_INSTRUCTION_op_rotr_i64) { + } else if (Opcode == LIBTCG_op_rotr_i32 or Opcode == LIBTCG_op_rotr_i64) { FirstShiftOp = Instruction::LShr; SecondShiftOp = Instruction::Shl; } else { @@ -1149,58 +1134,48 @@ IT::translateOpcode(PTCOpcode Opcode, InArguments[0], SecondShiftAmount); - return v{ Builder.CreateOr(FirstShift, SecondShift) }; + return { Builder.CreateOr(FirstShift, SecondShift) }; } - case PTC_INSTRUCTION_op_deposit_i32: - case PTC_INSTRUCTION_op_deposit_i64: { - unsigned Position = ConstArguments[0]; - if (Position == RegisterSize) - return v{ InArguments[0] }; - - unsigned Length = ConstArguments[1]; - uint64_t Bits = 0; - - // Thou shall not << 32 - if (Length == RegisterSize) - Bits = getMaxValue(RegisterSize); - else - Bits = (1 << Length) - 1; - - // result = (t1 & ~(bits << position)) | ((t2 & bits) << position) - uint64_t BaseMask = ~(Bits << Position); - Value *MaskedBase = Builder.CreateAnd(InArguments[0], BaseMask); - Value *Deposit = Builder.CreateAnd(InArguments[1], Bits); - Value *ShiftedDeposit = Builder.CreateShl(Deposit, Position); - Value *Result = Builder.CreateOr(MaskedBase, ShiftedDeposit); - - return v{ Result }; + case LIBTCG_op_deposit_i32: + case LIBTCG_op_deposit_i64: { + Value *Result = genDeposit(Builder, + RegisterSize, + InArguments[0], + InArguments[1], + InArguments[2], + InArguments[3]); + return { Result }; } - case PTC_INSTRUCTION_op_ext8s_i32: - case PTC_INSTRUCTION_op_ext16s_i32: - case PTC_INSTRUCTION_op_ext8u_i32: - case PTC_INSTRUCTION_op_ext16u_i32: - case PTC_INSTRUCTION_op_ext8s_i64: - case PTC_INSTRUCTION_op_ext16s_i64: - case PTC_INSTRUCTION_op_ext32s_i64: - case PTC_INSTRUCTION_op_ext8u_i64: - case PTC_INSTRUCTION_op_ext16u_i64: - case PTC_INSTRUCTION_op_ext32u_i64: { + case LIBTCG_op_ext8s_i32: + case LIBTCG_op_ext16s_i32: + case LIBTCG_op_ext8u_i32: + case LIBTCG_op_ext16u_i32: + case LIBTCG_op_ext8s_i64: + case LIBTCG_op_ext16s_i64: + case LIBTCG_op_ext32s_i64: + case LIBTCG_op_ext8u_i64: + case LIBTCG_op_ext16u_i64: + case LIBTCG_op_ext32u_i64: + case LIBTCG_op_ext_i32_i64: + case LIBTCG_op_extu_i32_i64: { Type *SourceType = nullptr; switch (Opcode) { - case PTC_INSTRUCTION_op_ext8s_i32: - case PTC_INSTRUCTION_op_ext8u_i32: - case PTC_INSTRUCTION_op_ext8s_i64: - case PTC_INSTRUCTION_op_ext8u_i64: + case LIBTCG_op_ext8s_i32: + case LIBTCG_op_ext8u_i32: + case LIBTCG_op_ext8s_i64: + case LIBTCG_op_ext8u_i64: SourceType = Builder.getInt8Ty(); break; - case PTC_INSTRUCTION_op_ext16s_i32: - case PTC_INSTRUCTION_op_ext16u_i32: - case PTC_INSTRUCTION_op_ext16s_i64: - case PTC_INSTRUCTION_op_ext16u_i64: + case LIBTCG_op_ext16s_i32: + case LIBTCG_op_ext16u_i32: + case LIBTCG_op_ext16s_i64: + case LIBTCG_op_ext16u_i64: SourceType = Builder.getInt16Ty(); break; - case PTC_INSTRUCTION_op_ext32s_i64: - case PTC_INSTRUCTION_op_ext32u_i64: + case LIBTCG_op_ext32s_i64: + case LIBTCG_op_ext32u_i64: + case LIBTCG_op_ext_i32_i64: + case LIBTCG_op_extu_i32_i64: SourceType = Builder.getInt32Ty(); break; default: @@ -1210,48 +1185,59 @@ IT::translateOpcode(PTCOpcode Opcode, Value *Truncated = Builder.CreateTrunc(InArguments[0], SourceType); switch (Opcode) { - case PTC_INSTRUCTION_op_ext8s_i32: - case PTC_INSTRUCTION_op_ext8s_i64: - case PTC_INSTRUCTION_op_ext16s_i32: - case PTC_INSTRUCTION_op_ext16s_i64: - case PTC_INSTRUCTION_op_ext32s_i64: - return v{ Builder.CreateSExt(Truncated, RegisterType) }; - case PTC_INSTRUCTION_op_ext8u_i32: - case PTC_INSTRUCTION_op_ext8u_i64: - case PTC_INSTRUCTION_op_ext16u_i32: - case PTC_INSTRUCTION_op_ext16u_i64: - case PTC_INSTRUCTION_op_ext32u_i64: - return v{ Builder.CreateZExt(Truncated, RegisterType) }; + case LIBTCG_op_ext8s_i32: + case LIBTCG_op_ext8s_i64: + case LIBTCG_op_ext16s_i32: + case LIBTCG_op_ext16s_i64: + case LIBTCG_op_ext32s_i64: + case LIBTCG_op_ext_i32_i64: + return { Builder.CreateSExt(Truncated, RegisterType) }; + case LIBTCG_op_ext8u_i32: + case LIBTCG_op_ext8u_i64: + case LIBTCG_op_ext16u_i32: + case LIBTCG_op_ext16u_i64: + case LIBTCG_op_ext32u_i64: + case LIBTCG_op_extu_i32_i64: + return { Builder.CreateZExt(Truncated, RegisterType) }; default: revng_unreachable("Unexpected opcode"); } } - case PTC_INSTRUCTION_op_not_i32: - case PTC_INSTRUCTION_op_not_i64: - return v{ Builder.CreateXor(InArguments[0], getMaxValue(RegisterSize)) }; - case PTC_INSTRUCTION_op_neg_i32: - case PTC_INSTRUCTION_op_neg_i64: { - auto *InitialValue = ConstantInt::get(RegisterType, 0); - return v{ Builder.CreateSub(InitialValue, InArguments[0]) }; + case LIBTCG_op_extrl_i64_i32: { + return { Builder.CreateTrunc(InArguments[0], Builder.getInt32Ty()) }; } - case PTC_INSTRUCTION_op_andc_i32: - case PTC_INSTRUCTION_op_andc_i64: - case PTC_INSTRUCTION_op_orc_i32: - case PTC_INSTRUCTION_op_orc_i64: - case PTC_INSTRUCTION_op_eqv_i32: - case PTC_INSTRUCTION_op_eqv_i64: { + case LIBTCG_op_extrh_i64_i32: { + Value *Shifted = Builder.CreateAShr(InArguments[0], + ConstantInt::get(Builder.getInt64Ty(), + 32)); + return { Builder.CreateTrunc(Shifted, Builder.getInt32Ty()) }; + } + case LIBTCG_op_not_i32: + case LIBTCG_op_not_i64: + return { Builder.CreateXor(InArguments[0], getMaxValue(RegisterSize)) }; + case LIBTCG_op_neg_i32: + case LIBTCG_op_neg_i64: { + auto *InitialValue = ConstantInt::get(RegisterType, 0); + return { Builder.CreateSub(InitialValue, InArguments[0]) }; + } + case LIBTCG_op_andc_i32: + case LIBTCG_op_andc_i64: + case LIBTCG_op_orc_i32: + case LIBTCG_op_orc_i64: + case LIBTCG_op_eqv_i32: + case LIBTCG_op_eqv_i64: { Instruction::BinaryOps ExternalOp; switch (Opcode) { - case PTC_INSTRUCTION_op_andc_i32: - case PTC_INSTRUCTION_op_andc_i64: + case LIBTCG_op_andc_i32: + case LIBTCG_op_andc_i64: ExternalOp = Instruction::And; break; - case PTC_INSTRUCTION_op_orc_i32: - case PTC_INSTRUCTION_op_orc_i64: + case LIBTCG_op_orc_i32: + case LIBTCG_op_orc_i64: ExternalOp = Instruction::Or; break; - case PTC_INSTRUCTION_op_eqv_i32: - case PTC_INSTRUCTION_op_eqv_i64: + case LIBTCG_op_eqv_i32: + case LIBTCG_op_eqv_i64: ExternalOp = Instruction::Xor; break; default: @@ -1261,36 +1247,36 @@ IT::translateOpcode(PTCOpcode Opcode, Value *Negate = Builder.CreateXor(InArguments[1], getMaxValue(RegisterSize)); Value *Result = Builder.CreateBinOp(ExternalOp, InArguments[0], Negate); - return v{ Result }; + return { Result }; } - case PTC_INSTRUCTION_op_nand_i32: - case PTC_INSTRUCTION_op_nand_i64: { + case LIBTCG_op_nand_i32: + case LIBTCG_op_nand_i64: { Value *AndValue = Builder.CreateAnd(InArguments[0], InArguments[1]); Value *Result = Builder.CreateXor(AndValue, getMaxValue(RegisterSize)); - return v{ Result }; + return { Result }; } - case PTC_INSTRUCTION_op_nor_i32: - case PTC_INSTRUCTION_op_nor_i64: { + case LIBTCG_op_nor_i32: + case LIBTCG_op_nor_i64: { Value *OrValue = Builder.CreateOr(InArguments[0], InArguments[1]); Value *Result = Builder.CreateXor(OrValue, getMaxValue(RegisterSize)); - return v{ Result }; + return { Result }; } - case PTC_INSTRUCTION_op_bswap16_i32: - case PTC_INSTRUCTION_op_bswap32_i32: - case PTC_INSTRUCTION_op_bswap16_i64: - case PTC_INSTRUCTION_op_bswap32_i64: - case PTC_INSTRUCTION_op_bswap64_i64: { + case LIBTCG_op_bswap16_i32: + case LIBTCG_op_bswap32_i32: + case LIBTCG_op_bswap16_i64: + case LIBTCG_op_bswap32_i64: + case LIBTCG_op_bswap64_i64: { Type *SwapType = nullptr; switch (Opcode) { - case PTC_INSTRUCTION_op_bswap16_i32: - case PTC_INSTRUCTION_op_bswap16_i64: + case LIBTCG_op_bswap16_i32: + case LIBTCG_op_bswap16_i64: SwapType = Builder.getInt16Ty(); break; - case PTC_INSTRUCTION_op_bswap32_i32: - case PTC_INSTRUCTION_op_bswap32_i64: + case LIBTCG_op_bswap32_i32: + case LIBTCG_op_bswap32_i64: SwapType = Builder.getInt32Ty(); break; - case PTC_INSTRUCTION_op_bswap64_i64: + case LIBTCG_op_bswap64_i64: SwapType = Builder.getInt64Ty(); break; default: @@ -1304,10 +1290,11 @@ IT::translateOpcode(PTCOpcode Opcode, { SwapType }); Value *Swapped = Builder.CreateCall(BSwapFunction, Truncated); - return v{ Builder.CreateZExt(Swapped, RegisterType) }; + return { Builder.CreateZExt(Swapped, RegisterType) }; } - case PTC_INSTRUCTION_op_set_label: { - unsigned LabelId = ptc.get_arg_label_id(ConstArguments[0]); + case LIBTCG_op_set_label: { + revng_assert(ConstArguments[0].kind == LIBTCG_ARG_LABEL); + auto LabelId = ConstArguments[0].label->id; std::stringstream LabelSS; LabelSS << "bb." << JumpTargets.nameForAddress(LastPC); @@ -1335,17 +1322,18 @@ IT::translateOpcode(PTCOpcode Opcode, Blocks.push_back(Fallthrough); Builder.SetInsertPoint(Fallthrough); - Variables.newBasicBlock(); + Variables.newExtendedBasicBlock(); - return v{}; + return {}; } - case PTC_INSTRUCTION_op_br: - case PTC_INSTRUCTION_op_brcond_i32: - case PTC_INSTRUCTION_op_brcond2_i32: - case PTC_INSTRUCTION_op_brcond_i64: { + case LIBTCG_op_br: + case LIBTCG_op_brcond_i32: + case LIBTCG_op_brcond2_i32: + case LIBTCG_op_brcond_i64: { // We take the last constant arguments, which is the LabelId both in // conditional and unconditional jumps - unsigned LabelId = ptc.get_arg_label_id(ConstArguments.back()); + revng_assert(ConstArguments.back().kind == LIBTCG_ARG_LABEL); + auto LabelId = ConstArguments.back().label->id; std::stringstream LabelSS; LabelSS << "bb." << JumpTargets.nameForAddress(LastPC); @@ -1366,14 +1354,15 @@ IT::translateOpcode(PTCOpcode Opcode, Target = LabeledBasicBlocks[Label]; } - if (Opcode == PTC_INSTRUCTION_op_br) { + if (Opcode == LIBTCG_op_br) { // Unconditional jump Builder.CreateBr(Target); - } else if (Opcode == PTC_INSTRUCTION_op_brcond_i32 - || Opcode == PTC_INSTRUCTION_op_brcond_i64) { + } else if (Opcode == LIBTCG_op_brcond_i32 + or Opcode == LIBTCG_op_brcond_i64) { // Conditional jump + revng_assert(ConstArguments[0].kind == LIBTCG_ARG_COND); Value *Compare = createICmp(Builder, - ConstArguments[0], + ConstArguments[0].cond, InArguments[0], InArguments[1]); Builder.CreateCondBr(Compare, Target, Fallthrough); @@ -1383,11 +1372,14 @@ IT::translateOpcode(PTCOpcode Opcode, Blocks.push_back(Fallthrough); Builder.SetInsertPoint(Fallthrough); - Variables.newBasicBlock(); - return v{}; + if (Opcode == LIBTCG_op_br) { + Variables.newExtendedBasicBlock(); + } + + return {}; } - case PTC_INSTRUCTION_op_exit_tb: { + case LIBTCG_op_exit_tb: { auto *Zero = ConstantInt::get(Type::getInt32Ty(Context), 0); Builder.CreateCall(JumpTargets.exitTB(), { Zero }); Builder.CreateUnreachable(); @@ -1397,17 +1389,18 @@ IT::translateOpcode(PTCOpcode Opcode, auto *NextBB = BasicBlock::Create(Context, "", TheFunction); Blocks.push_back(NextBB); Builder.SetInsertPoint(NextBB); - Variables.newBasicBlock(); + Variables.newExtendedBasicBlock(); - return v{}; + return {}; } - case PTC_INSTRUCTION_op_goto_tb: + case LIBTCG_op_goto_tb: + case LIBTCG_op_goto_ptr: // Nothing to do here - return v{}; - case PTC_INSTRUCTION_op_add2_i32: - case PTC_INSTRUCTION_op_sub2_i32: - case PTC_INSTRUCTION_op_add2_i64: - case PTC_INSTRUCTION_op_sub2_i64: { + return {}; + case LIBTCG_op_add2_i32: + case LIBTCG_op_sub2_i32: + case LIBTCG_op_add2_i64: + case LIBTCG_op_sub2_i64: { Value *FirstOpLow = nullptr; Value *FirstOpHigh = nullptr; Value *SecondOpLow = nullptr; @@ -1434,23 +1427,21 @@ IT::translateOpcode(PTCOpcode Opcode, Value *ShiftedResult = Builder.CreateLShr(Result, RegisterSize); Value *ResultHigh = Builder.CreateTrunc(ShiftedResult, RegisterType); - return v{ ResultLow, ResultHigh }; + return { ResultLow, ResultHigh }; } - case PTC_INSTRUCTION_op_mulu2_i32: - case PTC_INSTRUCTION_op_mulu2_i64: - case PTC_INSTRUCTION_op_muls2_i32: - case PTC_INSTRUCTION_op_muls2_i64: { + case LIBTCG_op_mulu2_i32: + case LIBTCG_op_mulu2_i64: + case LIBTCG_op_muls2_i32: + case LIBTCG_op_muls2_i64: { IntegerType *DestinationType = Builder.getIntNTy(RegisterSize * 2); Value *FirstOp = nullptr; Value *SecondOp = nullptr; - if (Opcode == PTC_INSTRUCTION_op_mulu2_i32 - || Opcode == PTC_INSTRUCTION_op_mulu2_i64) { + if (Opcode == LIBTCG_op_mulu2_i32 or Opcode == LIBTCG_op_mulu2_i64) { FirstOp = Builder.CreateZExt(InArguments[0], DestinationType); SecondOp = Builder.CreateZExt(InArguments[1], DestinationType); - } else if (Opcode == PTC_INSTRUCTION_op_muls2_i32 - || Opcode == PTC_INSTRUCTION_op_muls2_i64) { + } else if (Opcode == LIBTCG_op_muls2_i32 or Opcode == LIBTCG_op_muls2_i64) { FirstOp = Builder.CreateSExt(InArguments[0], DestinationType); SecondOp = Builder.CreateSExt(InArguments[1], DestinationType); } else { @@ -1463,18 +1454,135 @@ IT::translateOpcode(PTCOpcode Opcode, Value *ShiftedResult = Builder.CreateLShr(Result, RegisterSize); Value *ResultHigh = Builder.CreateTrunc(ShiftedResult, RegisterType); - return v{ ResultLow, ResultHigh }; + return { ResultLow, ResultHigh }; } - case PTC_INSTRUCTION_op_muluh_i32: - case PTC_INSTRUCTION_op_mulsh_i32: - case PTC_INSTRUCTION_op_muluh_i64: - case PTC_INSTRUCTION_op_mulsh_i64: - - case PTC_INSTRUCTION_op_setcond2_i32: - - case PTC_INSTRUCTION_op_trunc_shr_i32: + case LIBTCG_op_muluh_i32: + case LIBTCG_op_mulsh_i32: + case LIBTCG_op_muluh_i64: + case LIBTCG_op_mulsh_i64: + case LIBTCG_op_setcond2_i32: revng_unreachable("Instruction not implemented"); + case LIBTCG_op_extract_i32: { + auto *Const32 = ConstantInt::get(Type::getInt32Ty(Context), 32); + Value *Length = InArguments[1]; + Value *Offset = InArguments[2]; + Value *ShlAmount = Builder.CreateSub(Const32, + Builder.CreateAdd(Offset, Length)); + Value *Shl = Builder.CreateShl(InArguments[0], ShlAmount); + Value *LShr = Builder.CreateLShr(Shl, Builder.CreateSub(Const32, Length)); + return { LShr }; + } + case LIBTCG_op_sextract_i32: { + auto *Const32 = ConstantInt::get(Type::getInt32Ty(Context), 32); + Value *Length = InArguments[1]; + Value *Offset = InArguments[2]; + Value *ShlAmount = Builder.CreateSub(Const32, + Builder.CreateAdd(Offset, Length)); + Value *Shl = Builder.CreateShl(InArguments[0], ShlAmount); + Value *AShr = Builder.CreateAShr(Shl, Builder.CreateSub(Const32, Length)); + return { AShr }; + } + case LIBTCG_op_extract_i64: { + auto *Const64 = ConstantInt::get(Type::getInt64Ty(Context), 64); + Value *Length = InArguments[1]; + Value *Offset = InArguments[2]; + Value *ShlAmount = Builder.CreateSub(Const64, + Builder.CreateAdd(Offset, Length)); + Value *Shl = Builder.CreateShl(InArguments[0], ShlAmount); + Value *LShr = Builder.CreateLShr(Shl, Builder.CreateSub(Const64, Length)); + return { LShr }; + } + case LIBTCG_op_sextract_i64: { + auto *Const64 = ConstantInt::get(Type::getInt64Ty(Context), 64); + Value *Length = InArguments[1]; + Value *Offset = InArguments[2]; + Value *ShlAmount = Builder.CreateSub(Const64, + Builder.CreateAdd(Offset, Length)); + Value *Shl = Builder.CreateShl(InArguments[0], ShlAmount); + Value *AShr = Builder.CreateAShr(Shl, Builder.CreateSub(Const64, Length)); + return { AShr }; + } + case LIBTCG_op_extract2_i32: + case LIBTCG_op_extract2_i64: { + Value *Low = InArguments[0]; + Value *High = InArguments[1]; + Value *Offset = InArguments[2]; + + auto *ConstSize = ConstantInt::get(RegisterType, RegisterSize); + Value *Shift = Builder.CreateLShr(Low, Offset); + Value *Result = genDeposit(Builder, + RegisterSize, + Shift, + High, + Builder.CreateSub(ConstSize, Offset), + Offset); + + return { Result }; + } + case LIBTCG_op_clz_i32: { + Type *Int1Ty = Type::getInt1Ty(Context); + auto *One = ConstantInt::get(Int1Ty, 1); + auto *Zero = ConstantInt::get(RegisterType, 0); + Value *Arg = InArguments[0]; + Value *ZeroVal = InArguments[1]; + CallInst *Ctlz = Builder.CreateBinaryIntrinsic(Intrinsic::ctlz, Arg, One); + Value *ICmp = Builder.CreateICmp(CmpInst::ICMP_EQ, Arg, Zero); + Value *Select = Builder.CreateSelect(ICmp, ZeroVal, Ctlz); + return { Select }; + } + case LIBTCG_op_clz_i64: { + Type *Int1Ty = Type::getInt1Ty(Context); + auto *One = ConstantInt::get(Int1Ty, 1); + auto *Zero = ConstantInt::get(RegisterType, 0); + Value *Arg = InArguments[0]; + Value *ZeroVal = InArguments[1]; + CallInst *Ctlz = Builder.CreateBinaryIntrinsic(Intrinsic::ctlz, Arg, One); + Value *ICmp = Builder.CreateICmp(CmpInst::ICMP_EQ, Arg, Zero); + Value *Select = Builder.CreateSelect(ICmp, ZeroVal, Ctlz); + return { Select }; + } + case LIBTCG_op_ctz_i32: { + Type *Int1Ty = Type::getInt1Ty(Context); + auto *One = ConstantInt::get(Int1Ty, 1); + auto *Zero = ConstantInt::get(RegisterType, 0); + Value *Arg = InArguments[0]; + Value *ZeroVal = InArguments[1]; + CallInst *Cttz = Builder.CreateBinaryIntrinsic(Intrinsic::cttz, Arg, One); + Value *ICmp = Builder.CreateICmp(CmpInst::ICMP_EQ, Arg, Zero); + Value *Select = Builder.CreateSelect(ICmp, ZeroVal, Cttz); + return { Select }; + } + case LIBTCG_op_ctz_i64: { + Type *Int1Ty = Type::getInt1Ty(Context); + auto *One = ConstantInt::get(Int1Ty, 1); + auto *Zero = ConstantInt::get(RegisterType, 0); + Value *Arg = InArguments[0]; + Value *ZeroVal = InArguments[1]; + CallInst *Cttz = Builder.CreateBinaryIntrinsic(Intrinsic::cttz, Arg, One); + Value *ICmp = Builder.CreateICmp(CmpInst::ICMP_EQ, Arg, Zero); + Value *Select = Builder.CreateSelect(ICmp, ZeroVal, Cttz); + return { Select }; + } default: - revng_unreachable("Unknown opcode"); + // For debugging purposes printing the actual opcode + // really helps. + std::stringstream ErrSS; + ErrSS << "Unknown libtcg opcode [" << Opcode + << "]: " << LibTcg.instructionName(Opcode); + revng_unreachable(ErrSS.str().c_str()); } } + +void IT::handleExitTB() { + auto &Context = TheModule.getContext(); + auto *Zero = ConstantInt::get(Type::getInt32Ty(Context), 0); + Builder.CreateCall(JumpTargets.exitTB(), { Zero }); + Builder.CreateUnreachable(); + + ExitBlocks.push_back(Builder.GetInsertBlock()); + + auto *NextBB = BasicBlock::Create(Context, "", TheFunction); + Blocks.push_back(NextBB); + Builder.SetInsertPoint(NextBB); + Variables.newExtendedBasicBlock(); +} diff --git a/lib/Lift/InstructionTranslator.h b/lib/Lift/InstructionTranslator.h index 7d3b5256b..30d847e18 100644 --- a/lib/Lift/InstructionTranslator.h +++ b/lib/Lift/InstructionTranslator.h @@ -11,7 +11,7 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/Pass.h" -#include "revng/Lift/PTCDump.h" +#include "revng/Lift/LibTcg.h" #include "revng/Model/ProgramCounterHandler.h" #include "JumpTargetManager.h" @@ -40,7 +40,8 @@ public: /// \param Blocks reference to a `vector` of `BasicBlock`s used to keep track /// on which `BasicBlock`s the InstructionTranslator worked on, for /// further processing. - InstructionTranslator(revng::IRBuilder &Builder, + InstructionTranslator(LibTcg &LibTcg, + revng::IRBuilder &Builder, VariableManager &Variables, JumpTargetManager &JumpTargets, std::vector Blocks, @@ -48,10 +49,8 @@ public: ProgramCounterHandler *PCH); // Emit a call to newpc - llvm::CallInst *emitNewPCCall(revng::IRBuilder &Builder, - MetaAddress PC, - uint64_t Size, - llvm::Value *String) const; + llvm::CallInst * + emitNewPCCall(revng::IRBuilder &Builder, MetaAddress PC, uint64_t Size) const; /// Result status of the translation of a PTC opcode enum TranslationResult { @@ -78,30 +77,38 @@ public: /// `MetaAddress` representing the current and next PC. // TODO: rename to newPC // TODO: the signature of this function is ugly - std::tuple - newInstruction(PTCInstruction *Instr, - PTCInstruction *Next, + std::tuple + newInstruction(LibTcgInstruction *Instr, + LibTcgInstruction *Next, MetaAddress StartPC, MetaAddress EndPC, - bool IsFirst, - MetaAddress AbortAt); + bool IsFirst); /// Translate an ordinary instruction /// /// \param Instr the instruction to translate. /// \param PC the PC associated to \p Instr. - /// \param NextPC the PC associated to instruction after \p Instr. + /// \param SinceInstructionStart index of the TCG instruction since the last + /// input instruction has started. \param NextPC the PC associated to + /// instruction after \p Instr. /// /// \return see InstructionTranslator::TranslationResult. - TranslationResult - translate(PTCInstruction *Instr, MetaAddress PC, MetaAddress NextPC); + TranslationResult translate(LibTcgInstruction *Instr, + MetaAddress PC, + unsigned SinceInstructionStart, + MetaAddress NextPC); /// Translate a call to an helper /// /// \param Instr the PTCInstruction of the call to the helper. + /// \param PC the PC associated to \p Instr. + /// \param SinceInstructionStart index of the TCG instruction since the last + /// input instruction has started. /// /// \return see InstructionTranslator::TranslationResult. - TranslationResult translateCall(PTCInstruction *Instr); + TranslationResult translateCall(LibTcgInstruction *Instr, + MetaAddress PC, + unsigned SinceInstructionStart); /// Handle calls to `newPC` marker and emit coverage information void finalizeNewPCMarkers(); @@ -112,19 +119,24 @@ public: /// Preprocess the translated instructions /// /// Check if the translated code contains a delay slot and return a blacklist - /// of the PTC_INSTRUCTION_op_debug_insn_start instructions that have to be + /// of the LIBTCG_op_insn_start instructions that have to be /// ignored to merge the delay slot into the branch instruction. - llvm::SmallSet preprocess(PTCInstructionList *Instructions); + llvm::SmallSet preprocess(const LibTcgTranslationBlock &TB); void registerDirectJumps(); private: - llvm::Expected> - translateOpcode(PTCOpcode Opcode, - std::vector ConstArguments, + std::vector + translateOpcode(LibTcgOpcode Opcode, + std::vector ConstArguments, std::vector InArguments); + int64_t getEnvOffset(llvm::Instruction &I, int64_t Offset) const; + + void handleExitTB(); + private: + LibTcg &LibTcg; revng::IRBuilder &Builder; VariableManager &Variables; JumpTargetManager &JumpTargets; diff --git a/lib/Lift/JumpTargetManager.cpp b/lib/Lift/JumpTargetManager.cpp index 65bb76c0a..23ee73312 100644 --- a/lib/Lift/JumpTargetManager.cpp +++ b/lib/Lift/JumpTargetManager.cpp @@ -278,20 +278,10 @@ bool TDBP::pinConstantStore(Function &F) { static bool isPossiblyReturningHelper(ProgramCounterHandler *PCH, const MetaAddress &PC, llvm::Instruction *I) { - // Is this a helper? - auto *Call = getCallToHelper(I); - if (Call == nullptr) + if (not PCH->isPCAffectingHelper(I)) return false; - // Does this helper write something? - auto UsedCSV = getCSVUsedByHelperCallIfAvailable(Call); - if (not UsedCSV.has_value() or UsedCSV->Written.empty()) - return false; - - // Does this helper affect PC? - auto AffectsPC = [PCH](GlobalVariable *CSV) { return PCH->affectsPC(CSV); }; - if (not llvm::any_of(UsedCSV->Written, AffectsPC)) - return false; + auto *Call = cast(I); // Obtain the name of the helper StringRef CalleeName; @@ -465,7 +455,6 @@ MaterializedValue JumpTargetManager::readFromPointer(MetaAddress LoadAddress, JumpTargetManager::JumpTargetManager(Function *TheFunction, ProgramCounterHandler *PCH, - CSAAFactory CreateCSAA, const TupleTree &Model, const RawBinaryView &BinaryView) : TheModule(*TheFunction->getParent()), @@ -477,7 +466,6 @@ JumpTargetManager::JumpTargetManager(Function *TheFunction, Dispatcher(nullptr), DispatcherSwitch(nullptr), CurrentCFGForm(CFGForm::UnknownForm), - CreateCSAA(CreateCSAA), PCH(PCH), Model(Model), BinaryView(BinaryView) { diff --git a/lib/Lift/JumpTargetManager.h b/lib/Lift/JumpTargetManager.h index d33d58214..19dd342cb 100644 --- a/lib/Lift/JumpTargetManager.h +++ b/lib/Lift/JumpTargetManager.h @@ -25,6 +25,7 @@ #include "revng/Model/Binary.h" #include "revng/Model/ProgramCounterHandler.h" #include "revng/Model/RawBinaryView.h" +#include "revng/Support/IRHelperRegistry.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/MetaAddress.h" #include "revng/Support/MetaAddress/MetaAddressRangeSet.h" @@ -149,8 +150,6 @@ inline const char *getName(Values V) { } // namespace CFGForm -class CPUStateAccessAnalysisPass; - class JumpTargetManager { private: using interval_set = boost::icl::interval_set; @@ -214,16 +213,12 @@ public: public: using BlockMap = std::map; - using CSAAFactory = std::function; public: /// \param TheFunction the translated function. /// \param PCH ProgramCounterHandler instance. - /// \param CreateCSAA a factory function able to create - /// CPUStateAccessAnalysisPass. JumpTargetManager(llvm::Function *TheFunction, ProgramCounterHandler *PCH, - CSAAFactory CreateCSAA, const TupleTree &Model, const RawBinaryView &BinaryView); @@ -236,8 +231,6 @@ public: /// Collect jump targets from the program's segments void harvestGlobalData(); - auto createCSAA() { return CreateCSAA(); } - /// Handle a new program counter. We might already have a basic block for that /// program counter, or we could even have a translation for it. Return one /// of these, if appropriate. @@ -463,15 +456,11 @@ public: } MetaAddress fromPC(uint64_t PC) const { - using namespace model::Architecture; - auto Architecture = toLLVMArchitecture(Model->Architecture()); - return MetaAddress::fromPC(Architecture, PC); + return MetaAddress::fromPC(Model->Architecture(), PC); } MetaAddress fromGeneric(uint64_t Address) const { - using namespace model::Architecture; - auto Architecture = toLLVMArchitecture(Model->Architecture()); - return MetaAddress::fromGeneric(Architecture, Address); + return MetaAddress::fromGeneric(Model->Architecture(), Address); } MetaAddress fromPCStore(llvm::StoreInst *Store) { @@ -607,7 +596,6 @@ private: CFGForm::Values CurrentCFGForm; std::set ToPurge; std::set SimpleLiterals; - CSAAFactory CreateCSAA; ProgramCounterHandler *PCH = nullptr; diff --git a/lib/Lift/LibTcg.cpp b/lib/Lift/LibTcg.cpp new file mode 100644 index 000000000..f93721146 --- /dev/null +++ b/lib/Lift/LibTcg.cpp @@ -0,0 +1,64 @@ +/// \file LibTcg.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +extern "C" { +#include "dlfcn.h" +} + +#include "revng/Lift/LibTcg.h" +#include "revng/Support/ResourceFinder.h" + +static std::string +findLibTcgPath(const model::Architecture::Values Architecture) { + llvm::StringRef ArchName = model::Architecture::getQEMUName(Architecture); + const std::string LibTcgName = "/lib/libtcg-" + ArchName.str() + ".so"; + auto OptionalLibTcg = revng::ResourceFinder.findFile(LibTcgName); + revng_assert(OptionalLibTcg.has_value(), "Cannot find libtinycode"); + return OptionalLibTcg.value(); +} + +LibTcg LibTcg::get(model::Architecture::Values Architecture) { + LibTcg Result; + + // Look for the library in the system's paths + std::string LibraryPath = findLibTcgPath(Architecture); + Result.LibraryHandle = dlopen(LibraryPath.c_str(), RTLD_LAZY); + revng_assert(Result.LibraryHandle != nullptr); + + // Obtain the address of the libtcg_load entry point + using LibTcgLoadFunc = LIBTCG_FUNC_TYPE(libtcg_load); + void *LibTcgLoadSym = dlsym(Result.LibraryHandle, "libtcg_load"); + auto LibTcgLoad = reinterpret_cast(LibTcgLoadSym); + revng_assert(LibTcgLoad != nullptr); + + // Load the libtcg interface containing relevant function pointers + Result.Interface = LibTcgLoad(); + + Result.Context = Result.Interface.context_create(); + revng_assert(Result.Context != nullptr, "Failed to create libtcg context"); + + Result.ArchInfo = Result.Interface.get_arch_info(); + + std::set Names; + for (int I = 0; I < Result.ArchInfo.num_globals; ++I) { + if (Result.ArchInfo.globals[I].name == nullptr) + continue; + auto Offset = Result.ArchInfo.globals[I].offset + + Result.ArchInfo.env_offset; + llvm::StringRef Name(Result.ArchInfo.globals[I].name); + + revng_assert(not Names.contains(Name)); + revng_assert(not Result.GlobalNames.contains(Offset)); + Result.GlobalNames[Offset] = Name; + } + + return Result; +} + +LibTcg::~LibTcg() { + Interface.context_destroy(Context); + dlclose(LibraryHandle); +} diff --git a/lib/Lift/Lift.cpp b/lib/Lift/Lift.cpp index 582374887..851777288 100644 --- a/lib/Lift/Lift.cpp +++ b/lib/Lift/Lift.cpp @@ -4,23 +4,22 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include "revng/Lift/LibTcg.h" #include "revng/Lift/Lift.h" #include "revng/Support/CommandLine.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/ResourceFinder.h" #include "CodeGenerator.h" -#include "PTCInterface.h" using namespace llvm::cl; namespace { -#define DESCRIPTION desc("virtual address of the entry point where to start") +const char *EntryDescStr = "virtual address of the entry point where to start"; opt EntryPointAddress("entry", - DESCRIPTION, + desc(EntryDescStr), value_desc("address"), cat(MainCategory)); -#undef DESCRIPTION alias A1("e", desc("Alias for -entry"), aliasopt(EntryPointAddress), @@ -33,84 +32,38 @@ char LiftPass::ID; using Register = llvm::RegisterPass; static Register X("lift", "Lift Pass", true, true); -/// The interface with the PTC library. -PTCInterface ptc = {}; +struct ExternalFilePaths { + std::string LibHelpers; + std::string EarlyLinked; +}; -static std::string LibTinycodePath; -static std::string LibHelpersPath; -static std::string EarlyLinkedPath; - -// When LibraryPointer is destroyed, the destructor calls -// LibraryDestructor::operator()(LibraryPointer::get()). -// The problem is that LibraryDestructor::operator() does not take arguments, -// while the destructor tries to pass a void * argument, so it does not match. -// However, LibraryDestructor is an alias for -// std::intgral_constant, which has an implicit -// conversion operator to value_type, which unwraps the &dlclose from the -// std::integral_constant, making it callable. -using LibraryDestructor = std::integral_constant; -using LibraryPointer = std::unique_ptr; - -static void findFiles(model::Architecture::Values Architecture) { +static ExternalFilePaths +findExternalFilePaths(const model::Architecture::Values Architecture) { + // What symbols from the revng namespace are actually used here? using namespace revng; - std::string ArchName = model::Architecture::getQEMUName(Architecture).str(); + const std::string ArchName = model::Architecture::getQEMUName(Architecture) + .str(); - std::string LibtinycodeName = "/lib/libtinycode-" + ArchName + ".so"; - auto OptionalLibtinycode = ResourceFinder.findFile(LibtinycodeName); - revng_assert(OptionalLibtinycode.has_value(), "Cannot find libtinycode"); - LibTinycodePath = OptionalLibtinycode.value(); + ExternalFilePaths Paths = {}; - std::string LibHelpersName = "/lib/libtinycode-helpers-" + ArchName + ".bc"; + // Note: here we use the slim version of the helpers, i.e., where we only have + // definitions for revng_inline functions. + const std::string LibHelpersName = "/share/revng/libtcg-helpers-annotated" + "-slim-" + + ArchName + ".bc"; auto OptionalHelpers = ResourceFinder.findFile(LibHelpersName); revng_assert(OptionalHelpers.has_value(), "Cannot find tinycode helpers"); - LibHelpersPath = OptionalHelpers.value(); + Paths.LibHelpers = OptionalHelpers.value(); - std::string EarlyLinkedName = "/share/revng/early-linked-" + ArchName + ".ll"; + const std::string EarlyLinkedName = "/share/revng/early-linked-" + ArchName + + ".ll"; auto OptionalEarlyLinked = ResourceFinder.findFile(EarlyLinkedName); revng_assert(OptionalEarlyLinked.has_value(), "Cannot find early-linked.ll"); - EarlyLinkedPath = OptionalEarlyLinked.value(); -} -/// Given an architecture name, loads the appropriate version of the PTC -/// library, and initializes the PTC interface. -/// -/// \param Architecture the name of the architecture, e.g. "arm". -/// \param PTCLibrary a reference to the library handler. -/// -/// \return EXIT_SUCCESS if the library has been successfully loaded. -static int loadPTCLibrary(LibraryPointer &PTCLibrary) { - ptc_load_ptr_t PTCLoad = nullptr; - void *LibraryHandle = nullptr; + Paths.EarlyLinked = OptionalEarlyLinked.value(); - // Look for the library in the system's paths - LibraryHandle = dlopen(LibTinycodePath.c_str(), RTLD_LAZY | RTLD_NODELETE); - - if (LibraryHandle == nullptr) { - fprintf(stderr, "Couldn't load the PTC library: %s\n", dlerror()); - return EXIT_FAILURE; - } - - // The library has been loaded, initialize the pointer, the caller will take - // care of dlclose it from now on - PTCLibrary.reset(LibraryHandle); - - // Obtain the address of the ptc_load entry point - PTCLoad = reinterpret_cast(dlsym(LibraryHandle, "ptc_load")); - - if (PTCLoad == nullptr) { - fprintf(stderr, "Couldn't find ptc_load: %s\n", dlerror()); - return EXIT_FAILURE; - } - - // Initialize the ptc interface - if (PTCLoad(LibraryHandle, &ptc) != 0) { - fprintf(stderr, "Couldn't find PTC functions.\n"); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; + return Paths; } bool LiftPass::runOnModule(llvm::Module &M) { @@ -119,13 +72,11 @@ bool LiftPass::runOnModule(llvm::Module &M) { const TupleTree &Model = ModelWrapper.getReadOnlyModel(); T.advance("findFiles", false); - findFiles(Model->Architecture()); + const auto Paths = findExternalFilePaths(Model->Architecture()); - // Load the appropriate libtyncode version - T.advance("loadPTC", false); - LibraryPointer PTCLibrary; - if (loadPTCLibrary(PTCLibrary) != EXIT_SUCCESS) - return EXIT_FAILURE; + // Look for the library in the system's paths + T.advance("Load libtcg", false); + auto TheLibTcg = LibTcg::get(Model->Architecture()); // Get access to raw binary data RawBinaryView &RawBinary = getAnalysis().get(); @@ -134,15 +85,16 @@ bool LiftPass::runOnModule(llvm::Module &M) { CodeGenerator Generator(RawBinary, &M, Model, - LibHelpersPath, - EarlyLinkedPath, + Paths.LibHelpers, + Paths.EarlyLinked, model::Architecture::x86_64); std::optional EntryPointAddressOptional; if (EntryPointAddress.getNumOccurrences() != 0) EntryPointAddressOptional = EntryPointAddress; T.advance("Translate", true); - Generator.translate(EntryPointAddressOptional); + + Generator.translate(TheLibTcg, EntryPointAddressOptional); sortModule(M); diff --git a/lib/Lift/LiftPipe.cpp b/lib/Lift/LiftPipe.cpp index 4f33f63e9..477ef2a3e 100644 --- a/lib/Lift/LiftPipe.cpp +++ b/lib/Lift/LiftPipe.cpp @@ -5,10 +5,6 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // -extern "C" { -#include "dlfcn.h" -} - #include "llvm/Support/Error.h" #include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" @@ -24,6 +20,8 @@ extern "C" { #include "revng/Support/IRHelpers.h" #include "revng/Support/ResourceFinder.h" +#include "PostLiftVerifyPass.h" + using namespace llvm; using namespace pipeline; using namespace ::revng::pipes; @@ -46,6 +44,7 @@ void Lift::run(ExecutionContext &EC, PM.add(new LoadExecutionContextPass(&EC, Output.name())); PM.add(new LoadBinaryWrapperPass(Buffer->getBuffer())); PM.add(new LiftPass); + PM.add(new PostLiftVerifyPass); PM.run(Output.getModule()); EC.commitUniqueTarget(Output); diff --git a/lib/Lift/PTCDump.cpp b/lib/Lift/PTCDump.cpp deleted file mode 100644 index 2864b0231..000000000 --- a/lib/Lift/PTCDump.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/// \file PTCDump.cpp -/// This file handles dumping PTC to text - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include -#include -#include -#include - -#include "revng/Lift/PTCDump.h" -#include "revng/Support/Assert.h" - -#include "PTCInterface.h" - -static const int MaxTempNameLength = 128; - -static void getTemporaryName(char *Buffer, - size_t BufferSize, - PTCInstructionList *Instructions, - unsigned TemporaryId) { - PTCTemp *Temporary = ptc_temp_get(Instructions, TemporaryId); - - if (ptc_temp_is_global(Instructions, TemporaryId)) - strncpy(Buffer, Temporary->name, BufferSize); - else if (Temporary->temp_local) - snprintf(Buffer, - BufferSize, - "loc%u", - TemporaryId - Instructions->global_temps); - else - snprintf(Buffer, - BufferSize, - "tmp%u", - TemporaryId - Instructions->global_temps); -} - -int dumpInstruction(std::ostream &Result, - PTCInstructionList *Instructions, - unsigned Index) { - size_t I = 0; - // TODO: this should stay in Architecture - int Is64 = 0; - PTCInstruction &Instruction = Instructions->instructions[Index]; - - PTCOpcode Opcode = Instruction.opc; - PTCOpcodeDef *Definition = ptc_instruction_opcode_def(&ptc, &Instruction); - char TemporaryName[MaxTempNameLength + 1] = { '\0' }; - - if (Opcode == PTC_INSTRUCTION_op_debug_insn_start) { - // TODO: create accessors for PTC_INSTRUCTION_op_debug_insn_start - uint64_t PC = Instruction.args[0]; - - if (Is64) - PC |= Instruction.args[1] << 32; - - Result << " ---- 0x" << std::hex << PC << std::endl; - } else if (Opcode == PTC_INSTRUCTION_op_call) { - // TODO: replace PRIx64 with PTC_PRIxARG - PTCInstructionArg FunctionPointer = 0; - FunctionPointer = ptc_call_instruction_const_arg(&ptc, &Instruction, 0); - PTCInstructionArg Flags = ptc_call_instruction_const_arg(&ptc, - &Instruction, - 1); - size_t OutArgsCount = ptc_call_instruction_out_arg_count(&ptc, - &Instruction); - PTCHelperDef *Helper = ptc_find_helper(&ptc, FunctionPointer); - const char *HelperName = "unknown_helper"; - - if (Helper != nullptr && Helper->name != nullptr) - HelperName = Helper->name; - - // The output format is: - // call name, flags, out_args_count, out_args [...], in_args [...] - Result << Definition->name << " " << HelperName << "," - << "$0x" << std::hex << Flags << "," << std::dec << OutArgsCount; - - // Print out arguments - for (I = 0; I < OutArgsCount; I++) { - getTemporaryName(TemporaryName, - MaxTempNameLength, - Instructions, - ptc_call_instruction_out_arg(&ptc, &Instruction, I)); - Result << "," << TemporaryName; - } - - // Print in arguments - size_t InArgsCount = ptc_call_instruction_in_arg_count(&ptc, &Instruction); - for (I = 0; I < InArgsCount; I++) { - PTCInstructionArg InArg = ptc_call_instruction_in_arg(&ptc, - &Instruction, - I); - - if (InArg != PTC_CALL_DUMMY_ARG) { - getTemporaryName(TemporaryName, MaxTempNameLength, Instructions, InArg); - Result << "," << TemporaryName; - } else { - Result << ","; - } - } - - } else { - // TODO: fix commas - Result << Definition->name << " "; - - // Print out arguments - for (I = 0; I < ptc_instruction_out_arg_count(&ptc, &Instruction); I++) { - if (I != 0) - Result << ","; - - getTemporaryName(TemporaryName, - MaxTempNameLength, - Instructions, - ptc_instruction_out_arg(&ptc, &Instruction, I)); - Result << TemporaryName; - } - - if (I != 0) - Result << ","; - - // Print in arguments - for (I = 0; I < ptc_instruction_in_arg_count(&ptc, &Instruction); I++) { - if (I != 0) - Result << ","; - - getTemporaryName(TemporaryName, - MaxTempNameLength, - Instructions, - ptc_instruction_in_arg(&ptc, &Instruction, I)); - Result << TemporaryName; - } - - if (I != 0) - Result << ","; - - /* Parse some special const arguments */ - I = 0; - switch (Opcode) { - case PTC_INSTRUCTION_op_brcond_i32: - case PTC_INSTRUCTION_op_setcond_i32: - case PTC_INSTRUCTION_op_movcond_i32: - case PTC_INSTRUCTION_op_brcond2_i32: - case PTC_INSTRUCTION_op_setcond2_i32: - case PTC_INSTRUCTION_op_brcond_i64: - case PTC_INSTRUCTION_op_setcond_i64: - case PTC_INSTRUCTION_op_movcond_i64: { - PTCInstructionArg Arg = ptc_instruction_const_arg(&ptc, &Instruction, 0); - PTCCondition ConditionId = static_cast(Arg); - const char *ConditionName = ptc.get_condition_name(ConditionId); - - if (ConditionName != nullptr) - Result << "," << ConditionName; - else - Result << "," - << "$0x" << std::hex << Arg; - - /* Consume one argument */ - I++; - - } break; - case PTC_INSTRUCTION_op_qemu_ld_i32: - case PTC_INSTRUCTION_op_qemu_st_i32: - case PTC_INSTRUCTION_op_qemu_ld_i64: - case PTC_INSTRUCTION_op_qemu_st_i64: { - PTCInstructionArg Arg = ptc_instruction_const_arg(&ptc, &Instruction, 0); - PTCLoadStoreArg LoadStoreArg = {}; - LoadStoreArg = ptc.parse_load_store_arg(Arg); - - if (LoadStoreArg.access_type == PTC_MEMORY_ACCESS_UNKNOWN) - Result << "," - << "$0x" << std::hex << LoadStoreArg.raw_op; - else { - const char *Alignment = nullptr; - const char *LoadStoreName = nullptr; - LoadStoreName = ptc.get_load_store_name(LoadStoreArg.type); - - switch (LoadStoreArg.access_type) { - case PTC_MEMORY_ACCESS_NORMAL: - Alignment = ""; - break; - case PTC_MEMORY_ACCESS_UNALIGNED: - Alignment = "un+"; - break; - case PTC_MEMORY_ACCESS_ALIGNED: - Alignment = "al+"; - break; - default: - return EXIT_FAILURE; - } - - if (LoadStoreName == nullptr) - return EXIT_FAILURE; - - Result << "," << Alignment << LoadStoreName; - } - - Result << "," << LoadStoreArg.mmu_index; - - /* Consume one argument */ - I++; - - } break; - default: - break; - } - - switch (Opcode) { - case PTC_INSTRUCTION_op_set_label: - case PTC_INSTRUCTION_op_br: - case PTC_INSTRUCTION_op_brcond_i32: - case PTC_INSTRUCTION_op_brcond_i64: - case PTC_INSTRUCTION_op_brcond2_i32: { - PTCInstructionArg Arg = ptc_instruction_const_arg(&ptc, &Instruction, I); - Result << "," - << "$L" << ptc.get_arg_label_id(Arg); - - /* Consume one more argument */ - I++; - break; - } - default: - break; - } - - /* Print remaining const arguments */ - for (; I < ptc_instruction_const_arg_count(&ptc, &Instruction); I++) { - if (I != 0) { - Result << ","; - } - - Result << "$0x" << std::hex - << ptc_instruction_const_arg(&ptc, &Instruction, I); - } - } - - return EXIT_SUCCESS; -} - -void disassemble(std::ostream &Result, - MetaAddress PC, - uint32_t MaxBytes, - uint32_t InstructionCount) { - char *BufferPtr = nullptr; - size_t BufferLenPtr = 0; - FILE *MemoryStream = open_memstream(&BufferPtr, &BufferLenPtr); - - revng_assert(MemoryStream != nullptr); - - // Using SIZE_MAX is not very nice but the code should disassemble only a - // single instruction nonetheless. - ptc.disassemble(MemoryStream, PC.asPC(), MaxBytes, InstructionCount); - fflush(MemoryStream); - - revng_assert(BufferPtr != nullptr); - - Result << BufferPtr; - - fclose(MemoryStream); - free(BufferPtr); -} - -int dumpTranslation(MetaAddress VirtualAddress, - std::ostream &Result, - PTCInstructionList *Instructions) { - // TODO: this should stay in Architecture - int Is64 = 0; - - for (unsigned Index = 0; Index < Instructions->instruction_count; Index++) { - PTCInstruction &Instruction = Instructions->instructions[Index]; - PTCOpcode Opcode = Instruction.opc; - - if (Opcode == PTC_INSTRUCTION_op_debug_insn_start) { - uint64_t PC = Instruction.args[0]; - - if (Is64) - PC |= Instruction.args[1] << 32; - - disassemble(Result, VirtualAddress.replaceAddress(PC), 4096, 1); - } - - Result << std::dec << Index << ": "; - - if (dumpInstruction(Result, Instructions, Index) == EXIT_FAILURE) - return EXIT_FAILURE; - - Result << std::endl; - } - - return EXIT_SUCCESS; -} diff --git a/lib/Lift/PTCInterface.h b/lib/Lift/PTCInterface.h deleted file mode 100644 index 9454c7a44..000000000 --- a/lib/Lift/PTCInterface.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -// -// This file is distributed under the MIT License. See LICENSE.md for details. -// - -#include -#include - -#define USE_DYNAMIC_PTC -#include "ptc.h" - -template -using PTCDestructorWrapper = std::integral_constant; - -inline void ptcInstructionListDestructor(PTCInstructionList *This) { - ptc_instruction_list_free(This); - delete This; -} - -using PTCDestructor = PTCDestructorWrapper<&ptcInstructionListDestructor>; - -using PTCInstructionListPtr = std::unique_ptr; - -extern PTCInterface ptc; diff --git a/lib/Lift/PostLiftVerifyPass.cpp b/lib/Lift/PostLiftVerifyPass.cpp new file mode 100644 index 000000000..92193870e --- /dev/null +++ b/lib/Lift/PostLiftVerifyPass.cpp @@ -0,0 +1,125 @@ +/// \file PostLiftVerifyPass.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/ModuleSlotTracker.h" + +#include "revng/Support/Assert.h" + +#include "PostLiftVerifyPass.h" + +using namespace llvm; + +bool PostLiftVerifyPass::runOnModule(Module &M) { + Function *RootFunction = M.getFunction("root"); + revng_assert(RootFunction != nullptr); + + llvm::ModuleSlotTracker MST(&M, false); + + for (BasicBlock &BB : *RootFunction) { + // Ignore some special basic blocks + if (BB.getName() == "dispatcher.default" + or BB.getName() == "serialize_and_jump_out" + or BB.getName() == "return_from_external" or BB.getName() == "setjmp" + or BB.getName() == "dispatcher.external") + continue; + + for (Instruction &I : BB) { + bool Good = false; + + switch (I.getOpcode()) { + case Instruction::Store: + case Instruction::Load: + Good = true; + break; + + case Instruction::IntToPtr: + case Instruction::Add: + case Instruction::Sub: + case Instruction::Mul: + case Instruction::UDiv: + case Instruction::SDiv: + case Instruction::URem: + case Instruction::SRem: + case Instruction::And: + case Instruction::Or: + case Instruction::Xor: + case Instruction::ZExt: + case Instruction::Trunc: + case Instruction::SExt: + case Instruction::ICmp: + case Instruction::LShr: + case Instruction::AShr: + case Instruction::Shl: + case Instruction::Select: + Good = true; + break; + + case Instruction::Br: + case Instruction::Switch: + case Instruction::Unreachable: + Good = true; + break; + + case Instruction::PHI: + Good = true; + break; + + case Instruction::ExtractValue: + Good = true; + break; + + case Instruction::Call: + // Make further checks + auto *Call = cast(&I); + Value *CalledOperand = Call->getCalledOperand(); + Function *Callee = dyn_cast_or_null(CalledOperand); + StringRef CalleeName; + if (Callee != nullptr) + CalleeName = Callee->getName(); + + Good = (CalleeName == "newpc" or CalleeName == "jump_to_symbol" + or CalleeName.startswith("helper_") + or CalleeName == "function_call" + or CalleeName == "helper_initialize_env" + or CalleeName == "revng_abort"); + + switch (Callee->getIntrinsicID()) { + case Intrinsic::fshl: + case Intrinsic::fshr: + case Intrinsic::bswap: + case Intrinsic::abs: + case Intrinsic::umin: + case Intrinsic::umax: + case Intrinsic::smin: + case Intrinsic::smax: + case Intrinsic::ctlz: + case Intrinsic::cttz: + Good = true; + } + + break; + } + + if (not Good) { + std::string Buffer; + { + llvm::raw_string_ostream Stream(Buffer); + Stream << "Unexpected instruction: "; + I.print(Stream, MST); + Stream << "\n"; + } + revng_abort(Buffer.c_str()); + } + } + } + + return false; +} + +char PostLiftVerifyPass::ID; diff --git a/lib/Lift/PostLiftVerifyPass.h b/lib/Lift/PostLiftVerifyPass.h new file mode 100644 index 000000000..16e31f6b6 --- /dev/null +++ b/lib/Lift/PostLiftVerifyPass.h @@ -0,0 +1,18 @@ +#pragma once + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/Pass.h" + +class PostLiftVerifyPass : public llvm::ModulePass { +public: + static char ID; + +public: + PostLiftVerifyPass() : llvm::ModulePass(ID) {} + +public: + bool runOnModule(llvm::Module &M) final; +}; diff --git a/lib/Lift/RootAnalyzer.cpp b/lib/Lift/RootAnalyzer.cpp index 683f6ce40..d854f7dfd 100644 --- a/lib/Lift/RootAnalyzer.cpp +++ b/lib/Lift/RootAnalyzer.cpp @@ -4,18 +4,25 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include + #include "llvm/ADT/DepthFirstIterator.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Analysis/BasicAliasAnalysis.h" #include "llvm/Analysis/ScopedNoAliasAA.h" #include "llvm/CodeGen/UnreachableBlockElim.h" +#include "llvm/IR/Function.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Metadata.h" #include "llvm/IR/PatternMatch.h" #include "llvm/IR/Verifier.h" +#include "llvm/IRPrinter/IRPrintingPasses.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Transforms/InstCombine/InstCombine.h" #include "llvm/Transforms/Scalar/EarlyCSE.h" #include "llvm/Transforms/Scalar/JumpThreading.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" #include "llvm/Transforms/Utils/Cloning.h" #include "llvm/Transforms/Utils/Mem2Reg.h" @@ -23,7 +30,6 @@ #include "revng/ABI/FunctionType/Layout.h" #include "revng/BasicAnalyses/ShrinkInstructionOperandsPass.h" #include "revng/FunctionCallIdentification/FunctionCallIdentification.h" -#include "revng/Lift/CPUStateAccessAnalysisPass.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/OpaqueRegisterUser.h" #include "revng/Support/Statistics.h" @@ -333,15 +339,6 @@ RootAnalyzer::MetaAddressSet RootAnalyzer::inflateValueMaterializerWhitelist() { return Result; } -// Update CPUStateAccessAnalysisPass -void RootAnalyzer::updateCSAA() { - legacy::PassManager PM; - PM.add(new LoadModelWrapperPass(ModelWrapper::createConst(Model))); - PM.add(JTM.createCSAA()); - PM.add(new FunctionCallIdentification); - PM.run(TheModule); -} - static llvm::SmallSet getPreservedRegisters(const model::TypeDefinition &Prototype) { llvm::SmallSet Result; @@ -361,22 +358,23 @@ Function *RootAnalyzer::createTemporaryRoot(Function *TheFunction, llvm::DenseSet Callees; llvm::DenseMap Undo; auto *FunctionCall = getIRHelper("function_call", TheModule); - revng_assert(FunctionCall != nullptr); - for (CallBase *Call : callers(FunctionCall)) { - auto *T = Call->getParent()->getTerminator(); + if (FunctionCall) { + for (CallBase *Call : callers(FunctionCall)) { + auto *T = Call->getParent()->getTerminator(); - Callees.insert(getFunctionCallCallee(Call->getParent())); + Callees.insert(getFunctionCallCallee(Call->getParent())); - if (auto *Branch = dyn_cast(T)) { - revng_assert(Branch->isUnconditional()); - BasicBlock *Target = Branch->getSuccessor(0); - Use *U = &Branch->getOperandUse(0); + if (auto *Branch = dyn_cast(T)) { + revng_assert(Branch->isUnconditional()); + BasicBlock *Target = Branch->getSuccessor(0); + Use *U = &Branch->getOperandUse(0); - // We're after a function call: pretend we're jumping to anypc - U->set(JTM.anyPC()); + // We're after a function call: pretend we're jumping to anypc + U->set(JTM.anyPC()); - // Record Use for later undoing - Undo[U] = Target; + // Record Use for later undoing + Undo[U] = Target; + } } } @@ -449,8 +447,12 @@ Function *RootAnalyzer::createTemporaryRoot(Function *TheFunction, OpaqueRegisterUser Clobberer(M); SmallVector FunctionCallCalls; - llvm::copy(callersIn(FunctionCall, OptimizedFunction), - std::back_inserter(FunctionCallCalls)); + + if (FunctionCall != nullptr) { + llvm::copy(callersIn(FunctionCall, OptimizedFunction), + std::back_inserter(FunctionCallCalls)); + } + for (CallBase *Call : FunctionCallCalls) { Builder.SetInsertPoint(Call); @@ -528,17 +530,16 @@ RootAnalyzer::promoteCSVsToAlloca(Function *OptimizedFunction) { GlobalToAllocaTy CSVMap; // Collect all the non-PC affecting CSVs - DenseSet NonPCCSVs; + DenseSet CSVs; for (GlobalVariable &CSV : FunctionTags::CSV.globals(&TheModule)) - if (not JTM.programCounterHandler()->affectsPC(&CSV)) - NonPCCSVs.insert(&CSV); + CSVs.insert(&CSV); // Create and initialize an alloca per CSV (except for the PC-affecting ones) BasicBlock *EntryBB = &OptimizedFunction->getEntryBlock(); revng::NonDebugInfoCheckingIRBuilder AllocaBuilder(&*EntryBB->begin()); revng::NonDebugInfoCheckingIRBuilder InitBuilder(EntryBB->getTerminator()); - for (GlobalVariable *CSV : toSortedByName(NonPCCSVs)) { + for (GlobalVariable *CSV : toSortedByName(CSVs)) { Type *CSVType = CSV->getValueType(); auto *Alloca = AllocaBuilder.CreateAlloca(CSVType, nullptr, CSV->getName()); CSVMap[CSV] = Alloca; @@ -643,6 +644,8 @@ SummaryCallsBuilder RootAnalyzer::optimize(llvm::Function *OptimizedFunction, // instructions and have more accurate constraints. FPM.addPass(EarlyCSEPass(true)); + FPM.addPass(SimplifyCFGPass()); + // Drop range metadata FPM.addPass(DropRangeMetadataPass()); @@ -676,27 +679,60 @@ SummaryCallsBuilder RootAnalyzer::optimize(llvm::Function *OptimizedFunction, } void RootAnalyzer::collectMaterializedValues(AnalysisRegistry &AR) { + auto &Context = TheModule.getContext(); + QuickMetadata QMD(Context); + // Iterate over all the ValueMaterializer markers Function *ValueMaterializerMarker = AR.aviMarker(); + + // TODO: we could use a better data structure here + struct Entry { + StringRef SymbolName; + ConstantInt *Value = nullptr; + std::strong_ordering operator<=>(const Entry &Other) const = default; + }; + std::map> MaterializedValuesById; + + // Collect materialized values + // Note: there could be multiple calls to the marker with the same ID for (CallBase *Call : callers(ValueMaterializerMarker)) { revng_log(Log, "collectMaterializedValues on " << getName(Call)); - LoggerIndent<> Indent(Log); // Get the ID from the marker, and then the original instruction and marker // type Value *LastArgument = Call->getArgOperand(Call->arg_size() - 1); uint32_t ValueMaterializerID = getLimitedValue(LastArgument); - auto TV = AR.rootInstructionById(ValueMaterializerID); - auto TIT = TV.Type; - Instruction *I = TV.I; - - revng_log(Log, TrackedInstructionType::getName(TIT)); // Did ValueMaterializer produce any info? auto *T = dyn_cast_or_null(Call->getMetadata("revng.avi")); if (T == nullptr) continue; + auto &Operands = MaterializedValuesById[ValueMaterializerID]; + for (const MDOperand &Operand : cast(T)->operands()) { + // Extract the value + auto *Tuple = QMD.extract(Operand.get()); + auto SymbolName = QMD.extract(Tuple->getOperand(0).get()); + auto *Value = QMD.extract(Tuple->getOperand(1).get()); + Operands.push_back({ SymbolName, Value }); + } + } + + for (auto &[ValueMaterializerID, Operands] : MaterializedValuesById) { + revng_log(Log, "Parsing ID " << ValueMaterializerID); + LoggerIndent<> Indent(Log); + + // Remove duplicates + llvm::sort(Operands); + Operands.erase(std::unique(Operands.begin(), Operands.end()), + Operands.end()); + + auto TV = AR.rootInstructionById(ValueMaterializerID); + auto TIT = TV.Type; + Instruction *I = TV.I; + + revng_log(Log, TrackedInstructionType::getName(TIT)); + // Is this a direct write to PC? bool IsComposedIntegerPC = (TIT == TrackedInstructionType::WrittenInPC); @@ -705,15 +741,9 @@ void RootAnalyzer::collectMaterializedValues(AnalysisRegistry &AR) { bool AllPCs = true; SmallVector Targets; - QuickMetadata QMD(TheModule.getContext()); // Iterate over all the generated values - for (const MDOperand &Operand : cast(T)->operands()) { - // Extract the value - auto *Tuple = QMD.extract(Operand.get()); - auto SymbolName = QMD.extract(Tuple->getOperand(0).get()); - auto *Value = QMD.extract(Tuple->getOperand(1).get()); - + for (const auto &[SymbolName, Value] : Operands) { bool HasDynamicSymbol = SymbolName.size() != 0; if (not HasDynamicSymbol) { // Deserialize value into a MetaAddress, depending on the tracked @@ -795,10 +825,17 @@ void RootAnalyzer::collectMaterializedValues(AnalysisRegistry &AR) { // This is a call to `exit_tb`, transfer the revng.avi metadata on the // call as revng.targets for later processing revng_assert(TV.I != nullptr); - TV.I->setMetadata("revng.targets", T); + + // Compose the metadata for revng.targets + SmallVector NewOperands; + for (const auto &[SymbolName, Value] : Operands) + NewOperands.push_back(QMD.tuple({ QMD.get(SymbolName), + QMD.get(Value) })); + + TV.I->setMetadata("revng.targets", MDTuple::get(Context, NewOperands)); DetectedEdgesStatistics.push(Targets.size()); revng_log(NewEdgesLog, - Targets.size() << " targets from " << getName(Call)); + Targets.size() << " targets from #" << ValueMaterializerID); } } } @@ -843,7 +880,11 @@ static MetaAddress::Features findCommonFeatures(Function *F) { } void RootAnalyzer::cloneOptimizeAndHarvest(Function *TheFunction) { - updateCSAA(); + // Re-run the identification of function calls + legacy::PassManager PM; + PM.add(new LoadModelWrapperPass(ModelWrapper::createConst(Model))); + PM.add(new FunctionCallIdentification); + PM.run(TheModule); ValueToValueMapTy OldToNew; Function *OptimizedFunction = createTemporaryRoot(TheFunction, OldToNew); diff --git a/lib/Lift/RootAnalyzer.h b/lib/Lift/RootAnalyzer.h index b8352541b..2d2687234 100644 --- a/lib/Lift/RootAnalyzer.h +++ b/lib/Lift/RootAnalyzer.h @@ -40,8 +40,6 @@ public: void cloneOptimizeAndHarvest(llvm::Function *TheFunction); private: - void updateCSAA(); - llvm::Function *createTemporaryRoot(llvm::Function *TheFunction, llvm::ValueToValueMapTy &OldToNew); diff --git a/lib/Lift/ValueMaterializerPass.cpp b/lib/Lift/ValueMaterializerPass.cpp index 23c7d48ee..e1c2cd614 100644 --- a/lib/Lift/ValueMaterializerPass.cpp +++ b/lib/Lift/ValueMaterializerPass.cpp @@ -6,12 +6,15 @@ #include "llvm/Analysis/LazyValueInfo.h" #include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/Attributes.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/KnownBits.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" #include "revng/Support/Debug.h" #include "revng/Support/IRHelperRegistry.h" +#include "revng/Support/IRHelpers.h" +#include "revng/ValueMaterializer/DataFlowRangeAnalysis.h" #include "revng/ValueMaterializer/ValueMaterializer.h" #include "JumpTargetManager.h" @@ -79,6 +82,8 @@ PreservedAnalyses ValueMaterializerPass::run(Function &F, FunctionAnalysisManager &FAM) { using namespace llvm; + DataFlowRangeAnalysis DFRA(*F.getParent()); + llvm::EliminateUnreachableBlocks(F, nullptr, false); demoteOrToAdd(F); @@ -92,9 +97,6 @@ PreservedAnalyses ValueMaterializerPass::run(Function &F, auto &DT = FAM.getResult(F); BasicBlock *Entry = &F.getEntryBlock(); - SwitchInst *Terminator = cast(Entry->getTerminator()); - BasicBlock *Dispatcher = Terminator->getDefaultDest(); - auto GetConstantArgument = [](CallBase *Call, unsigned Index) { return cast(Call->getArgOperand(Index))->getLimitedValue(); }; @@ -120,6 +122,7 @@ PreservedAnalyses ValueMaterializerPass::run(Function &F, ToTrack, MO, LVI, + DFRA, DT, Limits, Oracle); diff --git a/lib/Lift/VariableManager.cpp b/lib/Lift/VariableManager.cpp index 8af8bef8a..f2e2d2196 100644 --- a/lib/Lift/VariableManager.cpp +++ b/lib/Lift/VariableManager.cpp @@ -12,9 +12,14 @@ #include #include +#include "qemu/libtcg/libtcg.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/Intrinsics.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/Module.h" #include "llvm/IR/Type.h" @@ -22,28 +27,20 @@ #include "llvm/Transforms/Utils/Cloning.h" #include "llvm/Transforms/Utils/ValueMapper.h" -#include "revng/Lift/PTCDump.h" #include "revng/Lift/VariableManager.h" #include "revng/Model/FunctionTags.h" +#include "revng/Model/Register.h" +#include "revng/Support/Assert.h" +#include "revng/Support/CommandLine.h" #include "revng/Support/Debug.h" #include "revng/Support/IRHelpers.h" -#include "PTCInterface.h" - // This name corresponds to a function in `early-linked`. RegisterIRHelper SetRegisterMarker("set_register"); -using namespace llvm; +static Logger<> Log("csv-at-offset"); -// TODO: rename -cl::opt External("external", - cl::desc("set CSVs linkage to external, useful for " - "debugging purposes"), - cl::cat(MainCategory)); -static cl::alias A1("E", - cl::desc("Alias for -external"), - cl::aliasopt(External), - cl::cat(MainCategory)); +using namespace llvm; class OffsetValueStack { @@ -86,47 +83,29 @@ private: static std::pair getTypeAtOffset(const DataLayout *TheLayout, Type *VarType, intptr_t Offset) { - static Logger<> Log("type-at-offset"); - - unsigned Depth = 0; - while (1) { + std::string Prefix = ""; + while (true) { switch (VarType->getTypeID()) { case llvm::Type::TypeID::PointerTyID: - // BEWARE: here we return { nullptr, 0 } as an intended workaround for - // a specific situation. - // - // We can't use assertions on pointers, as we do for all the other - // unhandled types, because they will be inevitably triggered during the - // execution. Indeed, all the other types are not present in QEMU - // CPUState and we can safely assert it. This is not true for pointers - // that are used in different places in QEMU CPUState. - // - // Given that we have ruled out assertions, we need to handle the - // pointer case so that it keeps working. This function is expected to - // return { nullptr, 0 } when the offset points to a memory location - // associated to padding space. In principle, pointers are not padding - // space, but the result of returning { nullptr, 0 } here is that load - // and store operations treat pointers like padding. This means that - // pointers cannot be read or written, and memcpy simply skips over them - // leaving them alone. - // - // This behavior is intended, because a pointer into the CPUState could - // be used to modify CPU registers indirectly, which is against all the - // assumption of the analysis necessary for the translation, and also - // against what really happens in a CPU, where CPU state cannot be - // addressed. + // Ignore pointers + revng_log(Log, Prefix << "Found a pointer. Bailing out."); return { nullptr, 0 }; case llvm::Type::TypeID::IntegerTyID: + revng_log(Log, Prefix << "Found a i" << VarType->getIntegerBitWidth()); return { cast(VarType), Offset }; - case llvm::Type::TypeID::ArrayTyID: + case llvm::Type::TypeID::ArrayTyID: { + auto ElementsCount = VarType->getArrayNumElements(); VarType = VarType->getArrayElementType(); - Offset %= TheLayout->getTypeAllocSize(VarType); + auto TypeSize = TheLayout->getTypeAllocSize(VarType); + auto Index = Offset / TypeSize; + Offset %= TypeSize; revng_log(Log, - std::string(Depth++ * 2, ' ') - << " Is an Array. Offset in Element: " << Offset); - break; + Prefix << "Element " << Index << " in an array of " + << ElementsCount + << " elements. Offset in Element: " << Offset); + } break; case llvm::Type::TypeID::StructTyID: { StructType *TheStruct = cast(VarType); @@ -137,41 +116,61 @@ getTypeAtOffset(const DataLayout *TheLayout, Type *VarType, intptr_t Offset) { intptr_t FieldEnd = FieldOffset + TheLayout->getTypeAllocSize(VarType); revng_log(Log, - std::string(Depth++ * 2, ' ') - << " Offset: " << Offset - << " Struct Name: " << TheStruct->getName().str() - << " Field Index: " << FieldIndex << " Field offset: " - << FieldOffset << " Field end: " << FieldEnd); + Prefix << "Offset: " << Offset + << "; Struct Name: " << TheStruct->getName().str() + << "; Field Index: " << FieldIndex << "; Field offset: " + << FieldOffset << "; Field end: " << FieldEnd << "."); - if (Offset >= FieldEnd) - return { nullptr, 0 }; // It's padding + if (Offset >= FieldEnd) { + revng_log(Log, Prefix << "It's padding. Bailing out."); + return { nullptr, 0 }; + } Offset -= FieldOffset; } break; default: - revng_abort("unexpected TypeID"); + revng_abort("Unexpected TypeID"); } + + Prefix += " "; } } VariableManager::VariableManager(Module &M, bool TargetIsLittleEndian, - StructType *CPUStruct, - unsigned EnvOffset) : + unsigned LibTcgEnvOffset, + uint8_t *LibTcgEnvPtr, + const std::map + &GlobalNames) : TheModule(M), AllocaBuilder(getContext(&M)), - CPUStateType(CPUStruct), + ArchCPUStruct(nullptr), ModuleLayout(&TheModule.getDataLayout()), - EnvOffset(EnvOffset), + LibTcgEnvOffset(LibTcgEnvOffset), + LibTcgEnvPtr(LibTcgEnvPtr), Env(nullptr), - TargetIsLittleEndian(TargetIsLittleEndian) { + TargetIsLittleEndian(TargetIsLittleEndian), + GlobalNames(GlobalNames) { - revng_assert(ptc.initialized_env != nullptr); + // Reminder: + // + // struct ArchCPU { + // CPUState parent_obj; + // CPUX86State /* aka CPUArchState */ env; + // }; + + // TODO: this not very robust. We should have a function with a sensible name + // taking as argument ${ARCH}CPU so that we can easily identify the + // struct. + ArchCPUStruct = StructType::getTypeByName(M.getContext(), "struct.ArchCPU"); + revng_assert(ArchCPUStruct != nullptr); + + revng_assert(LibTcgEnvPtr != nullptr); IntegerType *IntPtrTy = AllocaBuilder.getIntPtrTy(*ModuleLayout); Env = cast(TheModule.getOrInsertGlobal("env", IntPtrTy)); - Env->setInitializer(ConstantInt::getNullValue(IntPtrTy)); + Env->setInitializer(ConstantInt::get(IntPtrTy, LibTcgEnvOffset)); } std::optional @@ -181,7 +180,7 @@ VariableManager::storeToCPUStateOffset(revng::IRBuilder &Builder, Value *ToStore) { GlobalVariable *Target = nullptr; unsigned Remaining; - std::tie(Target, Remaining) = getByCPUStateOffsetInternal(Offset); + std::tie(Target, Remaining) = getByCPUStateOffsetWithRemainder(Offset); if (Target == nullptr) return {}; @@ -213,7 +212,7 @@ VariableManager::storeToCPUStateOffset(revng::IRBuilder &Builder, if (StoreSize > FieldSize) { // If we're storing more than it fits and the following memory is not // padding the store is not valid. - if (getByCPUStateOffsetInternal(Offset + FieldSize).first != nullptr) + if (getByCPUStateOffsetWithRemainder(Offset + FieldSize).first != nullptr) return {}; } @@ -246,7 +245,7 @@ Value *VariableManager::loadFromCPUStateOffset(revng::IRBuilder &Builder, unsigned Offset) { GlobalVariable *Target = nullptr; unsigned Remaining; - std::tie(Target, Remaining) = getByCPUStateOffsetInternal(Offset); + std::tie(Target, Remaining) = getByCPUStateOffsetWithRemainder(Offset); if (Target == nullptr) return nullptr; @@ -280,7 +279,7 @@ Value *VariableManager::loadFromCPUStateOffset(revng::IRBuilder &Builder, if (FieldSize < LoadSize) { // If after what we are loading there is something that is not padding we // cannot load safely - if (getByCPUStateOffsetInternal(Offset + FieldSize).first != nullptr) + if (getByCPUStateOffsetWithRemainder(Offset + FieldSize).first != nullptr) return nullptr; Result = Builder.CreateZExt(Result, LoadTy); } @@ -290,19 +289,24 @@ Value *VariableManager::loadFromCPUStateOffset(revng::IRBuilder &Builder, return Builder.CreateTrunc(Result, LoadTy); } -bool VariableManager::memcpyAtEnvOffset(revng::IRBuilder &Builder, - llvm::CallInst *CallMemcpy, - unsigned InitialEnvOffset, - bool EnvIsSrc) { - Function *Callee = getCallee(CallMemcpy); +void VariableManager::memOpAtEnvOffset(revng::IRBuilder &Builder, + llvm::CallInst *Call, + unsigned InitialEnvOffset, + bool EnvIsSrc) { + Function *Callee = getCallee(Call); // We only support memcpys where the last parameter is constant - revng_assert(Callee != nullptr - and (Callee->getIntrinsicID() == Intrinsic::memcpy - and isa(CallMemcpy->getArgOperand(2)))); + revng_assert(Callee != nullptr); + bool IsMemset = Callee->getIntrinsicID() == Intrinsic::memset; + revng_assert(Callee->getIntrinsicID() == Intrinsic::memcpy + or Callee->getIntrinsicID() == Intrinsic::memmove or IsMemset); + revng_assert(isa(Call->getArgOperand(2))); - Value *OtherOp = CallMemcpy->getArgOperand(EnvIsSrc ? 0 : 1); - auto *MemcpySize = cast(CallMemcpy->getArgOperand(2)); - Value *OtherBasePtr = Builder.CreatePtrToInt(OtherOp, Builder.getInt64Ty()); + Value *OtherOp = Call->getArgOperand(EnvIsSrc ? 0 : 1); + auto *MemcpySize = cast(Call->getArgOperand(2)); + Value *OtherBasePtr = nullptr; + + if (not IsMemset) + OtherBasePtr = Builder.CreatePtrToInt(OtherOp, Builder.getInt64Ty()); uint64_t TotalSize = getZExtValue(MemcpySize, *ModuleLayout); uint64_t Offset = 0; @@ -313,32 +317,36 @@ bool VariableManager::memcpyAtEnvOffset(revng::IRBuilder &Builder, // Consider the case when there's simply nothing there (alignment space). if (EnvVar == nullptr) { - // TODO: remove "false and", but after adding type based stuff - if (false && EnvIsSrc) { - ConstantInt *ZeroByte = Builder.getInt8(0); - ConstantInt *OffsetInt = Builder.getInt64(Offset); - Value *NewAddress = Builder.CreateAdd(OffsetInt, OtherBasePtr); - Type *Int8PtrTy = Builder.getInt8Ty()->getPointerTo(); - Value *OtherPtr = Builder.CreateIntToPtr(NewAddress, Int8PtrTy); - Builder.CreateStore(ZeroByte, OtherPtr); - OnlyPointersAndPadding = false; - } Offset++; continue; } OnlyPointersAndPadding = false; ConstantInt *OffsetInt = Builder.getInt64(Offset); - Value *NewAddress = Builder.CreateAdd(OffsetInt, OtherBasePtr); - Value *OtherPtr = Builder.CreateIntToPtr(NewAddress, EnvVar->getType()); + Value *NewAddress = nullptr; + Value *OtherPtr = nullptr; + + if (not IsMemset) { + NewAddress = Builder.CreateAdd(OffsetInt, OtherBasePtr); + OtherPtr = Builder.CreateIntToPtr(NewAddress, EnvVar->getType()); + } StoreInst *New = nullptr; if (EnvIsSrc) { + revng_assert(not IsMemset); New = Builder.CreateStore(createLoad(Builder, EnvVar), OtherPtr); } else { - New = Builder.CreateStore(Builder.CreateLoad(EnvVar->getValueType(), - OtherPtr), - EnvVar); + Type *CSVType = EnvVar->getValueType(); + Value *ToStore = nullptr; + if (IsMemset) { + // TODO: handle non-zero memset + Value *SetValue = Call->getArgOperand(1); + revng_assert(cast(SetValue)->getValue().isZero()); + ToStore = ConstantInt::get(CSVType, 0); + } else { + ToStore = Builder.CreateLoad(EnvVar->getValueType(), OtherPtr); + } + New = Builder.CreateStore(ToStore, EnvVar); } if (auto *GV = dyn_cast(New->getPointerOperand())) { @@ -352,19 +360,12 @@ bool VariableManager::memcpyAtEnvOffset(revng::IRBuilder &Builder, if (OnlyPointersAndPadding) eraseFromParent(cast(OtherBasePtr)); - return Offset == TotalSize; + revng_assert(Offset == TotalSize); } void VariableManager::finalize() { LLVMContext &Context = getContext(&TheModule); - if (not External) { - for (auto &P : CPUStateGlobals) - P.second->setLinkage(GlobalValue::InternalLinkage); - for (auto &P : OtherGlobals) - P.second->setLinkage(GlobalValue::InternalLinkage); - } - revng::NonDebugInfoCheckingIRBuilder Builder(Context); // Create the setRegister function @@ -423,14 +424,6 @@ void VariableManager::finalize() { Builder.CreateRetVoid(); } -// TODO: `newFunction` reflects the tcg terminology but in this context is -// highly misleading -void VariableManager::newFunction(PTCInstructionList *Instructions) { - LocalTemporaries.clear(); - this->Instructions = Instructions; - newBasicBlock(); -} - bool VariableManager::isEnv(Value *TheValue) { auto *Load = dyn_cast(TheValue); if (Load != nullptr) @@ -455,136 +448,172 @@ static ConstantInt *fromBytes(IntegerType *Type, void *Data) { } // TODO: document that it can return nullptr -GlobalVariable *VariableManager::getByCPUStateOffset(intptr_t Offset, - std::string Name) { +GlobalVariable *VariableManager::getByCPUStateOffset(intptr_t Offset) { GlobalVariable *Result = nullptr; unsigned Remaining; - std::tie(Result, Remaining) = getByCPUStateOffsetInternal(Offset, Name); + std::tie(Result, Remaining) = getByCPUStateOffsetWithRemainder(Offset); revng_assert(Remaining == 0); return Result; } -std::pair -VariableManager::getByCPUStateOffsetInternal(intptr_t Offset, - std::string Name) { - GlobalsMap::iterator It = CPUStateGlobals.find(Offset); - static const char *UnknownCSVPref = "state_0x"; - if (It == CPUStateGlobals.end() - || (Name.size() != 0 - && It->second->getName().startswith(UnknownCSVPref))) { - Type *VariableType = nullptr; - unsigned Remaining; - std::tie(VariableType, - Remaining) = getTypeAtOffset(ModuleLayout, CPUStateType, Offset); +std::optional> +VariableManager::getGlobalByCPUStateOffset(intptr_t Offset) const { + auto It = CPUStateGlobals.upper_bound(Offset); - // Unsupported type, let the caller handle the situation - if (VariableType == nullptr) - return { nullptr, 0 }; + // If we're earlier than the first one, bail out + if (It == CPUStateGlobals.begin()) + return std::nullopt; - // Check we're not trying to go inside an existing variable - if (Remaining != 0) { - GlobalsMap::iterator It = CPUStateGlobals.find(Offset - Remaining); - if (It != CPUStateGlobals.end()) - return { It->second, Remaining }; - } + // Move back of one position + --It; - if (Name.size() == 0) { - std::stringstream NameStream; - NameStream << UnknownCSVPref << std::hex << Offset; - Name = NameStream.str(); - } + // Compute GlobalVariable size + intptr_t Size = It->second->getValueType()->getIntegerBitWidth() / 8; + revng_assert(Size != 0); - // TODO: offset could be negative, we could segfault here - auto *InitialValue = fromBytes(cast(VariableType), - ptc.initialized_env - EnvOffset + Offset); - - auto *NewVariable = new GlobalVariable(TheModule, - VariableType, - false, - GlobalValue::ExternalLinkage, - InitialValue, - Name); - revng_assert(NewVariable != nullptr); - FunctionTags::CSV.addTo(NewVariable); - - if (It != CPUStateGlobals.end()) { - It->second->replaceAllUsesWith(NewVariable); - eraseFromParent(It->second); - } - - CPUStateGlobals[Offset] = NewVariable; - - return { NewVariable, Remaining }; - } else { - return { It->second, 0 }; + // Check if we're within the variable + if (It->first <= Offset and Offset < (It->first + Size)) { + // Return the global + offset within it + return { { It->second, Offset - It->first } }; } + + return std::nullopt; } -std::pair VariableManager::getOrCreate(unsigned TemporaryId, - bool Reading) { - revng_assert(Instructions != nullptr); +std::pair +VariableManager::getByCPUStateOffsetWithRemainder(intptr_t Offset) { - PTCTemp *Temporary = ptc_temp_get(Instructions, TemporaryId); - Type *VariableType = Temporary->type == PTC_TYPE_I32 ? + // Check if we already created a variable for this offset + if (auto MaybeResult = getGlobalByCPUStateOffset(Offset)) + return MaybeResult.value(); + + revng_log(Log, "Considering offset " << Offset); + LoggerIndent<> Indent(Log); + + // Get the type of the field at that offset (if any) and obtain the offset + // within the field + auto [VariableType, + Remaining] = getTypeAtOffset(ModuleLayout, ArchCPUStruct, Offset); + + // Unsupported type, let the caller handle the situation + if (VariableType == nullptr) { + revng_log(Log, "Unsupported bailing out."); + return { nullptr, 0 }; + } + + // Compute the actual start offset (discarding the offset within the global) + auto GlobalOffset = Offset - Remaining; + revng_assert(not CPUStateGlobals.contains(GlobalOffset)); + + // Compute the name + auto NameIt = GlobalNames.find(GlobalOffset); + std::string Name; + if (NameIt != GlobalNames.end()) { + Name = "_" + NameIt->second.str(); + } else { + static const char *UnknownCSVPrefix = "_state_0x"; + Name = UnknownCSVPrefix + utohexstr(GlobalOffset, true); + } + revng_log(Log, "Name " << Name); + + // TODO: if this is CSV, check it's of the correct size we expect + + // Check if a previous VariableManager has already created this variable + if (auto *Result = TheModule.getGlobalVariable(Name, true)) { + revng_log(Log, "It already exists."); + // Check the variable looks like what we'd create + revng_assert(Result->getValueType() == VariableType); + revng_assert(FunctionTags::CSV.isTagOf(Result)); + revng_assert(Result->hasInitializer()); + revng_assert(not Result->isConstant()); + revng_assert(Result->getLinkage() == GlobalValue::ExternalLinkage); + + // Record and return it + CPUStateGlobals[GlobalOffset] = Result; + return { Result, Remaining }; + } + + revng_log(Log, "Creating."); + + auto InitializerPointer = LibTcgEnvPtr - LibTcgEnvOffset + GlobalOffset; + revng_assert(InitializerPointer >= LibTcgEnvPtr - LibTcgEnvOffset); + auto *InitialValue = fromBytes(cast(VariableType), + InitializerPointer); + + // Create the global + auto *NewVariable = new GlobalVariable(TheModule, + VariableType, + false, + GlobalValue::ExternalLinkage, + InitialValue, + Name); + FunctionTags::CSV.addTo(NewVariable); + + // Register the variable + CPUStateGlobals[GlobalOffset] = NewVariable; + + return { NewVariable, Remaining }; +} + +std::pair VariableManager::getOrCreate(LibTcgArgument *Argument, + bool Reading) { + Type *VariableType = Argument->temp->type == LIBTCG_TYPE_I32 ? AllocaBuilder.getInt32Ty() : AllocaBuilder.getInt64Ty(); - if (ptc_temp_is_global(Instructions, TemporaryId)) { - // Basically we use fixed_reg to detect "env" - if (Temporary->fixed_reg == 0) { - Value *Result = getByCPUStateOffset(EnvOffset + Temporary->mem_offset, - Temporary->name); - revng_assert(Result != nullptr); - return { false, Result }; - } else { - GlobalsMap::iterator It = OtherGlobals.find(TemporaryId); - if (It != OtherGlobals.end()) { + switch (Argument->kind) { + case LIBTCG_ARG_TEMP: + switch (Argument->temp->kind) { + case LIBTCG_TEMP_EBB: { + // Temporary is dead at the end of the Extended Basic Block (EBB), the + // single entry, multiple exit region that falls through basic blocks. + auto It = EBBTemporaries.find(Argument->temp); + if (It != EBBTemporaries.end()) { return { false, It->second }; } else { - // TODO: what do we have here, apart from env? - auto InitialValue = ConstantInt::get(VariableType, 0); - StringRef Name(Temporary->name); - GlobalVariable *Result = nullptr; + // Can't read a temporary if it has never been written, we're probably + // translating rubbish + if (Reading) + return { false, nullptr }; - if (Name == "env") { - revng_assert(Env != nullptr); - Result = Env; - } else { - Result = new GlobalVariable(TheModule, - VariableType, - false, - GlobalValue::CommonLinkage, - InitialValue, - Name); - } - - OtherGlobals[TemporaryId] = Result; - return { false, Result }; + AllocaInst *NewTemporary = AllocaBuilder.CreateAlloca(VariableType); + EBBTemporaries[Argument->temp] = NewTemporary; + return { true, NewTemporary }; } } - } else if (Temporary->temp_local) { - auto It = LocalTemporaries.find(TemporaryId); - if (It != LocalTemporaries.end()) { - return { false, It->second }; - } else { - AllocaInst *NewTemporary = AllocaBuilder.CreateAlloca(VariableType); - LocalTemporaries[TemporaryId] = NewTemporary; - return { true, NewTemporary }; + case LIBTCG_TEMP_TB: { + // Temporary is dead at the end of the Translation Block (TB) + auto It = TBTemporaries.find(Argument->temp); + if (It != TBTemporaries.end()) { + return { false, It->second }; + } else { + AllocaInst *NewTemporary = AllocaBuilder.CreateAlloca(VariableType); + TBTemporaries[Argument->temp] = NewTemporary; + return { true, NewTemporary }; + } } - } else { - auto It = Temporaries.find(TemporaryId); - if (It != Temporaries.end()) { - return { false, It->second }; - } else { - // Can't read a temporary if it has never been written, we're probably - // translating rubbish - if (Reading) - return { false, nullptr }; - - AllocaInst *NewTemporary = AllocaBuilder.CreateAlloca(VariableType); - Temporaries[TemporaryId] = NewTemporary; - return { true, NewTemporary }; + case LIBTCG_TEMP_GLOBAL: { + // Temporary is alive at the end of a Translation Block (TB), and + // in between TBs + Value *Result = getByCPUStateOffset(LibTcgEnvOffset + + Argument->temp->mem_offset); + revng_assert(Result != nullptr); + return { false, Result }; } + case LIBTCG_TEMP_FIXED: { + revng_assert(std::string(Argument->temp->name) == "env"); + revng_assert(Env != nullptr); + return { false, Env }; + } + case LIBTCG_TEMP_CONST: { + return { true, ConstantInt::get(VariableType, Argument->temp->val) }; + } + default: + revng_unreachable("unhandled libtcg temp kind"); + } + break; + default: + revng_unreachable("unhandled libtcg arg kind"); } } @@ -612,6 +641,7 @@ Value *VariableManager::cpuStateToEnv(Value *CPUState, auto *OpaquePointer = PointerType::get(TheModule.getContext(), 0); auto *IntPtrTy = Builder.getIntPtrTy(TheModule.getDataLayout()); Value *CPUIntPtr = Builder.CreatePtrToInt(CPUState, IntPtrTy); - Value *EnvIntPtr = Builder.CreateAdd(CPUIntPtr, CI::get(IntPtrTy, EnvOffset)); + Value *EnvIntPtr = Builder.CreateAdd(CPUIntPtr, + CI::get(IntPtrTy, LibTcgEnvOffset)); return Builder.CreateIntToPtr(EnvIntPtr, OpaquePointer); } diff --git a/lib/Model/Binary.cpp b/lib/Model/Binary.cpp index 67516fcb8..6003e432f 100644 --- a/lib/Model/Binary.cpp +++ b/lib/Model/Binary.cpp @@ -405,3 +405,92 @@ void model::TypeDefinition::dumpTypeGraph(const char *Path, TypeSystemPrinter TSPrinter(Out, Binary); TSPrinter.print(*this); } + +llvm::StringRef model::Architecture::getPCCSVName(Values V) { + switch (V) { + case model::Architecture::x86_64: + return "_rip"; + + case model::Architecture::x86: + return "_eip"; + + case model::Architecture::systemz: + return "_psw_addr"; + + case model::Architecture::arm: + case model::Architecture::aarch64: + return "_pc"; + + case model::Architecture::mips: + case model::Architecture::mipsel: + return "_PC"; + + default: + revng_abort(); + } +} + +#define UnknownCSVPrefix "state_" + +std::string model::Register::getCSVName(Values V) { + // TODO: handle xmm0_x86 + + switch (V) { + case st0_x86: + return "_" UnknownCSVPrefix "0x2960"; + case xmm0_x86_64: + return "_" UnknownCSVPrefix "0x2b10"; + case xmm1_x86_64: + return "_" UnknownCSVPrefix "0x2b50"; + case xmm2_x86_64: + return "_" UnknownCSVPrefix "0x2b90"; + case xmm3_x86_64: + return "_" UnknownCSVPrefix "0x2bd0"; + case xmm4_x86_64: + return "_" UnknownCSVPrefix "0x2c10"; + case xmm5_x86_64: + return "_" UnknownCSVPrefix "0x2c50"; + case xmm6_x86_64: + return "_" UnknownCSVPrefix "0x2c90"; + case xmm7_x86_64: + return "_" UnknownCSVPrefix "0x2cd0"; + default: + return "_" + model::Register::getRegisterName(V).str(); + } +} + +model::Register::Values +model::Register::fromCSVName(llvm::StringRef Name, + model::Architecture::Values Architecture) { + if (not Name.starts_with("_")) + return model::Register::Invalid; + + Name = Name.substr(1); + + if (Architecture == model::Architecture::x86_64) { + // TODO: handle xmm0_x86 + if (Name == UnknownCSVPrefix "0x2960") { + return st0_x86; + } else if (Name == UnknownCSVPrefix "0x2b10") { + return xmm0_x86_64; + } else if (Name == UnknownCSVPrefix "0x2b50") { + return xmm1_x86_64; + } else if (Name == UnknownCSVPrefix "0x2b90") { + return xmm2_x86_64; + } else if (Name == UnknownCSVPrefix "0x2bd0") { + return xmm3_x86_64; + } else if (Name == UnknownCSVPrefix "0x2c10") { + return xmm4_x86_64; + } else if (Name == UnknownCSVPrefix "0x2c50") { + return xmm5_x86_64; + } else if (Name == UnknownCSVPrefix "0x2c90") { + return xmm6_x86_64; + } else if (Name == UnknownCSVPrefix "0x2cd0") { + return xmm7_x86_64; + } + } + + return model::Register::fromRegisterName(Name, Architecture); +} + +#undef UnknownCSVPrefix diff --git a/lib/Model/FunctionTags.cpp b/lib/Model/FunctionTags.cpp index 72ea5b87e..ccb62eeb2 100644 --- a/lib/Model/FunctionTags.cpp +++ b/lib/Model/FunctionTags.cpp @@ -4,6 +4,7 @@ #include "revng/Model/FunctionTags.h" #include "revng/Model/ProgramCounterHandler.h" +#include "revng/Support/IRHelpers.h" namespace FunctionTags { @@ -99,13 +100,13 @@ Tag ModelGEPRef("model-gep-ref"); FunctionPoolTag OpaqueExtractValue("opaque-extract-value", - { llvm::Attribute::OptimizeNone, - llvm::Attribute::NoInline, + { llvm::Attribute::NoInline, llvm::Attribute::NoMerge, llvm::Attribute::NoUnwind, llvm::Attribute::WillReturn }, - llvm::MemoryEffects::inaccessibleMemOnly() - | llvm::MemoryEffects::readOnly(), + // The following is necessary to prevent the optimizer to + // move these around. + llvm::MemoryEffects::inaccessibleMemOnly(), { &FunctionTags::UniquedByPrototype }, [](OpaqueFunctionsPool &Pool, llvm::Module &M, @@ -448,7 +449,7 @@ extractStringLiteralFromMetadata(const llvm::Function &F) { } // This name corresponds to a function in `early-linked`. -RegisterIRHelper RevngAbortHelper(AbortFunctionName.str()); +RegisterIRHelper AbortHelper(AbortFunctionName.str()); template llvm::CallInst &emitMessageImpl(revng::IRBuilder &Builder, @@ -714,6 +715,54 @@ llvm::FunctionType *getCopyType(llvm::Type *ReturnedType, return FunctionType::get(ReturnedType, FixedArgs, false /* IsVarArg */); } +static std::vector extractCSVs(llvm::Function *F, + unsigned MDKindID) { + using namespace llvm; + + std::vector Result; + auto *Tuple = cast_or_null(F->getMetadata(MDKindID)); + if (Tuple == nullptr) + return Result; + + llvm::Module *M = F->getParent(); + QuickMetadata QMD(M->getContext()); + + auto OperandsRange = QMD.extract(Tuple, 1)->operands(); + for (const MDOperand &Operand : OperandsRange) { + if (Metadata *MD = Operand.get()) { + auto CSVName = QMD.extract(MD); + + // Note: here we record the *names* of CSVs as opposed to a + // ConstantAsMetadata pointing to the GlobalVariable because otherwise, + // during linking, these get null-ified. + if (auto *CSV = M->getGlobalVariable(CSVName, true)) + Result.push_back(CSV); + } + } + + return Result; +} + +std::optional tryGetCSVUsedByHelperCall(llvm::Instruction *Call) { + revng_assert(isCallToHelper(Call)); + + auto *Callee = getCalledFunction(cast(Call)); + + const llvm::Module *M = getModule(Call); + const auto LoadMDKind = M->getMDKindID("revng.csvaccess.offsets.load"); + const auto StoreMDKind = M->getMDKindID("revng.csvaccess.offsets.store"); + + if (Callee->getMetadata(LoadMDKind) == nullptr + and Callee->getMetadata(StoreMDKind) == nullptr) { + return {}; + } + + CSVsUsage Result; + Result.Read = extractCSVs(Callee, LoadMDKind); + Result.Written = extractCSVs(Callee, StoreMDKind); + return Result; +} + const llvm::CallInst *getCallToIsolatedFunction(const llvm::Value *V) { if (const llvm::CallInst *Call = getCallToTagged(V, FunctionTags::Isolated)) { // The callee is an isolated function diff --git a/lib/Model/Importer/Binary/DwarfReader.h b/lib/Model/Importer/Binary/DwarfReader.h index 86c4bfa7d..58bddf6bb 100644 --- a/lib/Model/Importer/Binary/DwarfReader.h +++ b/lib/Model/Importer/Binary/DwarfReader.h @@ -103,7 +103,7 @@ private: template class DwarfReader { public: - DwarfReader(llvm::Triple::ArchType Architecture, + DwarfReader(model::Architecture::Values Architecture, llvm::ArrayRef Buffer, MetaAddress Address) : Architecture(Architecture), @@ -279,7 +279,7 @@ private: bool is64() const; private: - llvm::Triple::ArchType Architecture; + model::Architecture::Values Architecture; MetaAddress Address; const uint8_t *Start; const uint8_t *Cursor; diff --git a/lib/Model/Importer/Binary/ELFImporter.cpp b/lib/Model/Importer/Binary/ELFImporter.cpp index 12ddab837..fd5f9d43e 100644 --- a/lib/Model/Importer/Binary/ELFImporter.cpp +++ b/lib/Model/Importer/Binary/ELFImporter.cpp @@ -883,7 +883,7 @@ ELFImporter::ehFrameFromEhFrameHdr() { ArrayRef EHFrameHdr = *MaybeEHFrameHdr; using namespace model::Architecture; - DwarfReader EHFrameHdrReader(toLLVMArchitecture(Binary.Architecture()), + DwarfReader EHFrameHdrReader(Binary.Architecture(), EHFrameHdr, *EHFrameHdrAddress); @@ -936,10 +936,7 @@ void ELFImporter::parseEHFrame(MetaAddress EHFrameAddress, return; llvm::ArrayRef EHFrame = *MaybeEHFrame; - using namespace model::Architecture; - auto Architecture = toLLVMArchitecture(Model->Architecture()); - - DwarfReader EHFrameReader(Architecture, EHFrame, EHFrameAddress); + DwarfReader EHFrameReader(Model->Architecture(), EHFrame, EHFrameAddress); // A few fields of the CIE are used when decoding the FDE's. This struct // will cache those fields we need so that we don't have to decode it @@ -1132,9 +1129,7 @@ void ELFImporter::parseLSDA(MetaAddress FDEStart, } llvm::ArrayRef LSDA = *MaybeLSDA; - using namespace model::Architecture; - auto Architecture = toLLVMArchitecture(Model->Architecture()); - DwarfReader LSDAReader(Architecture, LSDA, LSDAAddress); + DwarfReader LSDAReader(Model->Architecture(), LSDA, LSDAAddress); uint32_t LandingPadBaseEncoding = LSDAReader.readNextU8(); MetaAddress LandingPadBase = MetaAddress::invalid(); diff --git a/lib/Model/Importer/Binary/ELFImporter.h b/lib/Model/Importer/Binary/ELFImporter.h index 3c74e3757..8d4c889d5 100644 --- a/lib/Model/Importer/Binary/ELFImporter.h +++ b/lib/Model/Importer/Binary/ELFImporter.h @@ -108,9 +108,7 @@ private: } MetaAddress getCodePointer(Pointer Ptr) const { - using namespace model::Architecture; - auto Architecture = Model->Architecture(); - return this->getGenericPointer(Ptr).toPC(toLLVMArchitecture(Architecture)); + return this->getGenericPointer(Ptr).toPC(Model->Architecture()); } /// Parse the .eh_frame_hdr section to obtain the address and the number of diff --git a/lib/Model/Importer/Binary/MachOImporter.cpp b/lib/Model/Importer/Binary/MachOImporter.cpp index e65a9a332..beed70ce2 100644 --- a/lib/Model/Importer/Binary/MachOImporter.cpp +++ b/lib/Model/Importer/Binary/MachOImporter.cpp @@ -161,9 +161,7 @@ static MetaAddress getInitialPC(Architecture::Values Architecture, } if (Reader.eof() and PC) { - return MetaAddress::fromPC(Architecture::toLLVMArchitecture(Architecture), - *PC); - + return MetaAddress::fromPC(Architecture, *PC); } else { // TODO: emit a diagnostic message for the user. return MetaAddress::invalid(); @@ -279,10 +277,8 @@ Error MachOImporter::import() { } if (EntryPointOffset) { - using namespace model::Architecture; - auto LLVMArchitecture = toLLVMArchitecture(Model->Architecture()); auto EntryPoint = File.offsetToAddress(*EntryPointOffset) - .toPC(LLVMArchitecture); + .toPC(Model->Architecture()); setEntryPoint(EntryPoint); } diff --git a/lib/Model/ProgramCounterHandler.cpp b/lib/Model/ProgramCounterHandler.cpp index f97f20981..20ca2b199 100644 --- a/lib/Model/ProgramCounterHandler.cpp +++ b/lib/Model/ProgramCounterHandler.cpp @@ -5,8 +5,10 @@ // #include "llvm/ADT/SmallSet.h" +#include "llvm/Support/GraphWriter.h" #include "llvm/Support/ModRef.h" +#include "revng/Model/Architecture.h" #include "revng/Model/FunctionTags.h" #include "revng/Model/ProgramCounterHandler.h" #include "revng/Support/Assert.h" @@ -28,8 +30,9 @@ public: auto Result = std::make_unique(Alignment); // Create and register the pc CSV - Result->AddressCSV = Factory(PCAffectingCSV::PC, AddressName); + Result->AddressCSV = Factory(PCAffectingCSV::PC); Result->CSVsAffectingPC.insert(Result->AddressCSV); + revng_assert(Result->AddressCSV != nullptr); // Create the other variables (non-CSV) Result->createMissingVariables(M); @@ -37,12 +40,15 @@ public: return Result; } - static std::unique_ptr fromModule(Module *M, - unsigned Alignment) { + static std::unique_ptr + fromModule(model::Architecture::Values Architecture, + Module *M, + unsigned Alignment) { auto Result = std::make_unique(Alignment); // Initialize the standard variables - Result->setMissingVariables(M); + using namespace model::Architecture; + Result->setMissingVariables(M, getPCCSVName(Architecture)); // Register pc as a CSV affecting the program counter Result->CSVsAffectingPC.insert(Result->AddressCSV); @@ -61,16 +67,17 @@ public: return createLoad(Builder, AddressCSV); } - std::array dissectJumpablePC(revng::IRBuilder &Builder, - Value *ToDissect, - Triple::ArchType Arch) const final { + std::array + dissectJumpablePC(revng::IRBuilder &Builder, + Value *ToDissect, + model::Architecture::Values Architecture) const final { IntegerType *Ty = getCSVType(TypeCSV); Value *Address = align(Builder, ToDissect); Value *Epoch = ConstantInt::get(Ty, 0); Value *AddressSpace = ConstantInt::get(Ty, 0); - Value *Type = ConstantInt::get(Ty, - MetaAddressType::defaultCodeFromArch(Arch)); + auto DefaultType = MetaAddressType::defaultCodeFromArch(Architecture); + Value *Type = ConstantInt::get(Ty, DefaultType); return { Address, Epoch, AddressSpace, Type }; } @@ -87,7 +94,7 @@ protected: class ARMProgramCounterHandler : public ProgramCounterHandler { private: - static constexpr const char *IsThumbName = "is_thumb"; + static constexpr const char *IsThumbName = "_thumb"; private: GlobalVariable *IsThumb = nullptr; @@ -101,10 +108,10 @@ public: auto Result = std::make_unique(); // Create and register the pc and is_thumb CSV - Result->AddressCSV = Factory(PCAffectingCSV::PC, AddressName); + Result->AddressCSV = Factory(PCAffectingCSV::PC); Result->CSVsAffectingPC.insert(Result->AddressCSV); - Result->IsThumb = Factory(PCAffectingCSV::IsThumb, IsThumbName); + Result->IsThumb = Factory(PCAffectingCSV::IsThumb); Result->CSVsAffectingPC.insert(Result->IsThumb); Result->createMissingVariables(M); @@ -115,8 +122,11 @@ public: static std::unique_ptr fromModule(Module *M) { auto Result = std::make_unique(); - // Initialize the standard variablesx - Result->setMissingVariables(M); + // Initialize the standard variables + using namespace model::Architecture; + Result->setMissingVariables(M, + + getPCCSVName(arm)); // Get is_thumb Result->IsThumb = M->getGlobalVariable(IsThumbName, true); @@ -155,9 +165,10 @@ private: AddressType)); } - std::array dissectJumpablePC(revng::IRBuilder &Builder, - Value *ToDissect, - Triple::ArchType Arch) const final { + std::array + dissectJumpablePC(revng::IRBuilder &Builder, + Value *ToDissect, + model::Architecture::Values Architecture) const final { constexpr uint32_t ThumbMask = 0x1; constexpr uint32_t AddressMask = 0xFFFFFFFE; IntegerType *Ty = getCSVType(TypeCSV); @@ -211,8 +222,13 @@ private: auto *ThumbCode = CI::get(TypeType, Code_arm_thumb); // We don't use select here, SCEV can't handle it // NewType = ARM + IsThumb * (Thumb - ARM) + unsigned ThumbSize = cast(IsThumb->getType())->getBitWidth(); + unsigned TypeSize = cast(TypeType)->getBitWidth(); + auto *CastedThumb = (ThumbSize < TypeSize) ? + B.CreateZExt(IsThumb, TypeType) : + B.CreateTrunc(IsThumb, TypeType); auto *NewType = B.CreateAdd(ArmCode, - B.CreateMul(B.CreateTrunc(IsThumb, TypeType), + B.CreateMul(CastedThumb, B.CreateSub(ThumbCode, ArmCode))); return NewType; } @@ -353,7 +369,7 @@ bool PCH::isPCAffectingHelper(Instruction *I) const { if (HelperCall == nullptr) return false; - auto MaybeUsedCSVs = getCSVUsedByHelperCallIfAvailable(HelperCall); + auto MaybeUsedCSVs = tryGetCSVUsedByHelperCall(HelperCall); // If CSAA didn't consider this helper, be conservative if (not MaybeUsedCSVs) @@ -866,17 +882,18 @@ PCH::buildDispatcher(DispatcherTargets &Targets, return Result; } -static unsigned getMinimumPCAlignment(Triple::ArchType Architecture) { +static unsigned +getMinimumPCAlignment(model::Architecture::Values Architecture) { switch (Architecture) { - case Triple::x86: - case Triple::x86_64: + case model::Architecture::x86: + case model::Architecture::x86_64: return 1; - case Triple::arm: - case Triple::systemz: + case model::Architecture::arm: + case model::Architecture::systemz: return 2; - case Triple::mips: - case Triple::mipsel: - case Triple::aarch64: + case model::Architecture::mips: + case model::Architecture::mipsel: + case model::Architecture::aarch64: return 4; default: revng_abort(); @@ -884,21 +901,21 @@ static unsigned getMinimumPCAlignment(Triple::ArchType Architecture) { } std::unique_ptr -PCH::create(Triple::ArchType Architecture, +PCH::create(model::Architecture::Values Architecture, Module *M, const CSVFactory &Factory) { auto Alignment = getMinimumPCAlignment(Architecture); switch (Architecture) { - case Triple::arm: + case model::Architecture::arm: return ARMProgramCounterHandler::create(M, Factory); - case Triple::x86_64: - case Triple::mips: - case Triple::mipsel: - case Triple::aarch64: - case Triple::systemz: - case Triple::x86: + case model::Architecture::x86_64: + case model::Architecture::mips: + case model::Architecture::mipsel: + case model::Architecture::aarch64: + case model::Architecture::systemz: + case model::Architecture::x86: return PCOnlyProgramCounterHandler::create(M, Factory, Alignment); default: @@ -909,20 +926,20 @@ PCH::create(Triple::ArchType Architecture, } std::unique_ptr -PCH::fromModule(Triple::ArchType Architecture, Module *M) { +PCH::fromModule(model::Architecture::Values Architecture, Module *M) { auto Alignment = getMinimumPCAlignment(Architecture); switch (Architecture) { - case Triple::arm: + case model::Architecture::arm: return ARMProgramCounterHandler::fromModule(M); - case Triple::x86_64: - case Triple::mips: - case Triple::mipsel: - case Triple::aarch64: - case Triple::systemz: - case Triple::x86: - return PCOnlyProgramCounterHandler::fromModule(M, Alignment); + case model::Architecture::x86_64: + case model::Architecture::mips: + case model::Architecture::mipsel: + case model::Architecture::aarch64: + case model::Architecture::systemz: + case model::Architecture::x86: + return PCOnlyProgramCounterHandler::fromModule(Architecture, M, Alignment); default: revng_abort("Unsupported architecture"); diff --git a/lib/Recompile/CMakeLists.txt b/lib/Recompile/CMakeLists.txt index e45d1723d..b4ffae308 100644 --- a/lib/Recompile/CMakeLists.txt +++ b/lib/Recompile/CMakeLists.txt @@ -6,5 +6,5 @@ revng_add_analyses_library_internal( revngRecompile LinkForTranslationPipe.cpp LinkForTranslation.cpp OriginalAssemblyAnnotationWriter.cpp CompileModulePipe.cpp) -target_link_libraries(revngRecompile revngModelImporterBinary revngLift +target_link_libraries(revngRecompile revngLift revngModelImporterBinary revngSupport revngPipes ${LLVM_LIBRARIES}) diff --git a/lib/Recompile/CompileModulePipe.cpp b/lib/Recompile/CompileModulePipe.cpp index 79b4562a4..dfa113f72 100644 --- a/lib/Recompile/CompileModulePipe.cpp +++ b/lib/Recompile/CompileModulePipe.cpp @@ -6,23 +6,33 @@ // #include +#include #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/CodeGen/CommandFlags.h" #include "llvm/CodeGen/MachineModuleInfo.h" #include "llvm/IR/AutoUpgrade.h" +#include "llvm/IR/DiagnosticInfo.h" +#include "llvm/IR/DiagnosticPrinter.h" +#include "llvm/IR/GlobalValue.h" #include "llvm/IR/LegacyPassManager.h" +#include "llvm/Linker/Linker.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Option/OptTable.h" #include "llvm/Support/CodeGen.h" #include "llvm/Support/raw_os_ostream.h" +#include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/IPO.h" #include "revng/Lift/IRAnnotators.h" +#include "revng/Model/FunctionTags.h" +#include "revng/Model/Register.h" #include "revng/Pipeline/AllRegistries.h" #include "revng/Pipeline/LLVMContainer.h" #include "revng/Pipeline/Target.h" #include "revng/Pipes/Kinds.h" +#include "revng/Pipes/ModelGlobal.h" #include "revng/Recompile/CompileModulePipe.h" #include "revng/Recompile/OriginalAssemblyAnnotationWriter.h" #include "revng/Support/Assert.h" @@ -38,12 +48,40 @@ using namespace cl; static cl::opt OptLevel("compile-opt-level", cl::desc("Optimization level. [-O0, -O1, -O2, or " - "-O3] " - "(default = '-O2')"), + "-O3] (default = '-O2')"), cl::Prefix, cl::ZeroOrMore, cl::init(' ')); +// TODO: should we use this in every LLVMContext? +class CustomDiagnosticHandler : public llvm::DiagnosticHandler { +public: + bool handleDiagnostics(const llvm::DiagnosticInfo &DI) override { + // Get diagnostic message + std::string Message; + { + raw_string_ostream Stream(Message); + DiagnosticPrinterRawOStream DP(Stream); + DI.print(DP); + } + + // Handle based on severity + switch (DI.getSeverity()) { + case llvm::DS_Error: + revng_abort(Message.c_str()); + break; + case llvm::DS_Warning: + case llvm::DS_Remark: + case llvm::DS_Note: + // TODO: dump to a logger + break; + } + + // Return true to indicate we've handled the diagnostic + return true; + } +}; + static void compileModuleRunImpl(const Context &Context, LLVMContainer &Module, ObjectFileContainer &TargetBinary) { @@ -63,6 +101,31 @@ static void compileModuleRunImpl(const Context &Context, llvm::Module *M = &Module.getModule(); + M->getContext() + .setDiagnosticHandler(std::make_unique()); + + { + auto Architecture = getModelFromContext(Context)->Architecture(); + auto ArchName = model::Architecture::getQEMUName(Architecture).str(); + + // Note: here we use the full version of the helpers, i.e., where we all the + // definitions (as opposed to only those with revng_inline, as it + // happens with the slim version). + const std::string LibHelpersName = "/share/revng/libtcg-helpers-annotated-" + + ArchName + ".bc"; + auto OptionalHelpers = ResourceFinder.findFile(LibHelpersName); + revng_assert(OptionalHelpers.has_value(), "Cannot find tinycode helpers"); + + auto HelpersModule = parseIR(M->getContext(), OptionalHelpers.value()); + + linkModules(std::move(HelpersModule), *M, GlobalValue::InternalLinkage); + + M->getFunction("main")->setLinkage(llvm::GlobalValue::ExternalLinkage); + } + + for (Function &F : *M) + F.setSection(""); + OriginalAssemblyAnnotationWriter OAAW(M->getContext()); createSelfReferencingDebugInfo(M, Module.name(), &OAAW); @@ -99,7 +162,7 @@ static void compileModuleRunImpl(const Context &Context, "", "", Options, - getRelocModel(), + std::nullopt, M->getCodeModel(), OLvl); unique_ptr Target(Ptr); @@ -111,27 +174,29 @@ static void compileModuleRunImpl(const Context &Context, // to check debug info whereas verifier relies on correct datalayout. UpgradeDebugInfo(*M); - LLVMTargetMachine &LLVMTM = static_cast(*Target); - auto *MMIWP = new MachineModuleInfoWrapperPass(&LLVMTM); + // Before compiling, do a last pass to collect all the globals (in particular, + // helpers) we don't need. This saves from spurious linking errors. + legacy::PassManager CleanupPM; + CleanupPM.add(llvm::createGlobalDCEPass()); + CleanupPM.run(*M); + + // Create pass manager + legacy::PassManager PM; + // Add an appropriate TargetLibraryInfo pass for the module's triple. + TargetLibraryInfoImpl TLII(Triple(M->getTargetTriple())); + PM.add(new TargetLibraryInfoWrapperPass(TLII)); std::error_code EC; raw_fd_ostream OutputStream(TargetBinary.getOrCreatePath(), EC); revng_assert(!EC); - // Create pass manager - legacy::PassManager PM; - - // Add an appropriate TargetLibraryInfo pass for the module's triple. - TargetLibraryInfoImpl TLII(Triple(M->getTargetTriple())); - PM.add(new TargetLibraryInfoWrapperPass(TLII)); - bool Err = Target->addPassesToEmitFile(PM, OutputStream, nullptr, CGFT_ObjectFile, - true, - MMIWP); + true); revng_assert(not Err); + revng::verify(M); PM.run(*M); revng::verify(M); diff --git a/lib/Recompile/OriginalAssemblyAnnotationWriter.cpp b/lib/Recompile/OriginalAssemblyAnnotationWriter.cpp index c656a0ca1..afcb378bf 100644 --- a/lib/Recompile/OriginalAssemblyAnnotationWriter.cpp +++ b/lib/Recompile/OriginalAssemblyAnnotationWriter.cpp @@ -51,8 +51,7 @@ void OAAW::emitInstructionAnnot(const Instruction *I, formatted_raw_ostream &Output) { // Ignore whatever is outside the root and the isolated functions + writeMetadataIfNew(I, PTCInstrMDKind, Output, "\n ; "); if (isRootOrLifted(I->getParent()->getParent())) { - writeMetadataIfNew(I, OriginalInstrMDKind, Output, "\n ; "); - writeMetadataIfNew(I, PTCInstrMDKind, Output, "\n ; "); } } diff --git a/lib/RemoveLiftingArtifacts/MakeSegmentRefPass.cpp b/lib/RemoveLiftingArtifacts/MakeSegmentRefPass.cpp index 5e40360c7..a90d75850 100644 --- a/lib/RemoveLiftingArtifacts/MakeSegmentRefPass.cpp +++ b/lib/RemoveLiftingArtifacts/MakeSegmentRefPass.cpp @@ -74,10 +74,10 @@ void MakeSegmentRefPassImpl::getAnalysisUsage(llvm::AnalysisUsage &AU) { static std::optional> findLiteralInSegments(const model::Binary &Binary, uint64_t Literal) { std::optional> Result = std::nullopt; - auto Arch = toLLVMArchitecture(Binary.Architecture()); + auto Architecture = Binary.Architecture(); for (const auto &Segment : Binary.Segments()) { - if (Segment.contains(MetaAddress::fromGeneric(Arch, Literal))) { + if (Segment.contains(MetaAddress::fromGeneric(Architecture, Literal))) { revng_assert(not Result.has_value()); Result = { { Segment.StartAddress(), Segment.VirtualSize() } }; } diff --git a/lib/RemoveLiftingArtifacts/RemoveLiftingArtifacts.cpp b/lib/RemoveLiftingArtifacts/RemoveLiftingArtifacts.cpp index c9c2a07ab..4241d53e6 100644 --- a/lib/RemoveLiftingArtifacts/RemoveLiftingArtifacts.cpp +++ b/lib/RemoveLiftingArtifacts/RemoveLiftingArtifacts.cpp @@ -51,7 +51,9 @@ static bool removeCallsToArtifacts(Function &F) { static bool removeStoresToCPULoopExiting(Function &F) { // Retrieve the global variable `cpu_loop_exiting` Module *M = F.getParent(); - GlobalVariable *CpuLoop = M->getGlobalVariable("cpu_loop_exiting"); + GlobalVariable *CpuLoop = M->getGlobalVariable("cpu_loop_exiting", true); + if (CpuLoop == nullptr) + return false; // Remove in bulk all the users of the global variable. SmallVector Loads; diff --git a/lib/Support/BasicBlockID.cpp b/lib/Support/BasicBlockID.cpp index cbafdcc64..bb668bbec 100644 --- a/lib/Support/BasicBlockID.cpp +++ b/lib/Support/BasicBlockID.cpp @@ -38,8 +38,8 @@ BasicBlockID BasicBlockID::fromString(llvm::StringRef Text) { } std::string -BasicBlockID::toString(std::optional Arch) const { - std::string Result = Start.toString(Arch); +BasicBlockID::toString(model::Architecture::Values Architecture) const { + std::string Result = Start.toString(Architecture); if (isInlined()) { Result += "-" + llvm::Twine(InliningIndex).str(); diff --git a/lib/Support/IRHelpers.cpp b/lib/Support/IRHelpers.cpp index d48218144..ddb3bbe14 100644 --- a/lib/Support/IRHelpers.cpp +++ b/lib/Support/IRHelpers.cpp @@ -6,6 +6,7 @@ // #include +#include #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SetVector.h" @@ -15,6 +16,7 @@ #include "llvm/IR/Dominators.h" #include "llvm/IR/TypedPointerType.h" #include "llvm/IR/Verifier.h" +#include "llvm/IRReader/IRReader.h" #include "llvm/Linker/Linker.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_os_ostream.h" @@ -25,7 +27,6 @@ #include "revng/Support/BlockType.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/StringOperations.h" -#include "revng/Support/Tag.h" using namespace llvm; @@ -530,7 +531,6 @@ bool deleteOnlyBody(llvm::Function &F) { // want them, we have to save them and re-add them after deleting the // body of the function. auto Attributes = F.getAttributes(); - auto FTags = FunctionTags::TagsSet::from(&F); MetadataBackup SavedMetadata(&F); @@ -538,7 +538,6 @@ bool deleteOnlyBody(llvm::Function &F) { F.deleteBody(); // Restore tags and attributes - FTags.set(&F); F.setAttributes(Attributes); F.clearMetadata(); @@ -616,43 +615,58 @@ void sortModule(llvm::Module &M) { } } -void linkModules(std::unique_ptr &&Source, - llvm::Module &Destination, - std::optional FinalLinkage) { - std::map HelperGlobals; +std::unique_ptr parseIR(LLVMContext &Context, StringRef Path) { + std::unique_ptr Result; + SMDiagnostic Errors; + Result = parseIRFile(Path, Errors, Context); + + if (Result.get() == nullptr) { + Errors.print("revng", dbgs()); + revng_abort(); + } + + return Result; +} + +void linkModules(std::unique_ptr &&Source, + Module &Destination, + std::optional FinalLinkage) { + std::map HelperGlobals; auto HandleGlobals = [&HelperGlobals, &Destination](auto &&GlobalsRange) { using T = std::decay_t; for (T &HelperGlobal : GlobalsRange) { auto GlobalName = HelperGlobal.getName(); - if (GlobalName.empty() or GlobalName.startswith("llvm.")) - continue; + if (not GlobalName.startswith("llvm.")) { - // Register so we can change its linkage later - HelperGlobals[GlobalName.str()] = HelperGlobal.getLinkage(); + // Register so we can change its linkage later + HelperGlobals[GlobalName.str()] = HelperGlobal.getLinkage(); - llvm::GlobalObject *LocalGlobal = nullptr; - if constexpr (std::is_same_v) { - LocalGlobal = Destination.getGlobalVariable(GlobalName, true); - } else { - static_assert(std::is_same_v); - LocalGlobal = Destination.getFunction(GlobalName); - } + GlobalObject *LocalGlobal = nullptr; + if constexpr (std::is_same_v) { + LocalGlobal = Destination.getGlobalVariable(GlobalName); + } else { + static_assert(std::is_same_v); + LocalGlobal = Destination.getFunction(GlobalName); + } - if (LocalGlobal != nullptr) { - // We have a global with the same name - HelperGlobal.setLinkage(llvm::GlobalValue::ExternalLinkage); - LocalGlobal->setLinkage(llvm::GlobalValue::ExternalLinkage); + if (LocalGlobal != nullptr) { + // We have a global with the same name + HelperGlobal.setLinkage(GlobalValue::ExternalLinkage); - bool AlreadyAvailable = not LocalGlobal->isDeclaration(); - if (AlreadyAvailable) { - // Turn helper global into declaration - if constexpr (std::is_same_v) { - HelperGlobal.setInitializer(nullptr); + bool AlreadyAvailable = not LocalGlobal->isDeclaration(); + if (AlreadyAvailable) { + // Turn helper global into declaration + if constexpr (std::is_same_v) { + HelperGlobal.setInitializer(nullptr); + } else { + static_assert(std::is_same_v); + HelperGlobal.deleteBody(); + } } else { - static_assert(std::is_same_v); - HelperGlobal.deleteBody(); + // Ensure it will be linked + LocalGlobal->setLinkage(GlobalValue::ExternalLinkage); } } } @@ -662,9 +676,9 @@ void linkModules(std::unique_ptr &&Source, HandleGlobals(Source->globals()); HandleGlobals(Source->functions()); - llvm::Linker TheLinker(Destination); + Linker TheLinker(Destination); bool Failed = TheLinker.linkInModule(std::move(Source), - llvm::Linker::LinkOnlyNeeded); + Linker::LinkOnlyNeeded); revng_assert(not Failed, "Linking failed"); for (auto [GlobalName, Linkage] : HelperGlobals) { diff --git a/lib/Support/MetaAddress.cpp b/lib/Support/MetaAddress.cpp index e17814bd5..44d0243a9 100644 --- a/lib/Support/MetaAddress.cpp +++ b/lib/Support/MetaAddress.cpp @@ -61,17 +61,18 @@ MetaAddress MetaAddress::decomposeIntegerPC(const APInt &Value) { } static std::string toStringImpl(const MetaAddress &Address, - std::optional Arch, + model::Architecture::Values Architecture, llvm::StringRef Separator) { if (Address.isInvalid()) return Separator.str() += "Invalid"; bool ShouldPrintTheType = true; - if (Arch.has_value()) { + if (Architecture != model::Architecture::Invalid) { // Assert if `Arch` is not supported. - static_cast(MetaAddressType::genericFromArch(Arch.value())); + static_cast(MetaAddressType::genericFromArch(Architecture)); - if (Address.arch().has_value() && Address.arch().value() == Arch.value()) + if (Address.arch() != model::Architecture::Invalid + and Address.arch() == Architecture) ShouldPrintTheType = false; } @@ -91,13 +92,13 @@ static std::string toStringImpl(const MetaAddress &Address, } std::string -MetaAddress::toString(std::optional Arch) const { - return toStringImpl(*this, Arch, Separator); +MetaAddress::toString(model::Architecture::Values Architecture) const { + return toStringImpl(*this, Architecture, Separator); } std::string -MetaAddress::toIdentifier(std::optional Arch) const { - return toStringImpl(*this, Arch, "_"); +MetaAddress::toIdentifier(model::Architecture::Values Architecture) const { + return toStringImpl(*this, Architecture, "_"); } MetaAddress MetaAddress::fromString(StringRef Text) { diff --git a/lib/ValueMaterializer/AdvancedValueInfo.cpp b/lib/ValueMaterializer/AdvancedValueInfo.cpp index 6ef0fc955..4200294a2 100644 --- a/lib/ValueMaterializer/AdvancedValueInfo.cpp +++ b/lib/ValueMaterializer/AdvancedValueInfo.cpp @@ -4,17 +4,39 @@ // This file is distributed under the MIT License. See LICENSE.md for details. // +#include +#include +#include +#include +#include +#include +#include +#include + +#include "llvm/ADT/GraphTraits.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Analysis/LazyValueInfo.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/CFG.h" #include "llvm/IR/Dominators.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" #include "llvm/Support/GraphWriter.h" +#include "revng/ADT/ConstantRangeSet.h" +#include "revng/ADT/Queue.h" #include "revng/MFP/DOTGraphTraits.h" #include "revng/MFP/Graph.h" +#include "revng/MFP/MFP.h" #include "revng/Support/GraphAlgorithms.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/Statistics.h" #include "revng/ValueMaterializer/AdvancedValueInfo.h" #include "revng/ValueMaterializer/DataFlowGraph.h" +#include "revng/ValueMaterializer/DataFlowRangeAnalysis.h" +#include "revng/ValueMaterializer/Helpers.h" using namespace llvm; @@ -109,12 +131,32 @@ AdvancedValueInfoMFI::applyTransferFunction(Label L, continue; } - ConstantRangeSet NewRange; + ConstantRangeSet NewRange(Candidate->getType()->getIntegerBitWidth(), + true); if (L->Destination != nullptr) { + // TODO: extend DataFlowRangeAnalysis so we no longer need to rely on + // LVI too. NewRange = LVI.getConstantRangeOnEdge(Candidate, L->Source, L->Destination, Context); + + auto *Branch = dyn_cast(L->Source->getTerminator()); + if (Branch != nullptr and Branch->isConditional()) { + BasicBlock *TrueBranch = Branch->getSuccessor(0); + BasicBlock *FalseBranch = Branch->getSuccessor(1); + if (TrueBranch != FalseBranch) { + if (auto MaybeRange = DFRA.visit(*Branch->getCondition(), + *Candidate)) { + + if (TrueBranch == L->Destination) { + NewRange = NewRange & *MaybeRange; + } else if (FalseBranch == L->Destination) { + NewRange = NewRange & ~*MaybeRange; + } + } + } + } } else { NewRange = LVI.getConstantRange(Candidate, L->Source->getTerminator()); } @@ -153,8 +195,8 @@ void AdvancedValueInfoMFI::dump(GraphType CFEG, const ResultsMap &AllResults) { llvm::WriteGraph(&MFPGraph, "cfeg"); } -/// \p DFG the data flow graph containing the instructions we're interested in. -/// \p Context the position in the function for the current query. +/// \p DFG the data flow graph containing the instructions we're interested +/// in. \p Context the position in the function for the current query. std::tuple, ControlFlowEdgesGraph, map *, @@ -163,11 +205,13 @@ runAVI(const DataFlowGraph &DFG, llvm::Instruction *Context, const llvm::DominatorTree &DT, llvm::LazyValueInfo &LVI, + DataFlowRangeAnalysis &DFRA, bool ZeroExtendConstraints) { using namespace llvm; // - // Identify nodes from the root of the DFG to all the instructions in the DFG + // Identify nodes from the root of the DFG to all the instructions in the + // DFG // SmallPtrSet Whitelist; @@ -260,7 +304,12 @@ runAVI(const DataFlowGraph &DFG, revng_assert(InitialNodes.size() > 0); // Run MFP - AdvancedValueInfoMFI AVIMFI(LVI, DT, Context, Targets, ZeroExtendConstraints); + AdvancedValueInfoMFI AVIMFI(LVI, + DFRA, + DT, + Context, + Targets, + ZeroExtendConstraints); AdvancedValueInfoMFI::LatticeElement ExtremalValue; for (Instruction *I : Targets) @@ -272,19 +321,13 @@ runAVI(const DataFlowGraph &DFG, {}, ExtremalValue, InitialNodes, - InitialNodes); + InitialNodes, + AVILogger); if (AVILogger.isEnabled()) { + AVILogger << "Dumping MFP results:" << DoLog; + LoggerIndent<> Indent(AVILogger); for (const auto &[Node, AnalysisResults] : AllResults) { - auto Dump = - [&](const std::map &Map) { - for (const auto &[I, Range] : Map) { - AVILogger << " " << getName(I) << ": "; - Range.dump(AVILogger); - AVILogger << "\n"; - } - }; - AVILogger << Node->toString() << ":\n"; AVILogger << " Initial value:\n"; MFP::dump(*AVILogger.getAsLLVMStream().get(), 2, AnalysisResults.InValue); @@ -313,3 +356,9 @@ void MFP::dump(llvm::raw_ostream &Stream, Stream << "\n"; } } + +template<> +void MFP::dumpLabel(llvm::raw_ostream &Stream, + const ControlFlowEdgesGraph::Node *const &Label) { + Stream << Label->toString(); +} diff --git a/lib/ValueMaterializer/CMakeLists.txt b/lib/ValueMaterializer/CMakeLists.txt index f759de8dc..d090fdf6f 100644 --- a/lib/ValueMaterializer/CMakeLists.txt +++ b/lib/ValueMaterializer/CMakeLists.txt @@ -3,8 +3,13 @@ # revng_add_library_internal( - revngValueMaterializer SHARED AdvancedValueInfo.cpp ControlFlowEdgesGraph.cpp - DataFlowGraph.cpp ValueMaterializer.cpp) + revngValueMaterializer + SHARED + AdvancedValueInfo.cpp + ControlFlowEdgesGraph.cpp + DataFlowGraph.cpp + DataFlowRangeAnalysis.cpp + ValueMaterializer.cpp) target_link_libraries(revngValueMaterializer PUBLIC revngSupport revngBasicAnalyses ${LLVM_LIBRARIES}) diff --git a/lib/ValueMaterializer/DataFlowRangeAnalysis.cpp b/lib/ValueMaterializer/DataFlowRangeAnalysis.cpp new file mode 100644 index 000000000..cc52fe962 --- /dev/null +++ b/lib/ValueMaterializer/DataFlowRangeAnalysis.cpp @@ -0,0 +1,403 @@ +/// \file DataFlowRangeAnalysis.cpp + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include "llvm/IR/Instructions.h" +#include "llvm/IR/PatternMatch.h" + +#include "revng/ADT/ConstantRangeSet.h" +#include "revng/ADT/RecursiveCoroutine.h" +#include "revng/ValueMaterializer/DataFlowRangeAnalysis.h" + +static Logger<> Log("data-flow-range-analysis"); + +static bool isSigned(llvm::ICmpInst::Predicate Predicate) { + switch (Predicate) { + case llvm::CmpInst::ICMP_SLE: + case llvm::CmpInst::ICMP_SGT: + case llvm::CmpInst::ICMP_SGE: + case llvm::CmpInst::ICMP_SLT: + return true; + + case llvm::CmpInst::ICMP_UGE: + case llvm::CmpInst::ICMP_ULT: + case llvm::CmpInst::ICMP_ULE: + case llvm::CmpInst::ICMP_UGT: + return false; + + default: + revng_abort(); + } +} + +static bool isInclusive(llvm::ICmpInst::Predicate Predicate) { + switch (Predicate) { + case llvm::CmpInst::ICMP_SLE: + case llvm::CmpInst::ICMP_ULE: + case llvm::CmpInst::ICMP_SGT: + case llvm::CmpInst::ICMP_UGT: + return true; + + case llvm::CmpInst::ICMP_SGE: + case llvm::CmpInst::ICMP_UGE: + case llvm::CmpInst::ICMP_SLT: + case llvm::CmpInst::ICMP_ULT: + return false; + + default: + revng_abort(); + } +} + +static bool isLowerThan(llvm::ICmpInst::Predicate Predicate) { + switch (Predicate) { + case llvm::CmpInst::ICMP_SLE: + case llvm::CmpInst::ICMP_ULE: + case llvm::CmpInst::ICMP_SLT: + case llvm::CmpInst::ICMP_ULT: + return true; + + case llvm::CmpInst::ICMP_SGE: + case llvm::CmpInst::ICMP_UGE: + case llvm::CmpInst::ICMP_SGT: + case llvm::CmpInst::ICMP_UGT: + return false; + + default: + revng_abort(); + } +} + +// x + c1 CMP_?? c2 +static ConstantRangeSet +getRangeFromInequality(const llvm::APInt &C1, + llvm::ICmpInst::Predicate Predicate, + const llvm::APInt &C2) { + using namespace llvm; + auto BitWidth = C1.getBitWidth(); + revng_assert(C2.getBitWidth() == BitWidth); + bool Signed = isSigned(Predicate); + + // Determine min and max + APInt Min; + APInt Max; + if (Signed) { + Min = APInt::getSignedMinValue(BitWidth); + Max = APInt::getSignedMaxValue(BitWidth); + } else { + Min = APInt::getMinValue(BitWidth); + Max = APInt::getMaxValue(BitWidth); + } + + // Determine start and stop ranges + auto Start = APInt::getMinValue(BitWidth) - C1; + auto Stop = C2 - C1; + + // Add +1 to stop if we're dealing with <= or > + if (isInclusive(Predicate)) + Stop += 1; + + // Check if we can represent this with a single range or if we need to + // consider wrap-around + bool Ordered = false; + if (Signed) + Ordered = Start.slt(Stop); + else + Ordered = Start.ult(Stop); + + ConstantRangeSet Result; + if (Start == Stop) { + Result = ConstantRange::getEmpty(BitWidth); + } else if (Ordered) { + Result = ConstantRange(Start, Stop); + } else { + Result = ConstantRangeSet({ Start, Max + 1 }) + | ConstantRangeSet({ Min, Stop }); + } + + if (not isLowerThan(Predicate)) + Result = ~Result; + + return Result; +} + +namespace revng::detail { + +class Visitor { +private: + using CacheEntry = DataFlowRangeAnalysis::CacheEntry; + +private: + llvm::DenseSet Stack; + std::map &Cache; + llvm::Value &Variable; + llvm::ModuleSlotTracker &MST; + +public: + Visitor(std::map &Cache, + llvm::Value &Variable, + llvm::ModuleSlotTracker &MST) : + Cache(Cache), Variable(Variable), MST(MST) {} + +public: + RecursiveCoroutine> + visit(llvm::Value &Constraint); + +private: + ConstantRangeSet *tryGet(llvm::Value &I) { + auto It = Cache.find({ &I, &Variable }); + if (It == Cache.end()) + return nullptr; + return &It->second; + } + + std::optional + record(llvm::Value &I, std::optional &&Result) { + if (not Result.has_value()) { + revng_log(Log, "Returning an empty result"); + return Result; + } + + if (Log.isEnabled()) { + Log << "Returning "; + Result.value().dump(Log); + Log << DoLog; + } + + revng_assert(tryGet(I) == nullptr); + Cache[{ &I, &Variable }] = Result.value(); + return Result; + } + + void pop(llvm::Value &I) { + auto It = Stack.find(&I); + revng_assert(It != Stack.end()); + Stack.erase(It); + } + + class StackEntry { + private: + Visitor *V = nullptr; + llvm::Value *I = nullptr; + + public: + StackEntry(Visitor &V, llvm::Value &I) : V(&V), I(&I) {} + + StackEntry(StackEntry &&Other) { + V = Other.V; + I = Other.I; + Other.I = nullptr; + } + + StackEntry &operator=(StackEntry &&Other) { + V = Other.V; + I = Other.I; + Other.I = nullptr; + return *this; + } + + public: + ~StackEntry() { + if (I != nullptr) + V->pop(*I); + } + }; + + std::optional newStackEntry(llvm::Value &I) { + if (Stack.find(&I) != Stack.end()) + return std::nullopt; + Stack.insert(&I); + return StackEntry(*this, I); + } +}; +} // namespace revng::detail + +std::optional +DataFlowRangeAnalysis::visit(llvm::Value &Constraint, llvm::Value &Variable) { + revng_log(Log, "New analysis"); + LoggerIndent<> Indent(Log); + + if (Log.isEnabled()) { + Log << "Constraint: "; + Constraint.print(*Log.getAsLLVMStream(), MST, true); + Log << DoLog; + + Log << "Variable: "; + Variable.print(*Log.getAsLLVMStream(), MST, true); + Log << DoLog; + } + + revng::detail::Visitor V(Cache, Variable, MST); + return V.visit(Constraint); +} + +// TODO: emit graph while performing the visit +// TODO: cache std::nullopt? +inline RecursiveCoroutine> +revng::detail::Visitor::visit(llvm::Value &Constraint) { + if (Log.isEnabled()) { + Log << "Visiting "; + Constraint.print(*Log.getAsLLVMStream(), MST, true); + Log << DoLog; + } + LoggerIndent<> Indent(Log); + + // Check cache + if (auto *Ranges = tryGet(Constraint)) { + revng_log(Log, "Found in cache: " << Ranges->toString()); + rc_return *Ranges; + } + + // Start processing current instruction, unless there's recursion + auto &&MaybeStackEntry = newStackEntry(Constraint); + bool RecursionDetected = not MaybeStackEntry.has_value(); + if (RecursionDetected) { + revng_log(Log, "Recursion detected, bailing out"); + rc_return std::nullopt; + } + + using namespace llvm::PatternMatch; + + // Note: from here on, remember to wrap in record(Constraint, ...) each + // returned value + + // + // Handle constants + // + if (auto *Constant = dyn_cast(&Constraint)) { + revng_log(Log, "Handling ConstantInt"); + const llvm::APInt &Value = Constant->getValue(); + auto BitWidth = Variable.getType()->getIntegerBitWidth(); + rc_return record(Constraint, ConstantRangeSet(BitWidth, !Value.isZero())); + } + + if (isa(Constraint)) { + revng_log(Log, "Ignoring non-ConstantInt Constant"); + rc_return std::nullopt; + } + + // + // Handle constraints on Variable + // + + // Handle x - 4 < 5 as x >= 4 and x < 9 + using namespace llvm; + ICmpInst::Predicate Predicate{}; + ConstantInt *Addend = nullptr; + ConstantInt *Bound = nullptr; + if (match(&Constraint, + m_ICmp(Predicate, + m_Add(m_Specific(&Variable), m_ConstantInt(Addend)), + m_ConstantInt(Bound))) + and ICmpInst::isRelational(Predicate)) { + revng_log(Log, "Handling inequality"); + rc_return record(Constraint, + getRangeFromInequality(Addend->getValue(), + Predicate, + Bound->getValue())); + } + + // Handle x - 4, i.e., x - 4 != 0, i.e., x != 4 + if (match(&Constraint, m_Add(m_Specific(&Variable), m_ConstantInt(Addend)))) { + revng_log(Log, "Handling exact comparison"); + rc_return record(Constraint, + ~ConstantRangeSet(ConstantRange(-Addend->getValue()))); + } + + // Handle x & 0b1100 == 0b0100, i.e., 0b0100 <= x <= 0b0111 + llvm::ConstantInt *NegatedMask = nullptr; + llvm::ConstantInt *FixedConstant = nullptr; + if (match(&Constraint, + m_ICmp(Predicate, + m_And(m_Specific(&Variable), m_ConstantInt(NegatedMask)), + m_ConstantInt(FixedConstant)))) { + bool IsExactComparison = not ICmpInst::isRelational(Predicate); + const APInt &FixedValue = FixedConstant->getValue(); + auto Masked = FixedValue & NegatedMask->getValue(); + bool FixedValueIsCompatible = Masked == FixedValue; + + APInt MaskValue = ~NegatedMask->getValue(); + if (IsExactComparison and MaskValue.isMask() and FixedValueIsCompatible) { + revng_log(Log, "Handling mask"); + revng_assert(Predicate == ICmpInst::Predicate::ICMP_EQ + or Predicate == ICmpInst::Predicate::ICMP_NE); + + ConstantRangeSet Result({ FixedValue, FixedValue + MaskValue + 1 }); + + if (Predicate == ICmpInst::Predicate::ICMP_NE) + Result = ~Result; + + rc_return record(Constraint, Result); + } + } + + // + // Handle boolean operations from here on + // + + // Handle comparison with zero + Value *Operand = nullptr; + if (match(&Constraint, + m_ICmp(Predicate, m_Value(Operand), m_SpecificInt(0)))) { + if (auto MaybeRange = rc_recur visit(*Operand)) { + revng_log(Log, "Handling comparison with zero"); + if (Predicate == llvm::CmpInst::ICMP_NE) { + rc_return record(Constraint, std::move(MaybeRange)); + } else if (Predicate == llvm::CmpInst::ICMP_EQ) { + rc_return record(Constraint, MaybeRange.value().complement()); + } + } + } + + // Handle bitwise operations + Value *LHS = nullptr; + Value *RHS = nullptr; + if (match(&Constraint, m_BinOp(m_Value(LHS), m_Value(RHS))) + and cast(Constraint).isBitwiseLogicOp()) { + auto MaybeLHSRange = rc_recur visit(*LHS); + auto MaybeRHSRange = rc_recur visit(*RHS); + if (MaybeLHSRange and MaybeRHSRange) { + revng_log(Log, "Handling bitwise operator"); + switch (cast(Constraint).getOpcode()) { + case llvm::Instruction::And: + rc_return record(Constraint, *MaybeLHSRange & *MaybeRHSRange); + case llvm::Instruction::Or: + rc_return record(Constraint, *MaybeLHSRange | *MaybeRHSRange); + default: + revng_abort(); + } + } + } + + // Handle select + Value *Condition = nullptr; + Value *TrueValue = nullptr; + Value *FalseValue = nullptr; + if (match(&Constraint, + m_Select(m_Value(Condition), + m_Value(TrueValue), + m_Value(FalseValue)))) { + auto MaybeConditionRange = rc_recur visit(*Condition); + auto MaybeTrueRange = rc_recur visit(*TrueValue); + auto MaybeFalseRange = rc_recur visit(*FalseValue); + if (MaybeConditionRange and MaybeTrueRange and MaybeFalseRange) { + revng_log(Log, "Handling select"); + rc_return record(Constraint, + (*MaybeConditionRange & *MaybeTrueRange) + | (~*MaybeConditionRange & *MaybeFalseRange)); + } + } + + if (&Constraint == &Variable) { + revng_log(Log, "Returning full set"); + rc_return record(Constraint, + ConstantRangeSet(Constraint.getType() + ->getIntegerBitWidth(), + true)); + } + + revng_log(Log, "Can't handle, bailing out"); + rc_return std::nullopt; +} diff --git a/lib/ValueMaterializer/ValueMaterializer.cpp b/lib/ValueMaterializer/ValueMaterializer.cpp index 6bad6cab7..e6c6492be 100644 --- a/lib/ValueMaterializer/ValueMaterializer.cpp +++ b/lib/ValueMaterializer/ValueMaterializer.cpp @@ -62,7 +62,7 @@ void ValueMaterializer::computeOracleConstraints() { case Oracle::AdvancedValueInfo: std::tie(OracleConstraints, CFEG, - MFIResults) = runAVI(DataFlowGraph, Context, DT, LVI, true); + MFIResults) = runAVI(DataFlowGraph, Context, DT, LVI, DFRA, true); break; default: diff --git a/lib/Yield/Assembly/LLVMDisassemblerInterface.cpp b/lib/Yield/Assembly/LLVMDisassemblerInterface.cpp index 5afad1ccb..ebbfce357 100644 --- a/lib/Yield/Assembly/LLVMDisassemblerInterface.cpp +++ b/lib/Yield/Assembly/LLVMDisassemblerInterface.cpp @@ -32,15 +32,16 @@ static void ensureDisassemblersWereInitializedOnce() { using DI = LLVMDisassemblerInterface; DI::LLVMDisassemblerInterface(MetaAddressType::Values AddrType, const model::DisassemblyConfiguration &Config) { + using namespace model::Architecture; ensureDisassemblersWereInitializedOnce(); - auto LLVMArchitecture = MetaAddressType::arch(AddrType); - revng_assert(LLVMArchitecture.has_value(), + auto LLVMArchitecture = toLLVMArchitecture(MetaAddressType::arch(AddrType)); + revng_assert(MetaAddressType::arch(AddrType) != Invalid, "Impossible to create a disassembler for a non-code section"); - auto Architecture = llvm::Triple::getArchTypeName(*LLVMArchitecture); + auto Architecture = llvm::Triple::getArchTypeName(LLVMArchitecture); // Workaround for ARM - if (*LLVMArchitecture == llvm::Triple::ArchType::arm) + if (LLVMArchitecture == llvm::Triple::ArchType::arm) Architecture = "armv7"; std::string ErrorMessage; @@ -84,8 +85,8 @@ DI::LLVMDisassemblerInterface(MetaAddressType::Values AddrType, InstructionInformation.reset(LLVMTarget->createMCInstrInfo()); unsigned AssemblyDialect = 0; - if (*LLVMArchitecture == llvm::Triple::ArchType::x86 - || *LLVMArchitecture == llvm::Triple::ArchType::x86_64) { + if (LLVMArchitecture == llvm::Triple::ArchType::x86 + or LLVMArchitecture == llvm::Triple::ArchType::x86_64) { if (not Config.UseX86ATTSyntax()) AssemblyDialect = 1; } diff --git a/lib/Yield/Assembly/LLVMTagsToPTML.cpp b/lib/Yield/Assembly/LLVMTagsToPTML.cpp index 5fb1783be..ed688208c 100644 --- a/lib/Yield/Assembly/LLVMTagsToPTML.cpp +++ b/lib/Yield/Assembly/LLVMTagsToPTML.cpp @@ -17,13 +17,12 @@ template std::string yield::sanitizedAddress(const T &Target, const model::Binary &Binary) { const auto &Configuration = Binary.Configuration().Disassembly(); - std::optional SerializationStyle = std::nullopt; + auto SerializationStyle = model::Architecture::Invalid; if (!Configuration.PrintFullMetaAddress()) { - namespace Arch = model::Architecture; - SerializationStyle = Arch::toLLVMArchitecture(Binary.Architecture()); + SerializationStyle = Binary.Architecture(); } - std::string Result = Target.toString(std::move(SerializationStyle)); + std::string Result = Target.toString(SerializationStyle); constexpr std::array ForbiddenCharacters = { ' ', ':', '!', '#', '?', '<', '>', '/', '\\', '{', diff --git a/scripts/extract-helper-names.sh b/scripts/extract-helper-names.sh index 63c2bd562..8ed58da83 100755 --- a/scripts/extract-helper-names.sh +++ b/scripts/extract-helper-names.sh @@ -23,22 +23,6 @@ printf "Name\\n" fi ) | - # TODO: remove `--defined-only` to pick up all the symbols instead of just - # the symbol the helper modules explicitly contain. - # - # As of now, this is necessary because otherwise all the standard C library - # functions helper binaries (both our and external) use populate the helper - # namespace, leading to a heavy decrease in quality of decompiled binaries - # that use c standard library (names like `malloc` and `free` (and so on) - # are no longer allowed, no matter whether they are linked statically or - # dynamically. - # - # Once everything within helper binaries is prefixed, this limitation is no - # more and all the helper symbol names should be banned. - # - # The only downside of not doing so now is that *if* a helper using - # a standard function call gets inlined, it is now calling a non-reserved - # name, which might have a different meaning assigned to it by the model. - llvm-nm - --defined-only --format=just-symbols + llvm-nm - --format=just-symbols done ) | sort -u diff --git a/share/revng/clang-format-style-file.yml b/share/revng/clang-format-style-file.yml index e669e6954..9f9e6263f 100644 --- a/share/revng/clang-format-style-file.yml +++ b/share/revng/clang-format-style-file.yml @@ -81,11 +81,12 @@ { Regex: '^"clang/', Priority: -3 }, { Regex: '^"mlir/', Priority: -4 }, { Regex: '^"llvm/', Priority: -5 }, - { Regex: '^"archive\.h"$', Priority: -6 }, - { Regex: '^"archive_entry\.h"$', Priority: -6 }, - { Regex: '^"nanobind/', Priority: -7 }, - { Regex: '^"boost/', Priority: -8 }, - { Regex: "^<", Priority: -9 }, + { Regex: '^"qemu/', Priority: -6 }, + { Regex: '^"archive\.h"$', Priority: -7 }, + { Regex: '^"archive_entry\.h"$', Priority: -7 }, + { Regex: '^"nanobind/', Priority: -8 }, + { Regex: '^"boost/', Priority: -9 }, + { Regex: "^<", Priority: -10 }, ], IncludeIsMainRegex: "_THIS_SEQUENCE_IS_NEVER_GOING_TO_HAPPEN", IncludeIsMainSourceRegex: "_THIS_SEQUENCE_IS_NEVER_GOING_TO_HAPPEN", diff --git a/share/revng/support.c b/share/revng/support.c index 25c108c57..665189eaf 100644 --- a/share/revng/support.c +++ b/share/revng/support.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -213,12 +214,6 @@ uintptr_t qemu_real_host_page_mask = ~((1 << 12) - 1); uintptr_t qemu_host_page_size = 1 << 12; uintptr_t qemu_host_page_mask = ~((1 << 12) - 1); -void page_set_flags(target_reg start, target_reg end, int flags) { -} - -void tb_invalidate_phys_range(target_reg start, target_reg end) { -} - const char *path(const char *name) { return name; } @@ -227,10 +222,26 @@ void *g_realloc(void *mem, size_t size) { return realloc(mem, size); } +void *g_realloc_n(void *mem, size_t n, size_t size) { + return realloc(mem, n * size); +} + void *g_malloc0_n(size_t n, size_t size) { return calloc(n, size); } +void *g_malloc0(size_t n) { + return calloc(n, 1); +} + +void *g_malloc_n(size_t n, size_t size) { + return calloc(n, size); +} + +void *g_try_malloc0_n(size_t n, size_t size) { + return calloc(n, size); +} + void *g_malloc(size_t n_bytes) { if (n_bytes == 0) return NULL; @@ -238,6 +249,20 @@ void *g_malloc(size_t n_bytes) { return malloc(n_bytes); } +void *g_try_malloc(size_t n_bytes) { + if (n_bytes == 0) + return NULL; + else + return malloc(n_bytes); +} + +void *g_try_malloc_n(size_t n, size_t size) { + if (n == 0 || size == 0) + return NULL; + else + return calloc(n, size); +} + void g_free(void *memory) { if (memory == NULL) return; @@ -245,16 +270,41 @@ void g_free(void *memory) { return free(memory); } -void g_assertion_message_expr(const char *domain, - const char *file, - int line, - const char *func, - const char *expr) { +size_t g_strlcpy(char *dest, const char *src, size_t dest_size) { + char *d = dest; + const char *s = src; + size_t n = dest_size; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) + do { + char c = *s++; + + *d++ = c; + if (c == 0) + break; + } while (--n != 0); + + /* If not enough room in dest, add NUL and traverse rest of src */ + if (n == 0) { + if (dest_size != 0) + *d = 0; + while (*s++) + ; + } + + /* Count does not include NUL */ + return s - src - 1; +} + +int memfd_create(const char *name, unsigned int flags) { + errno = ENOSYS; + return -1; } void unknown_pc() { int arg; - fprintf(stderr, "Unknown PC:"); + fprintf(stderr, "Unknown PC: "); fprint_metaaddress(stderr, ¤t_pc); fprintf(stderr, "\n"); @@ -365,14 +415,12 @@ void newpc(uint64_t pc, void init_tracing(void) { } -void on_exit_syscall(void) { -} - -void newpc(uint64_t pc, +void newpc(const char *pc, uint64_t instruction_size, uint32_t is_first, uint8_t *vars, ...) { + // fprintf(stderr, "%s\n", pc); } #endif @@ -423,22 +471,35 @@ int main(int argc, char *argv[]) { // Initialize the tracing system init_tracing(); - // Allocate and initialize the stack - void *stack = mmap((void *) NULL, - 16 * 0x100000, + // Allocate and initialize the stack and brk + size_t page_size = 0x1000; + + int mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE; + + void *stack_address = NULL; + size_t stack_size = 16 * 0x100 * page_size; + + void *brk_address = NULL; + size_t brk_size = page_size; + + if (sizeof(target_reg) * 8 == 32) { + mmap_flags |= MAP_32BIT; + } + + void *stack = mmap(stack_address, + stack_size, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE, + mmap_flags, -1, 0) - + 16 * 0x100000 - 0x1000; + + stack_size - page_size; assert(stack != NULL); stack = prepare_stack(stack, argc, argv); - // Allocate the brk page - void *brk = mmap((void *) NULL, - 0x1000, + void *brk = mmap(brk_address, + brk_size, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE, + mmap_flags, -1, 0); assert(brk != NULL); @@ -453,13 +514,6 @@ int main(int argc, char *argv[]) { // Implant custom SIGSEGV handler install_sigsegv_handler(); -#ifdef TARGET_x86_64 - unsigned long fs_value; - int result = arch_prctl(ARCH_GET_FS, &fs_value); - assert(result == 0); - set_register(REGISTER_FS, fs_value); -#endif - // Run the translated program SAFE_CAST(stack); root((uintptr_t) stack); diff --git a/tests/unit/AdvancedValueInfo.cpp b/tests/unit/AdvancedValueInfo.cpp index 2b417bac8..49e422676 100644 --- a/tests/unit/AdvancedValueInfo.cpp +++ b/tests/unit/AdvancedValueInfo.cpp @@ -15,10 +15,12 @@ bool init_unit_test(); #include "llvm/InitializePasses.h" #include "llvm/Transforms/Scalar.h" +#include "revng/ADT/Queue.h" #include "revng/Support/Debug.h" #include "revng/Support/IRHelpers.h" #include "revng/UnitTestHelpers/LLVMTestHelpers.h" #include "revng/UnitTestHelpers/UnitTestHelpers.h" +#include "revng/ValueMaterializer/DataFlowRangeAnalysis.h" #include "revng/ValueMaterializer/ValueMaterializer.h" using namespace llvm; @@ -81,20 +83,22 @@ bool TestAdvancedValueInfoPass::runOnModule(llvm::Module &M) { auto &DT = getAnalysis(Root).getDomTree(); MockupMemoryOracle MO(M.getDataLayout()); + DataFlowRangeAnalysis DFRA(M); for (User *U : M.getGlobalVariable("pc", true)->users()) { if (auto *Store = dyn_cast(U)) { Value *V = Store->getValueOperand(); - auto - MaybeValues = ValueMaterializer::getValuesFor(Store, - V, - MO, - LVI, - DT, - {}, - Oracle::AdvancedValueInfo) - .values(); + auto AVIOracle = Oracle::AdvancedValueInfo; + auto MaybeValues = ValueMaterializer::getValuesFor(Store, + V, + MO, + LVI, + DFRA, + DT, + {}, + AVIOracle) + .values(); if (MaybeValues) { (*Results)[V] = *MaybeValues; } @@ -512,3 +516,150 @@ end: aI64(33), aI64(34) } } }); } + +static void testVisit(const char *Body, + std::optional Expected) { + LLVMContext C; + std::unique_ptr Module = loadModule(C, Body); + Function *Main = Module->getFunction("main"); + BasicBlock &BB = Main->getEntryBlock(); + llvm::Instruction *Variable = &*BB.begin(); + revng_assert(isa(Variable)); + auto *Condition = cast(*BB.rbegin()).getCondition(); + llvm::Instruction *Constraint = cast(Condition); + + DataFlowRangeAnalysis Context(*Module); + auto Result = Context.visit(*Constraint, *Variable); + + if (Result != Expected) { + Main->dump(); + + dbg << "Result: "; + if (Result.has_value()) + Result->dump(); + else + dbg << "nullopt"; + dbg << "\n"; + dbg << "Expected: "; + if (Expected.has_value()) + Expected->dump(); + else + dbg << "nullopt"; + dbg << "\n"; + } + + revng_check(Result == Expected); +} + +BOOST_AUTO_TEST_CASE(TestDataFlowRangeAnalysis) { + auto Range = [](uint64_t Lower, uint64_t Upper) { + return ConstantRange(APInt(64, Lower), APInt(64, Upper)); + }; + + const char *Body = nullptr; + + // Test x - 4 < 5 + Body = R"LLVM( + %rdi = load i64, i64* @rdi + %add = add i64 %rdi, -4 + %cmp = icmp ult i64 %add, 5 + br i1 %cmp, label %a, label %b + a: + unreachable + b: + unreachable + )LLVM"; + testVisit(Body, ConstantRangeSet(Range(4, 9))); + + // Test x - 4 != 0 + Body = R"LLVM( + %rdi = load i64, i64* @rdi + %add = add i64 %rdi, -4 + %cmp = icmp ne i64 %add, 0 + br i1 %cmp, label %a, label %b + a: + unreachable + b: + unreachable + )LLVM"; + testVisit(Body, ConstantRangeSet(Range(5, 4))); + + // Test and with bitmask + Body = R"LLVM( + %rdi = load i64, i64* @rdi + %and = and i64 %rdi, -4 + %cmp = icmp eq i64 %and, 12 + br i1 %cmp, label %a, label %b + a: + unreachable + b: + unreachable + )LLVM"; + testVisit(Body, ConstantRangeSet(Range(12, 16))); + + // Test or + Body = R"LLVM( + %rdi = load i64, i64* @rdi + + %add1 = add i64 %rdi, -4 + %cmp1 = icmp ult i64 %add1, 5 + + %add2 = add i64 %rdi, -20 + %cmp2 = icmp ult i64 %add2, 10 + + %or = or i1 %cmp1, %cmp2 + + br i1 %or, label %a, label %b + a: + unreachable + b: + unreachable + )LLVM"; + testVisit(Body, + ConstantRangeSet(Range(4, 9)) | ConstantRangeSet(Range(20, 30))); + + // Test and + Body = R"LLVM( + %rdi = load i64, i64* @rdi + + %add1 = add i64 %rdi, -10 + %cmp1 = icmp ult i64 %add1, 20 + + %add2 = add i64 %rdi, -20 + %cmp2 = icmp ult i64 %add2, 20 + + %or = and i1 %cmp1, %cmp2 + + br i1 %or, label %a, label %b + a: + unreachable + b: + unreachable + )LLVM"; + testVisit(Body, ConstantRangeSet(Range(20, 30))); + + // Test select + // [30, 60] ? [20, 40] : [50, 70] + Body = R"LLVM( + %rdi = load i64, i64* @rdi + + %add1 = add i64 %rdi, -30 + %cmp1 = icmp ult i64 %add1, 30 + + %add2 = add i64 %rdi, -20 + %cmp2 = icmp ult i64 %add2, 20 + + %add3 = add i64 %rdi, -50 + %cmp3 = icmp ult i64 %add3, 20 + + %select = select i1 %cmp1, i1 %cmp2, i1 %cmp3 + + br i1 %select, label %a, label %b + a: + unreachable + b: + unreachable + )LLVM"; + testVisit(Body, + ConstantRangeSet(Range(30, 40)) | ConstantRangeSet(Range(60, 70))); +} diff --git a/tests/unit/MetaAddress.cpp b/tests/unit/MetaAddress.cpp index 1e9c2e23c..b009ea169 100644 --- a/tests/unit/MetaAddress.cpp +++ b/tests/unit/MetaAddress.cpp @@ -18,17 +18,18 @@ BOOST_TEST_DONT_PRINT_LOG_VALUE(MetaAddress) BOOST_TEST_DONT_PRINT_LOG_VALUE(MetaAddressType::Values) using namespace llvm; +using namespace model::Architecture; static MetaAddress generic32(uint64_t Address) { - return MetaAddress::fromGeneric(Triple::x86, Address); + return MetaAddress::fromGeneric(x86, Address); } static MetaAddress generic64(uint64_t Address) { - return MetaAddress::fromGeneric(Triple::x86_64, Address); + return MetaAddress::fromGeneric(x86_64, Address); } static MetaAddress pc(uint64_t Address) { - return MetaAddress::fromPC(Triple::x86, Address); + return MetaAddress::fromPC(x86, Address); } BOOST_AUTO_TEST_CASE(Constructor) { @@ -51,11 +52,11 @@ BOOST_AUTO_TEST_CASE(Factory) { // Code BOOST_TEST(pc(0x1000).isCode()); - BOOST_TEST(pc(0x1000).isCode(Triple::x86)); + BOOST_TEST(pc(0x1000).isCode(x86)); // Regular ARM and Thumb are both ARM - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0x1000).isCode(Triple::arm)); - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0x1001).isCode(Triple::arm)); + BOOST_TEST(MetaAddress::fromPC(arm, 0x1000).isCode(arm)); + BOOST_TEST(MetaAddress::fromPC(arm, 0x1001).isCode(arm)); // Generic BOOST_TEST(generic64(0x1000).isGeneric()); @@ -64,8 +65,8 @@ BOOST_AUTO_TEST_CASE(Factory) { BOOST_TEST(pc(0x1000).toGeneric().isGeneric()); // bitSize - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0).bitSize() == uint64_t(32)); - BOOST_TEST(MetaAddress::fromPC(Triple::aarch64, 0).bitSize() == uint64_t(64)); + BOOST_TEST(MetaAddress::fromPC(arm, 0).bitSize() == uint64_t(32)); + BOOST_TEST(MetaAddress::fromPC(aarch64, 0).bitSize() == uint64_t(64)); // Epoch BOOST_TEST(generic64(0).epoch() == uint64_t(0)); @@ -80,7 +81,7 @@ BOOST_AUTO_TEST_CASE(Accessors) { BOOST_TEST(MetaAddress::invalid().asPCOrZero() == uint64_t(0)); BOOST_TEST(pc(0x1000).asPC() == uint64_t(0x1000)); using MA = MetaAddress; - BOOST_TEST(MA::fromPC(Triple::arm, 0x1001).asPC() == uint64_t(0x1001)); + BOOST_TEST(MA::fromPC(arm, 0x1001).asPC() == uint64_t(0x1001)); BOOST_TEST(generic64(0x1000).address() == uint64_t(0x1000)); } @@ -106,12 +107,12 @@ BOOST_AUTO_TEST_CASE(Overflow) { } BOOST_AUTO_TEST_CASE(Thumb) { - auto NonThumb = MetaAddress::fromPC(Triple::arm, 0x1000); + auto NonThumb = MetaAddress::fromPC(arm, 0x1000); BOOST_TEST(NonThumb.type() == MetaAddressType::Code_arm); BOOST_TEST(NonThumb.address() == uint64_t(0x1000)); BOOST_TEST(NonThumb.asPC() == uint64_t(0x1000)); - auto Thumb = MetaAddress::fromPC(Triple::arm, 0x1001); + auto Thumb = MetaAddress::fromPC(arm, 0x1001); BOOST_TEST(Thumb.type() == MetaAddressType::Code_arm_thumb); BOOST_TEST(Thumb.address() == uint64_t(0x1000)); BOOST_TEST(Thumb.asPC() == uint64_t(0x1001)); @@ -119,38 +120,38 @@ BOOST_AUTO_TEST_CASE(Thumb) { BOOST_AUTO_TEST_CASE(Alignment) { // Regular ARM - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0x1000).isValid()); + BOOST_TEST(MetaAddress::fromPC(arm, 0x1000).isValid()); // Thumb aligned at 4-bytes - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0x1001).isValid()); + BOOST_TEST(MetaAddress::fromPC(arm, 0x1001).isValid()); // Thumb aligned at 2-bytes - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0x1003).isValid()); + BOOST_TEST(MetaAddress::fromPC(arm, 0x1003).isValid()); // Misaligned regular ARM - BOOST_TEST(MetaAddress::fromPC(Triple::arm, 0x1002).isInvalid()); + BOOST_TEST(MetaAddress::fromPC(arm, 0x1002).isInvalid()); // MIPS - BOOST_TEST(MetaAddress::fromPC(Triple::mips, 0x1000).isValid()); - BOOST_TEST(MetaAddress::fromPC(Triple::mips, 0x1001).isInvalid()); - BOOST_TEST(MetaAddress::fromPC(Triple::mips, 0x1002).isInvalid()); - BOOST_TEST(MetaAddress::fromPC(Triple::mips, 0x1003).isInvalid()); + BOOST_TEST(MetaAddress::fromPC(mips, 0x1000).isValid()); + BOOST_TEST(MetaAddress::fromPC(mips, 0x1001).isInvalid()); + BOOST_TEST(MetaAddress::fromPC(mips, 0x1002).isInvalid()); + BOOST_TEST(MetaAddress::fromPC(mips, 0x1003).isInvalid()); // x86 - BOOST_TEST(MetaAddress::fromPC(Triple::x86, 0x1000).isValid()); - BOOST_TEST(MetaAddress::fromPC(Triple::x86, 0x1001).isValid()); - BOOST_TEST(MetaAddress::fromPC(Triple::x86, 0x1002).isValid()); - BOOST_TEST(MetaAddress::fromPC(Triple::x86, 0x1003).isValid()); + BOOST_TEST(MetaAddress::fromPC(x86, 0x1000).isValid()); + BOOST_TEST(MetaAddress::fromPC(x86, 0x1001).isValid()); + BOOST_TEST(MetaAddress::fromPC(x86, 0x1002).isValid()); + BOOST_TEST(MetaAddress::fromPC(x86, 0x1003).isValid()); // SystemZ - BOOST_TEST(MetaAddress::fromPC(Triple::systemz, 0x1000).isValid()); - BOOST_TEST(MetaAddress::fromPC(Triple::systemz, 0x1001).isInvalid()); - BOOST_TEST(MetaAddress::fromPC(Triple::systemz, 0x1002).isValid()); + BOOST_TEST(MetaAddress::fromPC(systemz, 0x1000).isValid()); + BOOST_TEST(MetaAddress::fromPC(systemz, 0x1001).isInvalid()); + BOOST_TEST(MetaAddress::fromPC(systemz, 0x1002).isValid()); } BOOST_AUTO_TEST_CASE(Comparison) { - auto A = MetaAddress::fromGeneric(Triple::x86, 0x1000); - auto B = MetaAddress::fromGeneric(Triple::x86, 0x1001); + auto A = MetaAddress::fromGeneric(x86, 0x1000); + auto B = MetaAddress::fromGeneric(x86, 0x1001); BOOST_TEST(A.addressLowerThan(B)); BOOST_TEST(A != B); @@ -178,8 +179,8 @@ BOOST_AUTO_TEST_CASE(Map) { Map[generic64(0)] = 1; Map[MetaAddress::invalid()] = 1; Map[pc(0)] = 1; - Map[MetaAddress::fromPC(Triple::arm, 0)] = 1; - Map[MetaAddress::fromPC(Triple::arm, 1)] = 1; + Map[MetaAddress::fromPC(arm, 0)] = 1; + Map[MetaAddress::fromPC(arm, 1)] = 1; BOOST_TEST(Map.size() == size_t(5)); } diff --git a/tests/unit/Model.cpp b/tests/unit/Model.cpp index 1d0652fa7..e852ed034 100644 --- a/tests/unit/Model.cpp +++ b/tests/unit/Model.cpp @@ -23,6 +23,8 @@ bool init_unit_test(); using namespace model; +static_assert(revng::__any_imp::_IsSmallObject::value); + auto ARM1000 = MetaAddress::fromString("0x1000:Code_arm"); auto ARM2000 = MetaAddress::fromString("0x2000:Code_arm"); auto ARM3000 = MetaAddress::fromString("0x3000:Code_arm"); @@ -346,7 +348,7 @@ BOOST_AUTO_TEST_CASE(TrackingPushAndPopperShouldCompile) { BOOST_AUTO_TEST_CASE(CollectReadFieldsShouldBeEmptyAtFirst) { model::Binary Model; - auto MetaAddress = MetaAddress::fromPC(llvm::Triple::ArchType::x86_64, 0); + auto MetaAddress = MetaAddress::fromPC(model::Architecture::x86_64, 0); Model.Segments().insert(Segment(MetaAddress, 1000)); revng::Tracking::clearAndResume(Model); @@ -362,10 +364,11 @@ toTupleTreePaths(const std::vector &Strings) { return Result; } +using TupleTreePathSet = decltype(ReadFields::Read); + BOOST_AUTO_TEST_CASE(CollectReadFieldsShouldCollectSegments) { model::Binary Model; - const auto MetaAddress = MetaAddress::fromPC(llvm::Triple::ArchType::x86_64, - 0); + const auto MetaAddress = MetaAddress::fromPC(model::Architecture::x86_64, 0); Model.Segments().insert(Segment(MetaAddress, 1000)); revng::Tracking::clearAndResume(Model); const auto &ConstModel = Model; @@ -381,8 +384,7 @@ BOOST_AUTO_TEST_CASE(CollectReadFieldsShouldCollectSegments) { BOOST_AUTO_TEST_CASE(CollectReadFieldsShouldCollectNotFoundSegments) { model::Binary Model; - const auto MetaAddress = MetaAddress::fromPC(llvm::Triple::ArchType::x86_64, - 0); + const auto MetaAddress = MetaAddress::fromPC(model::Architecture::x86_64, 0); revng::Tracking::clearAndResume(Model); const auto &ConstModel = Model; ConstModel.Segments().tryGet(Segment::Key(MetaAddress, 1000)); @@ -396,8 +398,7 @@ BOOST_AUTO_TEST_CASE(CollectReadFieldsShouldCollectNotFoundSegments) { BOOST_AUTO_TEST_CASE(CollectReadFieldsShouldCollectAllSegments) { model::Binary Model; - const auto MetaAddress = MetaAddress::fromPC(llvm::Triple::ArchType::x86_64, - 0); + const auto MetaAddress = MetaAddress::fromPC(model::Architecture::x86_64, 0); Model.Segments().insert(Segment(MetaAddress, 1000)); revng::Tracking::clearAndResume(Model); const auto &ConstModel = Model; diff --git a/tools/model/import/debug-info/Main.cpp b/tools/model/import/debug-info/Main.cpp index f491ce6ce..586275f22 100644 --- a/tools/model/import/debug-info/Main.cpp +++ b/tools/model/import/debug-info/Main.cpp @@ -13,6 +13,7 @@ #include "llvm/Support/ToolOutputFile.h" #include "revng/ABI/DefaultFunctionPrototype.h" +#include "revng/Model/Architecture.h" #include "revng/Model/Importer/Binary/Options.h" #include "revng/Model/Importer/DebugInfo/DwarfImporter.h" #include "revng/Model/Importer/DebugInfo/PDBImporter.h" @@ -87,8 +88,9 @@ int main(int Argc, char *Argv[]) { Model->DefaultPrototype() = abi::registerDefaultFunctionPrototype(*Model); const llvm::object::pe32_header *PE32Header = Binary->getPE32Header(); + auto Architecture = model::Architecture::fromLLVMArchitecture(LLVMArch); if (PE32Header) { - ImageBase = MetaAddress::fromPC(LLVMArch, PE32Header->ImageBase); + ImageBase = MetaAddress::fromPC(Architecture, PE32Header->ImageBase); } else { const llvm::object::pe32plus_header *PE32PlusHeader = Binary->getPE32PlusHeader(); @@ -96,7 +98,7 @@ int main(int Argc, char *Argv[]) { return EXIT_FAILURE; // PE32+ Header. - ImageBase = MetaAddress::fromPC(LLVMArch, PE32PlusHeader->ImageBase); + ImageBase = MetaAddress::fromPC(Architecture, PE32PlusHeader->ImageBase); } PDBImporter Importer(Model, ImageBase); Importer.import(*Binary, Options);