diff --git a/lib/Canonicalize/ArithmeticToGEP.cpp b/lib/Canonicalize/ArithmeticToGEP.cpp new file mode 100644 index 000000000..6064b3d5b --- /dev/null +++ b/lib/Canonicalize/ArithmeticToGEP.cpp @@ -0,0 +1,1154 @@ +// +// 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; + + // 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)); + auto *I = dyn_cast(V); + if (I) { + 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) { + using WTVH = llvm::WeakTrackingVH; + 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 }; diff --git a/lib/Canonicalize/CMakeLists.txt b/lib/Canonicalize/CMakeLists.txt index d0c7c6a5e..a2c368d79 100644 --- a/lib/Canonicalize/CMakeLists.txt +++ b/lib/Canonicalize/CMakeLists.txt @@ -4,6 +4,7 @@ revng_add_analyses_library_internal( revngCanonicalize + ArithmeticToGEP.cpp DiscardBrokenDebugInformation.cpp EmbedStatementComments.cpp ExitSSAPass.cpp diff --git a/python/revng/internal/pipeline.yml b/python/revng/internal/pipeline.yml index e8c589600..777689721 100644 --- a/python/revng/internal/pipeline.yml +++ b/python/revng/internal/pipeline.yml @@ -353,6 +353,7 @@ branches: - peephole-opt-for-decompilation - ternary-reduction - exit-ssa + - arithmetic-to-gep - pipe: switch-to-statements arguments: [llvm-functions] - pipe: pure-llvm-passes-pipe diff --git a/share/revng/pipelines/revng-pipelines.yml b/share/revng/pipelines/revng-pipelines.yml index 8c9fbf154..33e98ca8f 100644 --- a/share/revng/pipelines/revng-pipelines.yml +++ b/share/revng/pipelines/revng-pipelines.yml @@ -681,6 +681,7 @@ Branches: - peephole-opt-for-decompilation - ternary-reduction - exit-ssa + - arithmetic-to-gep - switch-to-statements - remove-constant-array-returns - dagify diff --git a/tests/unit/llvm_lit_tests/ArithmeticToGEP.ll b/tests/unit/llvm_lit_tests/ArithmeticToGEP.ll new file mode 100644 index 000000000..1126bc013 --- /dev/null +++ b/tests/unit/llvm_lit_tests/ArithmeticToGEP.ll @@ -0,0 +1,352 @@ +; +; This file is distributed under the MIT License. See LICENSE.md for details. +; + +; RUN: %revngopt %s -arithmetic-to-gep -S -o - | FileCheck %s + +; ============================================================================= +; ============================================================================= +; Simple tests to check that various kinds of integer arithmetic are converted +; to GEPs, without the !revng.pointers metadata. +; ============================================================================= +; ============================================================================= + +; ---------------------------------------------------- +; Functions with pointer argument, integer return type +; ---------------------------------------------------- + +; Pointer argument, cast to integer, then incremented and returned as integer +; +; CHECK-LABEL: define i64 @a +; CHECK: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr %arg, i64 1 +; CHECK-NEXT: [[CAST:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[CAST]] +define i64 @a (ptr %arg) { + %intptr = ptrtoint ptr %arg to i64 + %with_offset = add i64 %intptr, 1 + ret i64 %with_offset +} + +; Same as above, but with more casts back and forth between integer and pointer +; +; CHECK-LABEL: define i64 @b +; CHECK: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr %arg, i64 2 +; CHECK-NEXT: [[CAST:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[CAST]] +define i64 @b (ptr %arg) { + %intptr = ptrtoint ptr %arg to i64 + %a = inttoptr i64 %intptr to ptr + %b = ptrtoint ptr %a to i64 + %with_offset = add i64 %b, 2 + %c = inttoptr i64 %with_offset to ptr + %d = ptrtoint ptr %c to i64 + ret i64 %d +} + +; Same as @a, but check that the transformation works even with unknown offset +; +; CHECK-LABEL: define i64 @unknown_offset +; CHECK: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr %arg, i64 %offset +; CHECK-NEXT: [[CAST:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[CAST]] +define i64 @unknown_offset (ptr %arg, i64 %offset) { + %intptr = ptrtoint ptr %arg to i64 + %with_offset = add i64 %intptr, %offset + ret i64 %with_offset +} + +; ---------------------------------------------------- +; Functions with integer argument, pointer return type +; ---------------------------------------------------- + +; Pointer argument, cast to integer, then incremented and returned as integer +; +; CHECK-LABEL: define ptr @aa +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 1 +; CHECK-NEXT: ret ptr [[GEP]] +define ptr @aa (i64 %arg) { + %with_offset = add i64 %arg, 1 + %ptr = inttoptr i64 %with_offset to ptr + ret ptr %ptr +} + +; Same as above, but with more casts back and forth between integer and pointer +; +; CHECK-LABEL: define ptr @bb +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 2 +; CHECK-NEXT: ret ptr [[GEP]] +define ptr @bb (i64 %arg) { + %a = inttoptr i64 %arg to ptr + %b = ptrtoint ptr %a to i64 + %with_offset = add i64 %b, 2 + %c = inttoptr i64 %with_offset to ptr + %d = ptrtoint ptr %c to i64 + %result = inttoptr i64 %d to ptr + ret ptr %result +} + +; -------------------------------------------------------------------------- +; Functions that call other functions that either have pointer arguments, or +; return pointers. +; -------------------------------------------------------------------------- + +; CHECK-LABEL: define i64 @aaa +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = call ptr @aa(i64 %arg) +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 13 +; CHECK-NEXT: [[PTRTOINT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[PTRTOINT]] +define i64 @aaa(i64 %arg) { + %ptr = call ptr @aa(i64 %arg) + %intptr = ptrtoint ptr %ptr to i64 + %with_offset = add i64 %intptr, 13 + ret i64 %with_offset +} + +; CHECK-LABEL: define i64 @bbb +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 17 +; CHECK-NEXT: [[CALL:%[a-zA-Z0-9_]+]] = call i64 @b(ptr [[GEP]]) +; CHECK-NEXT: ret i64 [[CALL]] +define i64 @bbb(i64 %arg) { + %with_offset = add i64 %arg, 17 + %ptr = inttoptr i64 %with_offset to ptr + %result = call i64 @b(ptr %ptr) + ret i64 %result +} + +; ============================================================================= +; ============================================================================= +; Tests to check the transformation can handle different kinds of arithmetic, +; partly expressed as GEPs that are already present. +; ============================================================================= +; ============================================================================= + +; Pointer argument, incremented via GEP already present. +; Then cast to integer and more arithmetic done via integer. +; Then casted back to pointer and returned. +; +; CHECK-LABEL: define ptr @c +; CHECK: [[ORIGINAL_GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr %arg, i64 5 +; CHECK-NEXT: [[NEW_GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[ORIGINAL_GEP]], i64 6 +; CHECK-NEXT: ret ptr [[NEW_GEP]] +define ptr @c (ptr %arg) { + %gep = getelementptr i8, ptr %arg, i64 5 + %intptr = ptrtoint ptr %gep to i64 + %with_offset = add i64 %intptr, 6 + %ptr_result = inttoptr i64 %with_offset to ptr + ret ptr %ptr_result +} + +; Pointer argument, cast to integer. +; Two consecutive adds turned into 2 GEPs. +; Then casted back to pointer and returned. +; +; CHECK-LABEL: define ptr @d +; CHECK: [[GEP1:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr %arg, i64 7 +; CHECK-NEXT: [[GEP2:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[GEP1]], i64 8 +; CHECK-NEXT: ret ptr [[GEP2]] +define ptr @d (ptr %arg) { + %intptr = ptrtoint ptr %arg to i64 + %plus7 = add i64 %intptr, 7 + %plus8 = add i64 %plus7, 8 + %ptr_result = inttoptr i64 %plus8 to ptr + ret ptr %ptr_result +} + +; ============================================================================= +; ============================================================================= +; Tests to check that the !revng.pointers metadata is used properly in various +; places. +; These tests basically mimic all the above, but instead of using ptr types, we +; we use integer types everywhere and then just use the !revng.pointers metadata +; to mark as pointers. +; ============================================================================= +; ============================================================================= + +; ---------------------------------------------------- +; Functions with pointer argument, integer return type +; ---------------------------------------------------- + +; Pointer argument, cast to integer, then incremented and returned as integer +; +; CHECK-LABEL: define i64 @xa +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 1 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @xa (i64 %arg) !revng.pointers !1001 { + %with_offset = add i64 %arg, 1 + ret i64 %with_offset +} + +; Same as above, but with more casts back and forth between integer and pointer +; +; CHECK-LABEL: define i64 @xb +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 2 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @xb (i64 %arg) !revng.pointers !1001 { + %a = inttoptr i64 %arg to ptr + %b = ptrtoint ptr %a to i64 + %with_offset = add i64 %b, 2 + %c = inttoptr i64 %with_offset to ptr + %d = ptrtoint ptr %c to i64 + ret i64 %d +} + +; ---------------------------------------------------- +; Functions with integer argument, pointer return type +; ---------------------------------------------------- + +; Pointer argument, cast to integer, then incremented and returned as integer +; +; CHECK-LABEL: define i64 @xaa +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 1 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @xaa (i64 %arg) !revng.pointers !1002 { + %with_offset = add i64 %arg, 1 + ret i64 %with_offset +} + +; Same as above, but with more casts back and forth between integer and pointer +; +; CHECK-LABEL: define i64 @xbb +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 2 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @xbb (i64 %arg) !revng.pointers !1002 { + %a = inttoptr i64 %arg to ptr + %b = ptrtoint ptr %a to i64 + %with_offset = add i64 %b, 2 + %c = inttoptr i64 %with_offset to ptr + %d = ptrtoint ptr %c to i64 + ret i64 %d +} + +; -------------------------------------------------------------------------- +; Functions that call other functions that either have pointer arguments, or +; return pointers. +; -------------------------------------------------------------------------- + +; CHECK-LABEL: define i64 @xaaa +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = call i64 @xaa(i64 %arg) +; CHECK-NEXT: [[INTTOPTR:%[a-zA-Z0-9_]+]] = inttoptr i64 [[PTR]] to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[INTTOPTR]], i64 13 +; CHECK-NEXT: [[PTRTOINT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[PTRTOINT]] +define i64 @xaaa(i64 %arg) !revng.pointers !1000 { + %ptr = call i64 @xaa(i64 %arg) + %with_offset = add i64 %ptr, 13 + ret i64 %with_offset +} + +; CHECK-LABEL: define i64 @xbbb +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 17 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: [[CALL:%[a-zA-Z0-9_]+]] = call i64 @xb(i64 [[INT]]) +; CHECK-NEXT: ret i64 [[CALL]] +define i64 @xbbb(i64 %arg) !revng.pointers !1000 { + %with_offset = add i64 %arg, 17 + %result = call i64 @xb(i64 %with_offset) + ret i64 %result +} + +; ------------------------------------------------------------------------------ +; Functions that call other functions indirectly, with call sites decorated with +; !revng.pointers metadata +; ------------------------------------------------------------------------------ + +; CHECK-LABEL: define i64 @indirect_a +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 31 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: [[CALL:%[a-zA-Z0-9_]+]] = call i64 undef(i64 [[INT]]) +; CHECK-NEXT: ret i64 [[CALL]] +define i64 @indirect_a(i64 %arg) !revng.pointers !1000 { + %with_offset = add i64 %arg, 31 + %result = call i64 undef(i64 %with_offset), !revng.pointers !1001 + ret i64 %result +} + +; CHECK-LABEL: define i64 @indirect_b +; CHECK: [[CALL:%[a-zA-Z0-9_]+]] = call i64 undef(i64 %arg) +; CHECK-NEXT: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 [[CALL]] to ptr +; CHECK-NEXT: [[GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 39 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @indirect_b(i64 %arg) !revng.pointers !1000 { + %result = call i64 undef(i64 %arg), !revng.pointers !1002 + %with_offset = add i64 %result, 39 + ret i64 %with_offset +} + +; ============================================================================= +; ============================================================================= +; Tests to check the transformation can handle different kinds of arithmetic, +; partly expressed as GEPs that are already present. +; ============================================================================= +; ============================================================================= + +; Pointer argument, incremented via GEP already present. +; Then cast to integer and more arithmetic done via integer. +; Then casted back to pointer and returned. +; +; CHECK-LABEL: define i64 @xc +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[ORIGINAL_GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 5 +; CHECK-NEXT: [[NEW_GEP:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[ORIGINAL_GEP]], i64 6 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[NEW_GEP]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @xc (i64 %arg) !revng.pointers !1003 { + %ptr = inttoptr i64 %arg to ptr + %gep = getelementptr i8, ptr %ptr, i64 5 + %intptr = ptrtoint ptr %gep to i64 + %with_offset = add i64 %intptr, 6 + ret i64 %with_offset +} + +; Pointer argument, cast to integer. +; Two consecutive adds turned into 2 GEPs. +; Then casted back to pointer and returned. +; +; CHECK-LABEL: define i64 @xd +; CHECK: [[PTR:%[a-zA-Z0-9_]+]] = inttoptr i64 %arg to ptr +; CHECK-NEXT: [[GEP1:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[PTR]], i64 7 +; CHECK-NEXT: [[GEP2:%[a-zA-Z0-9_]+]] = getelementptr i8, ptr [[GEP1]], i64 8 +; CHECK-NEXT: [[INT:%[a-zA-Z0-9_]+]] = ptrtoint ptr [[GEP2]] to i64 +; CHECK-NEXT: ret i64 [[INT]] +define i64 @xd (i64 %arg) !revng.pointers !1003 { + %plus7 = add i64 %arg, 7 + %plus8 = add i64 %plus7, 8 + ret i64 %plus8 +} + +; ---------------------------------------------------------------------- +; Check that if !revng.pointers is missing we never promote adds to GEPs +; ---------------------------------------------------------------------- + +; CHECK-LABEL: define i64 @dont_optimize +; CHECK: [[ADD:%[a-zA-Z0-9_]+]] = add i64 %arg, 47 +; CHECK-NEXT: ret i64 [[ADD]] +define i64 @dont_optimize(i64 %arg) { + %result = add i64 %arg, 47 + ret i64 %result +} + +!0 = !{ i1 false } +!1 = !{ i1 true } +; non-pointer return return type, non-pointer operand type +!1000 = !{ !0, !0 } +; non-pointer return return type, pointer operand type +!1001 = !{ !0, !1 } +; pointer return return type, non-pointer operand type +!1002 = !{ !1, !0 } +; pointer return return type, pointer operand type +!1003 = !{ !1, !1 }