// // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/Argument.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalValue.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/ModuleSlotTracker.h" #include "llvm/IR/Value.h" #include "llvm/IR/ValueHandle.h" #include "llvm/Pass.h" #include "llvm/Support/Casting.h" #include "llvm/Support/InstructionCost.h" #include "llvm/Transforms/Utils.h" #include "llvm/Transforms/Utils/Local.h" #include "revng/ABI/FunctionType/Layout.h" #include "revng/ADT/EagerMaterializationRangeIterator.h" #include "revng/Model/FunctionTags.h" #include "revng/Support/Debug.h" #include "revng/Support/IRBuilder.h" #include "revng/Support/IRHelpers.h" using llvm::cast; using llvm::dyn_cast; using llvm::isa; static constexpr const char *ArithmeticToGEPFlag = "arithmetic-to-gep"; static Logger FindLog{ "arithmetic-to-gep-find-pointers" }; struct ArithmeticToGEPPass : public llvm::FunctionPass { public: static char ID; public: ArithmeticToGEPPass() : FunctionPass(ID) {} public: bool runOnFunction(llvm::Function &F) override; void getAnalysisUsage(llvm::AnalysisUsage &AU) const override { AU.setPreservesCFG(); } }; static bool isExtractValue(const llvm::Value &V) { return isa(V) or isCallToTagged(&V, FunctionTags::OpaqueExtractValue); } static unsigned getExtractValueNumIndices(const llvm::Value &V) { revng_assert(isExtractValue(V)); if (const auto *E = dyn_cast(&V)) return E->getNumIndices(); // If it's an OpaqueExtractValue, we only ever admit a single index. return 1; } static const llvm::Value *getAggregateOperand(const llvm::Value &V) { revng_assert(isExtractValue(V)); revng_assert(getExtractValueNumIndices(V) == 1); if (const auto *E = dyn_cast(&V)) return E->getAggregateOperand(); const llvm::CallInst *C = getCallToTagged(&V, FunctionTags::OpaqueExtractValue); return C->getArgOperand(0); } static uint64_t getExtractedFieldIndex(const llvm::Value &V) { revng_assert(isExtractValue(V)); revng_assert(getExtractValueNumIndices(V) == 1); if (const auto *E = dyn_cast(&V)) return E->getIndices()[0]; const llvm::CallInst *C = getCallToTagged(&V, FunctionTags::OpaqueExtractValue); const auto *Index = llvm::cast(C->getArgOperand(1)); return Index->getZExtValue(); } // // Helper functions to detect if `llvm::Value`s are pointers, even if their // `llvm::Type` is not a pointer. // This is done using additional information coming from Model types and from // specific instances of `llvm::Value` that can be associated to those types in // the Model. // This association is attached to `llvm::Value`s via the revng.is_pointer // metadata. // static bool isArgumentModelPointer(const llvm::Argument *A) { const llvm::Function *F = A->getParent(); std::optional> PointerArguments = getPointerOperandsMetadata(F); // The metadata can be missing, if for some reason A is an argument of an // llvm::Function that doesn't represent something coming from the binary // (e.g. a QEMU helper, or an LLVM intrinsic). But if it's present, the ArgNo // must be in range, otherwise something severely wrong is going on. revng_assert(not PointerArguments.has_value() or A->getArgNo() < PointerArguments.value().size()); return PointerArguments.has_value() and PointerArguments.value()[A->getArgNo()]; } static bool isNthArgumentModelPointer(const llvm::CallInst *Call, uint64_t ArgumentIndex) { if (const llvm::Function *Callee = getCallee(Call)) return isArgumentModelPointer(Callee->getArg(ArgumentIndex)); std::optional> PointerArguments = getPointerOperandsMetadata(Call); // The metadata cannot be missing, because this is an indirect call, and // indirect calls can only call llvm::Functions that represent something // coming from the binary, hence the metadata must be there. revng_assert(PointerArguments.has_value()); revng_assert(ArgumentIndex < PointerArguments.value().size()); return PointerArguments.value()[ArgumentIndex]; } static bool returnsModelPointer(const llvm::Function *F) { std::optional> PointerReturnValues = getPointerValuesMetadata(F); // The metadata can be missing, if for some reason F is an // llvm::Function that doesn't represent something coming from the binary // (e.g. a QEMU helper, or an LLVM intrinsic). return PointerReturnValues.has_value() and PointerReturnValues.value().size() == 1 and PointerReturnValues.value()[0]; } static bool returnsModelPointer(const llvm::CallInst *Call) { if (const llvm::Function *Callee = getCallee(Call)) return returnsModelPointer(Callee); std::optional> PointerReturnValues = getPointerValuesMetadata(Call); // The metadata cannot be missing, because this is an indirect call, and // indirect calls can only call llvm::Functions that represent something // coming from the binary, hence the metadata must be there. revng_assert(PointerReturnValues.has_value()); return PointerReturnValues.value().size() == 1 and PointerReturnValues.value()[0]; } static bool isNthReturnValueModelPointer(const llvm::Function *F, unsigned ReturnValueIndex) { std::optional> PointerReturnValues = getPointerValuesMetadata(F); // The metadata can be missing, if F doesn't represent a model::Function nor a // model::DynamicFunction. Otherwise it must be well formed. revng_assert(not PointerReturnValues.has_value() or ReturnValueIndex < PointerReturnValues.value().size()); return PointerReturnValues.has_value() and PointerReturnValues.value()[ReturnValueIndex]; } static bool isNthReturnValueModelPointer(const llvm::CallInst *Call, unsigned ReturnValueIndex) { if (const llvm::Function *Callee = getCallee(Call)) return isNthReturnValueModelPointer(Callee, ReturnValueIndex); std::optional> PointerReturnValues = getPointerValuesMetadata(Call); // The metadata cannot be missing, because this is an indirect call, and // indirect calls can only call llvm::Functions that represent something // coming from the binary, hence the metadata must be there. revng_assert(PointerReturnValues.has_value()); revng_assert(ReturnValueIndex < PointerReturnValues.value().size()); return PointerReturnValues.has_value() and PointerReturnValues.value()[ReturnValueIndex]; } static bool isExtractedValueModelPointer(const llvm::Value &V) { revng_assert(isExtractValue(V)); const auto *AggregateCall = dyn_cast(getAggregateOperand(V)); if (not AggregateCall) return false; uint64_t FieldIndex = getExtractedFieldIndex(V); return isNthReturnValueModelPointer(AggregateCall, FieldIndex); } static bool isPointer(const llvm::Value &V) { if (V.getType()->isPointerTy()) return true; if (const auto *A = dyn_cast(&V)) return isArgumentModelPointer(A); if (const auto *Call = dyn_cast(&V)) if (returnsModelPointer(Call)) return true; if (isExtractValue(V)) return isExtractedValueModelPointer(V); return false; } [[maybe_unused]] static bool isPointer(const llvm::Use &U) { return isPointer(*U.get()); } // // Helper functions to detect if an `llvm::Use` implies that the used // `llvm::Value` is a pointer, even if its `llvm::Type` is not a pointer. // This is done using additional information coming from Model types and from // specific instances of `llvm::User`s` whose uses in certain situations can be // associated to those types in the Model. // static bool isLLVMPointerUse(const llvm::Use &U) { return U->getType()->isPointerTy(); } static bool isModelPointerUse(const llvm::Use &U) { const llvm::User *TheUser = U.getUser(); if (not isa(TheUser)) return false; const llvm::Function &F = *cast(TheUser)->getFunction(); if (const auto *Ret = dyn_cast(TheUser)) return returnsModelPointer(&F); const auto *C = dyn_cast(TheUser); if (not C) return false; // The callee is always a pointer. if (C->isCallee(&U)) return true; revng_assert(C->isArgOperand(&U)); uint64_t ArgNo = C->getArgOperandNo(&U); if (const auto *Initializer = getCallToTagged(TheUser, FunctionTags::StructInitializer)) { return isNthReturnValueModelPointer(&F, ArgNo); } return isNthArgumentModelPointer(C, ArgNo); } static bool isPointerUse(const llvm::Use &U) { return isLLVMPointerUse(U) or isModelPointerUse(U); } // // Helpers for what is considered a local or a global in this file. // static bool isLocal(const llvm::Value *V) { return isa(V) or isa(V); } [[maybe_unused]] static bool isLocal(const llvm::Use *U) { return isLocal(U->get()); } static bool isGlobal(const llvm::Value *V) { return isa(V) or isa(V) or isa(V); } static bool isGlobal(const llvm::Use *U) { return isGlobal(U->get()); } // // Helper concepts for constraining LocalValue constructors, assignments // template concept DerivedFromInstruction = std::derived_from; template concept DerivedFromArgument = std::derived_from; template concept DerivedFromLocal = DerivedFromInstruction or DerivedFromArgument; template concept ConstDerivedFromLocal = DerivedFromLocal and std::is_const_v; template concept MutableDerivedFromLocal = DerivedFromInstruction and not std::is_const_v; // Wrapper for an llvm::Value that for global values only exposes a single use // at a time. This allows to treat e.g. each use of a constant in a Function as // if they were effectively separate values. template class LocalValue { public: using ValueType = std::conditional_t; using UseType = std::conditional_t; using UserType = std::conditional_t; using UseIterator = std::conditional_t; private: // The llvm::Value being wrapped. ValueType *WrappedValue; // When WrappedValue is not DerivedFromLocal, this refers to the single user // of WrappedValue that we want to consider. UseType *WrappedUse; public: bool verify() const { if (WrappedUse) { // If the Use is not null, so must be the Value, and it must be a global, // and the Use must be using V. return WrappedValue and isGlobal(WrappedValue) and WrappedUse->get() == WrappedValue; } if (WrappedValue) { // If the Value is not null, and the Use is missing, the Value must be a // local. return isLocal(WrappedValue); } return true; } private: LocalValue(ValueType *V, UseType *U) : WrappedValue(V), WrappedUse(U) { revng_assert(this->verify()); } public: // No need to mark this explicit. Initializing from nullptr explicitly is // never ambiguous and can never lead to problems. // All other constructors are marked as explicit because they verify LocalValue(nullptr_t) : LocalValue(nullptr, nullptr) {} LocalValue() : LocalValue(nullptr) {} public: LocalValue(const LocalValue &Other) = default; LocalValue &operator=(const LocalValue &Other) = default; LocalValue(LocalValue &&Other) = default; LocalValue &operator=(LocalValue &&Other) = default; ~LocalValue() = default; // Always enable assigning a Value to a LocalValue. template LocalValue &operator=(LocalValueType *V) { return *this = LocalValue(V); } // Enable assigning a const Value to a LocalValue, only if IsConst. template LocalValue &operator=(ConstLocalValueType *V) requires IsConst { return *this = LocalValue(V); } // Always enable assigning a Use to a LocalValue. LocalValue &operator=(llvm::Use *U) { return *this = LocalValue(U); } // Enable assigning a const Use to a LocalValue, only if IsConst. LocalValue &operator=(const llvm::Use *U) requires IsConst { return *this = LocalValue(U); } // Needed by conversion operator template friend class LocalValue; // Enable converting an mutable LocalValue to a const one. // This conversion doesn't need to be explicit. In fact it's ergonomic for it // not to be, without risks. operator LocalValue() requires(not IsConst) { LocalValue Result; Result.WrappedValue = this->WrappedValue; Result.WrappedUse = this->WrappedUse; return Result; } public: LocalValue(ValueType *V) : LocalValue(V, nullptr) { revng_assert(this->verify()); } LocalValue(UseType *U) : LocalValue(U ? U->get() : nullptr, (U and isGlobal(U)) ? U : nullptr) {} public: friend std::strong_ordering operator<=>(const LocalValue &LHS, const LocalValue &RHS) = default; friend bool operator==(const LocalValue &LHS, const LocalValue &RHS) = default; public: llvm::SmallVector uses() const { llvm::SmallVector Result; if (WrappedUse) { Result.push_back(WrappedUse); } else { Result.reserve(WrappedValue->getNumUses()); for (UseType &U : WrappedValue->uses()) Result.push_back(&U); } return Result; } llvm::SmallVector, 4> users() const { llvm::SmallVector, 4> Result; llvm::transform(this->uses(), std::back_inserter(Result), [](UseType *U) { return LocalValue(U->getUser()); }); return Result; } public: ValueType *value() const { return WrappedValue; } UseType *use() const { return WrappedUse; } llvm::Type *getType() const { return WrappedValue->getType(); } }; // // Helper concepts for Value-based deduction guides for LocalValue // template concept DerivedFromValue = std::derived_from; template concept ConstDerivedFromValue = std::is_const_v and DerivedFromValue; template concept MutableDerivedFromValue = not std::is_const_v and DerivedFromValue; // // Value-based deduction guide for LocalValue // template LocalValue(ConstValue *V) -> LocalValue; template LocalValue(MutableValue *V) -> LocalValue; // // Helper concepts for Use-based deduction guides for LocalValue // template concept DerivedFromUse = std::derived_from; template concept ConstDerivedFromUse = std::is_const_v and DerivedFromUse; template concept MutableDerivedFromUse = not std::is_const_v and DerivedFromUse; // // Use-based deduction guide for LocalValue // template LocalValue(ConstUse *U) -> LocalValue; template LocalValue(MutableUse *U) -> LocalValue; // // Graph traits for LocalValue // template struct llvm::GraphTraits> { private: using EagerRange = EagerMaterializationRangeIterator>; public: using NodeRef = LocalValue; using ChildIteratorType = EagerRange; public: static ChildIteratorType child_begin(NodeRef N) { return { N.users() }; } static ChildIteratorType child_end(NodeRef N) { return { { nullptr } }; } public: static NodeRef getEntryNode(NodeRef V) { return LocalValue{ nullptr }; } }; class PointersFinder { private: using Help = llvm::CalculateSmallVectorDefaultInlinedElements>; static constexpr unsigned SmallSize = Help::value; llvm::ModuleSlotTracker MST; llvm::SmallVector, SmallSize> Pointers; llvm::SmallSet, SmallSize> UniquePointers; public: PointersFinder(llvm::Function &F) : MST(F.getParent()) {} llvm::SmallVector> findPointers(llvm::Function &F) { Pointers.clear(); UniquePointers.clear(); initializeObviousPointers(F); findLikelyPointers(F); return Pointers; } private: bool insertUniquePointer(LocalValue<> V) { bool New = UniquePointers.insert(V).second; if (New) Pointers.push_back(V); return New; } bool insertUniquePointer(llvm::Value *V) { return insertUniquePointer(LocalValue<>(V)); } bool insertUniquePointer(llvm::Use *U) { return insertUniquePointer(LocalValue<>(U)); } bool insertUniquePointer(llvm::Use &U) { return insertUniquePointer(&U); } [[nodiscard]] bool alreadyFoundPointer(LocalValue<> V) const { return UniquePointers.contains(V); } [[nodiscard]] bool alreadyFoundPointer(llvm::Value *V) const { return alreadyFoundPointer(LocalValue<>(V)); } [[nodiscard]] bool alreadyFoundPointer(llvm::Use *U) const { return alreadyFoundPointer(LocalValue<>(U)); } [[nodiscard]] bool alreadyFoundPointer(llvm::Use &U) const { return alreadyFoundPointer(&U); } void initializeObviousPointers(llvm::Function &F) { revng_log(FindLog, "initializeObviousPointers: " << F.getName()); LoggerIndent Indent{ FindLog }; for (llvm::Argument &A : F.args()) { if (isPointer(A)) { revng_log(FindLog, "Pointer Argument: " << dumpToString(A, MST)); insertUniquePointer(&A); } } for (llvm::Instruction &I : llvm::instructions(F)) { // Let's enqueue pointer operands first, because we want them to be // processed earlier for replacement, since in some cases the replacement // could involve directly also the user, if the user hasn't already be // replaced when we replace the operand. for (llvm::Use &U : I.operands()) { if (isPointerUse(U)) { revng_log(FindLog, "Operand used as pointer: " << dumpToString(*U, MST) << " in instruction: " << dumpToString(I, MST)); insertUniquePointer(&U); } } if (isPointer(I)) { revng_log(FindLog, "Instruction is pointer: " << dumpToString(I, MST)); insertUniquePointer(&I); } } } bool hasAmbiguousPointerOperands(llvm::User *I) const { return cast(I)->getOpcode() == llvm::Instruction::Add; } RecursiveCoroutine cannotBePointer(llvm::Use &U) const { // Constants can't be pointers if (isa(U.get())) rc_return true; // Globals that are not constants can be pointers and they actually are. if (isGlobal(U.get())) rc_return false; // Arguments can be pointers. if (auto *A = dyn_cast(U.get())) rc_return false; // Other things that are not instructions cannot be pointers. auto *I = dyn_cast(U.get()); if (not I) rc_return true; // For instructions, the default is that they can be pointers. Only for a // selected list we can rule at at the beginning that they are not pointers. switch (I->getOpcode()) { case llvm::Instruction::BitCast: case llvm::Instruction::IntToPtr: case llvm::Instruction::PtrToInt: case llvm::Instruction::Freeze: rc_return rc_recur cannotBePointer(I->getOperandUse(0)); // All incoming values can be pointers, but not incoming blocks. case llvm::Instruction::PHI: // If any of the incoming operands cannot be a pointer we consider it // evidence strong enough to say that this cannot be an operand for (llvm::Use &Op : cast(I)->incoming_values()) if (rc_recur cannotBePointer(Op)) rc_return true; rc_return false; case llvm::Instruction::Select: if (U.getOperandNo() == 0) rc_return false; // If any of the other operands cannot be a pointer we consider it // evidence strong enough to say that this cannot be an operand for (llvm::Use &Incoming : llvm::drop_begin(I->operands())) if (rc_recur cannotBePointer(Incoming)) rc_return true; rc_return false; // Assuming the result is a pointer, every operand that is not the first // cannot be a pointer itself. case llvm::Instruction::GetElementPtr: case llvm::Instruction::Sub: rc_return U.getOperandNo() != 0; // An Add is the only ambiguous instruction we recurse through here: // when both of its operands cannot be pointers, the result of the Add // cannot be a pointer either. // This lets disambiguation in canDisambiguatePointerOperand see through // a sub-tree of nested Adds whose leaves are all clearly-non-pointer // values. case llvm::Instruction::Add: rc_return rc_recur cannotBePointer(I->getOperandUse(0)) and rc_recur cannotBePointer(I->getOperandUse(1)); // Floats cannot be pointers. case llvm::Instruction::FCmp: case llvm::Instruction::FAdd: case llvm::Instruction::FSub: case llvm::Instruction::FDiv: case llvm::Instruction::FMul: rc_return true; // Operands of multiplications and divisions cannot be pointers. // TODO: In principle the first operand of a division or reminder could be a // pointer, if the code is trying to reason about alignment, but for now we // don't consider it. case llvm::Instruction::Mul: case llvm::Instruction::UDiv: case llvm::Instruction::URem: case llvm::Instruction::SDiv: case llvm::Instruction::SRem: rc_return true; // Operands of bitwise operations cannot be pointers. // TODO: In principle the first operand of shifts could be a pointer, but we // don't consider that for now. // TODO: In principle also operands of masks could be pointers, but either // operand could be a pointer, so they would have to be treated as // ambiguous, which we don't for now. case llvm::Instruction::AShr: case llvm::Instruction::LShr: case llvm::Instruction::Shl: case llvm::Instruction::And: case llvm::Instruction::Or: case llvm::Instruction::Xor: rc_return true; // The fact that an ICmp is transitively used as a pointer is no strong sign // that any of the operand can be a pointer. On the contrary, I can only // think of pathologic code that would use the result of a ICmp in a way // that looks like a pointer. case llvm::Instruction::ICmp: rc_return true; } rc_return false; } // Returns true if U is a pointer operand of its user, who has ambiguous // pointer operands. bool canDisambiguatePointerOperand(llvm::Use &U) const { revng_log(FindLog, "canDisambiguatePointerOperand"); LoggerIndent Indent{ FindLog }; revng_assert(not alreadyFoundPointer(U)); auto *User = U.getUser(); revng_assert(hasAmbiguousPointerOperands(User)); // If it's already clear that U cannot be a pointer, just bail out. if (cannotBePointer(U)) return false; // Otherwise look at all the other operands. for (llvm::Use &Operand : User->operands()) { if (&Operand == &U) continue; // If we find even one for which we aren't sure that it's definitely not a // pointer we return false because if it was a pointer then User would // have 2 pointer operands, which means that we failed disambiguating. if (not cannotBePointer(Operand)) return false; } // If we reach this point we haven't ruled out the fact that U can be a // pointer, and we have ruled out that all the other operands can be // pointers. So the pointer must definitely be U, which means we've // successfully disambiguated it. return true; } bool isPointerOperandIfUserIsPointer(llvm::Use &U) const { auto *User = U.getUser(); // For some users, like AddInst, we cannot say for sure if any of the // operands is a pointer, even if we have strong evidence that the value of // the user is a pointer, because only one of the operands is a pointer and // we have no way of knowing which one without inspecting the operands. if (hasAmbiguousPointerOperands(User)) return false; auto *UI = cast(User); switch (UI->getOpcode()) { case llvm::Instruction::BitCast: case llvm::Instruction::IntToPtr: case llvm::Instruction::PtrToInt: case llvm::Instruction::Freeze: return true; // All incoming values can be pointers, but not incoming blocks. case llvm::Instruction::PHI: return not isa(U); // All operands can be pointers except for the condition. case llvm::Instruction::Select: return U.getOperandNo() != 0; // Only the first argument can be a pointer. case llvm::Instruction::GetElementPtr: case llvm::Instruction::Sub: return U.getOperandNo() == 0; default: return false; } return false; } RecursiveCoroutine propagatePointersBackwards(llvm::Use &U) { revng_log(FindLog, "propagatePointersBackwards: on operand " << U.getOperandNo() << " of: " << dumpToString(*U.getUser(), MST)); LoggerIndent Indent{ FindLog }; if (alreadyFoundPointer(U)) { revng_log(FindLog, "alreadyFoundPointer"); rc_return true; } bool IsPointer = isPointerOperandIfUserIsPointer(U); revng_log(FindLog, "isPointerOperandIfUserIsPointer: " << IsPointer); auto *User = U.getUser(); auto *UI = cast(User); if (not IsPointer and hasAmbiguousPointerOperands(UI)) { revng_log(FindLog, "hasAmbiguousPointerOperands"); if (canDisambiguatePointerOperand(U)) { IsPointer = true; } } revng_log(FindLog, "IsPointer: " << IsPointer); if (IsPointer) { if (auto *I = dyn_cast(U.get())) for (llvm::Use &OperandUse : I->operands()) rc_recur propagatePointersBackwards(OperandUse); // We want to insert U after recurring on its operands because in we want // to insert operands in Pointers before users. Doing this induces an // order in Pointers, which leads to the same order in the replacements. bool New = insertUniquePointer(U); revng_assert(New); } rc_return IsPointer; } void findLikelyPointers(llvm::Function &F) { revng_log(FindLog, "findLikelyPointers: " << F.getName()); LoggerIndent Indent{ FindLog }; // From pointer uses, we want to walk backwards to find potential LLVM // values whose LLVM type is not a pointer, who aren't pointers on the // model, but that are likely to be pointers nevertheless. // The idea is that we want to rewrite also integer arithmetic starting from // such likely pointers as GEPs. // We'll be discovering new Pointers while we iterate on this, but we only // want to start new searches from the Pointers that were present // originally. So let's just count how many there are and iterate only on // those. auto NumPointers = Pointers.size(); for (decltype(NumPointers) K = 0; K < NumPointers; ++K) { LocalValue<> &Pointer = Pointers[K]; // Skip globals because they have no local operands. llvm::Value *V = Pointer.value(); if (isGlobal(V)) continue; revng_assert(isLocal(V)); if (auto *I = dyn_cast(V)) { for (llvm::Use &U : I->operands()) { propagatePointersBackwards(U); } } } auto NewNumPointers = Pointers.size(); if (NewNumPointers != NumPointers) { revng_log(FindLog, "found " << (NewNumPointers - NumPointers) << " likely pointers"); LoggerIndent MoreIndent{ FindLog }; for (decltype(NewNumPointers) I = NumPointers; I < NewNumPointers; ++I) { LocalValue<> &Pointer = Pointers[I]; revng_log(FindLog, "likely pointer: " << dumpToString(*Pointer.value(), MST)); } } } }; static bool foldPointerCasts(llvm::Function &F) { llvm::SmallVector Dead; for (llvm::Instruction &I : llvm::instructions(F)) { if (auto *PtrToInt = dyn_cast(&I)) { for (auto &U : PtrToInt->uses()) { if (auto *IntToPtr = dyn_cast(U.getUser())) { llvm::Value *Pointer = PtrToInt->getOperand(0); if (Pointer->getType() != IntToPtr->getType()) continue; IntToPtr->replaceAllUsesWith(Pointer); Dead.push_back(IntToPtr); } } } else if (auto *IntToPtr = dyn_cast(&I)) { for (auto &U : IntToPtr->uses()) { if (auto *PtrToInt = dyn_cast(U.getUser())) { llvm::Value *Integer = IntToPtr->getOperand(0); if (Integer->getType() != PtrToInt->getType()) continue; PtrToInt->replaceAllUsesWith(Integer); Dead.push_back(PtrToInt); } } } } RecursivelyDeleteTriviallyDeadInstructionsPermissive(Dead); return not Dead.empty(); } class GEPRewriter { private: llvm::Function &F; revng::NonDebugInfoCheckingIRBuilder B; // Collect all the llvm::Values that had at least one of their uses changed // across all calls to replace(). // This is useful to call DCE in the destructor, given that any of those can // now be dead code. using WTVH = llvm::WeakTrackingVH; llvm::SetVector, llvm::SmallSet> HadUsesReplaced; public: GEPRewriter(llvm::Function &F) : F{ F }, B{ F.getContext() } {} ~GEPRewriter() { llvm::SmallVector R = HadUsesReplaced.takeVector(); RecursivelyDeleteTriviallyDeadInstructionsPermissive(R); foldPointerCasts(F); } public: bool replace(LocalValue<> LV) { bool Changed = false; for (llvm::Use *U : LV.uses()) Changed |= rc_eval(replaceImpl(U, LV.value())); return Changed; } private: void setInsertPointBeforeUserInstruction(llvm::Use *U) { auto *UserInstruction = cast(U->getUser()); revng_assert(not isa(UserInstruction)); if (auto *PHI = dyn_cast(UserInstruction)) B.SetInsertPoint(PHI->getIncomingBlock(*U)->getTerminator()); else B.SetInsertPoint(UserInstruction); } void setInsertPointAfter(llvm::Instruction *I) { if (I->isTerminator()) { B.SetInsertPoint(I->getParent()); return; } auto *NextInstr = &*std::next(I->getIterator()); if (isa(NextInstr)) B.SetInsertPoint(NextInstr->getParent()->getFirstNonPHI()); else if (isa(NextInstr)) B.SetInsertPointPastAllocas(NextInstr->getParent()->getParent()); else B.SetInsertPoint(NextInstr); } void setInsertPointAfter(LocalValue<> LV) { llvm::Value *V = LV.value(); if (isGlobal(V)) { B.SetInsertPoint(cast(LV.uses().front()->getUser())); } else if (auto *A = dyn_cast(V)) { B.SetInsertPointPastAllocas(A->getParent()); } else { setInsertPointAfter(cast(V)); } } llvm::Instruction *replaceAddWithGEP(llvm::Use *PointerOperandInAdd, llvm::Value *BasePointer) { auto *Add = cast(PointerOperandInAdd->getUser()); revng_assert(Add->getOpcode() == llvm::Instruction::Add); B.SetInsertPoint(Add); unsigned PointerOpIndex = PointerOperandInAdd->getOperandNo(); unsigned OffsetOpIndex = PointerOpIndex == 0 ? 1 : 0; auto *Offset = Add->getOperand(OffsetOpIndex); auto *Pointer = PointerOperandInAdd->get(); if (Pointer != BasePointer) { // We have traversed a bunch of casts, so that Pointer is obtained from // BasePointer via just casts. // What we want to do here is to make use BasePointer as pointer operand // of the GEP we're going to create. // It might be necessary to add a IntToPtr cast first. Pointer = BasePointer; } auto *PointerType = llvm::PointerType::get(B.getContext(), 0); auto *IntToPtr = B.CreateIntToPtr(Pointer, PointerType); auto *Int8 = llvm::IntegerType::getInt8Ty(B.getContext()); auto *GEP = B.CreateGEP(Int8, IntToPtr, Offset); auto *AddType = Add->getType(); auto *GEPToInt = cast(B.CreatePtrToInt(GEP, AddType)); Add->replaceAllUsesWith(GEPToInt); HadUsesReplaced.insert(Add); return GEPToInt; } llvm::SmallVector snapshotUses(llvm::Instruction *V) const { llvm::SmallVector Uses; const auto ToPointer = [](llvm::Use &U) { return &U; }; llvm::transform(V->uses(), std::back_inserter(Uses), ToPointer); return Uses; } RecursiveCoroutine replaceImpl(llvm::Use *U, llvm::Value *BasePointer) { revng_assert(BasePointer->getType()->isIntOrPtrTy()); revng_assert(U->get()->getType()->isIntOrPtrTy()); bool Changed = false; auto *UserInstruction = cast(U->getUser()); switch (auto Opcode = UserInstruction->getOpcode(); Opcode) { case llvm::Instruction::Add: { // TODO: should we bail out in case of add with negative constant? auto *GEPCastedToInt = replaceAddWithGEP(U, BasePointer); Changed = true; for (llvm::Use *IntUse : snapshotUses(GEPCastedToInt)) Changed |= rc_recur replaceImpl(IntUse, GEPCastedToInt); } break; case llvm::Instruction::BitCast: case llvm::Instruction::IntToPtr: case llvm::Instruction::PtrToInt: case llvm::Instruction::Freeze: { for (llvm::Use *CastUse : snapshotUses(UserInstruction)) Changed |= rc_recur replaceImpl(CastUse, BasePointer); } break; case llvm::Instruction::GetElementPtr: { // Don't create any GEP in this case, since one is already there. // Just recur on all of the GEP's uses if this is the address operand. Or // fall back on the default if this is one of the indices. auto *GEP = cast(UserInstruction); unsigned PointerOpNo = llvm::GetElementPtrInst::getPointerOperandIndex(); if (U->getOperandNo() == PointerOpNo) { for (llvm::Use *GEPUse : snapshotUses(GEP)) Changed |= rc_recur replaceImpl(GEPUse, GEP); break; // break from switch. all operands do fall through to default. } } [[fallthrough]]; default: { // We've reached the end of the linear path that can be rewritten as a // GEP. Just cast back the value to the proper integer or pointer type if // necessary. if (U->get() != BasePointer) { auto *UseType = U->get()->getType(); auto *BasePointerType = BasePointer->getType(); if (BasePointerType != UseType) { setInsertPointBeforeUserInstruction(U); if (BasePointerType->isPointerTy()) { BasePointer = B.CreatePtrToInt(BasePointer, UseType); } else { BasePointer = B.CreateIntToPtr(BasePointer, UseType); } } auto *OldOperand = U->get(); U->set(BasePointer); HadUsesReplaced.insert(OldOperand); Changed = true; } } } rc_return Changed; } }; static void crashOnPHINode(const llvm::Function &F) { for (const llvm::Instruction &I : llvm::instructions(F)) { if (isa(I)) { std::string Message = "Unexpected PHINode in Function: "; Message += F.getName().str(); revng_abort(Message.c_str()); } } } bool ArithmeticToGEPPass::runOnFunction(llvm::Function &F) { crashOnPHINode(F); PointersFinder Finder(F); llvm::SmallVector> Pointers = Finder.findPointers(F); if (Pointers.empty()) return false; { GEPRewriter Rewriter(F); std::set> Replaced; for (const LocalValue<> &PointerValue : Pointers) { // This can happen because a likely pointer may also be an obvious // pointer. bool New = Replaced.insert(PointerValue).second; if (not New) continue; Rewriter.replace(PointerValue); } } return true; } char ArithmeticToGEPPass::ID = 0; static constexpr const char *Description = "Arithmetic-to-i8-GEP replacement"; static llvm::RegisterPass X{ ArithmeticToGEPFlag, Description, false, false };