From cb5702dc043e007fff43ef94e9f9fdcb8eebd34f Mon Sep 17 00:00:00 2001 From: Pietro Fezzardi Date: Fri, 8 May 2026 11:37:10 +0200 Subject: [PATCH] Add ArithmeticToGEP pass Introduce the ArithmeticToGEP canonicalisation pass. It detects pointers (LLVM-typed pointer values, model pointers flagged by the "revng.pointers" metadata, and pointer-tagged extract values) and rewrites integer arithmetic computed on top of them as i8 GEPs, followed by a ptrtoint when the original use needs an integer. For each obvious pointer the pass also walks backwards through Add chains and disambiguates the pointer operand of each Add when the other operand cannot be a pointer, so the rewrite extends to integer arithmetic that only later flows into a pointer use. --- lib/Canonicalize/ArithmeticToGEP.cpp | 1154 ++++++++++++++++++ lib/Canonicalize/CMakeLists.txt | 1 + python/revng/internal/pipeline.yml | 1 + share/revng/pipelines/revng-pipelines.yml | 1 + tests/unit/llvm_lit_tests/ArithmeticToGEP.ll | 352 ++++++ 5 files changed, 1509 insertions(+) create mode 100644 lib/Canonicalize/ArithmeticToGEP.cpp create mode 100644 tests/unit/llvm_lit_tests/ArithmeticToGEP.ll 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 }