Files
revng-revng/lib/Canonicalize/ArithmeticToGEP.cpp
T
Alessandro Di Federico b5e3ccba9e Do not allocate const objects
2026-06-18 16:44:15 +02:00

1165 lines
38 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <compare>
#include <iterator>
#include <type_traits>
#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<llvm::ExtractValueInst>(V)
or isCallToTagged(&V, FunctionTags::OpaqueExtractValue);
}
static unsigned getExtractValueNumIndices(const llvm::Value &V) {
revng_assert(isExtractValue(V));
if (const auto *E = dyn_cast<llvm::ExtractValueInst>(&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<llvm::ExtractValueInst>(&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<llvm::ExtractValueInst>(&V))
return E->getIndices()[0];
const llvm::CallInst *C = getCallToTagged(&V,
FunctionTags::OpaqueExtractValue);
const auto *Index = llvm::cast<llvm::ConstantInt>(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<llvm::SmallVector<bool>>
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<llvm::SmallVector<bool>>
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<llvm::SmallVector<bool>>
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<llvm::SmallVector<bool>>
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<llvm::SmallVector<bool>>
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<llvm::SmallVector<bool>>
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<llvm::CallInst>(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<llvm::Argument>(&V))
return isArgumentModelPointer(A);
if (const auto *Call = dyn_cast<llvm::CallInst>(&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<llvm::Instruction>(TheUser))
return false;
const llvm::Function &F = *cast<llvm::Instruction>(TheUser)->getFunction();
if (const auto *Ret = dyn_cast<llvm::ReturnInst>(TheUser))
return returnsModelPointer(&F);
const auto *C = dyn_cast<llvm::CallInst>(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<llvm::Instruction>(V) or isa<llvm::Argument>(V);
}
[[maybe_unused]] static bool isLocal(const llvm::Use *U) {
return isLocal(U->get());
}
static bool isGlobal(const llvm::Value *V) {
return isa<llvm::Function>(V) or isa<llvm::GlobalVariable>(V)
or isa<llvm::Constant>(V);
}
static bool isGlobal(const llvm::Use *U) {
return isGlobal(U->get());
}
//
// Helper concepts for constraining LocalValue constructors, assignments
//
template<class Derived>
concept DerivedFromInstruction = std::derived_from<Derived, llvm::Instruction>;
template<class Derived>
concept DerivedFromArgument = std::derived_from<Derived, llvm::Argument>;
template<class Derived>
concept DerivedFromLocal = DerivedFromInstruction<Derived>
or DerivedFromArgument<Derived>;
template<class Derived>
concept ConstDerivedFromLocal = DerivedFromLocal<Derived>
and std::is_const_v<Derived>;
template<class Derived>
concept MutableDerivedFromLocal = DerivedFromInstruction<Derived>
and not std::is_const_v<Derived>;
// 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<bool IsConst = false>
class LocalValue {
public:
using ValueType = std::conditional_t<IsConst, const llvm::Value, llvm::Value>;
using UseType = std::conditional_t<IsConst, const llvm::Use, llvm::Use>;
using UserType = std::conditional_t<IsConst, const llvm::User, llvm::User>;
using UseIterator = std::conditional_t<IsConst,
llvm::Value::const_use_iterator,
llvm::Value::user_iterator>;
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<MutableDerivedFromLocal LocalValueType>
LocalValue &operator=(LocalValueType *V) {
return *this = LocalValue<IsConst>(V);
}
// Enable assigning a const Value to a LocalValue, only if IsConst.
template<ConstDerivedFromLocal ConstLocalValueType>
LocalValue &operator=(ConstLocalValueType *V)
requires IsConst
{
return *this = LocalValue<IsConst>(V);
}
// Always enable assigning a Use to a LocalValue.
LocalValue &operator=(llvm::Use *U) { return *this = LocalValue<IsConst>(U); }
// Enable assigning a const Use to a LocalValue, only if IsConst.
LocalValue &operator=(const llvm::Use *U)
requires IsConst
{
return *this = LocalValue<IsConst>(U);
}
// Needed by conversion operator
template<bool Const>
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</* IsConst */ true>()
requires(not IsConst)
{
LocalValue</*IsConst*/ true> 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<UseType *> uses() const {
llvm::SmallVector<UseType *> 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<LocalValue<IsConst>, 4> users() const {
llvm::SmallVector<LocalValue<IsConst>, 4> Result;
llvm::transform(this->uses(), std::back_inserter(Result), [](UseType *U) {
return LocalValue<IsConst>(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<class Derived>
concept DerivedFromValue = std::derived_from<Derived, llvm::Value>;
template<class Derived>
concept ConstDerivedFromValue = std::is_const_v<Derived>
and DerivedFromValue<Derived>;
template<class Derived>
concept MutableDerivedFromValue = not std::is_const_v<Derived>
and DerivedFromValue<Derived>;
//
// Value-based deduction guide for LocalValue
//
template<ConstDerivedFromValue ConstValue>
LocalValue(ConstValue *V) -> LocalValue</*IsConst*/ true>;
template<MutableDerivedFromValue MutableValue>
LocalValue(MutableValue *V) -> LocalValue</*IsConst*/ false>;
//
// Helper concepts for Use-based deduction guides for LocalValue
//
template<class Derived>
concept DerivedFromUse = std::derived_from<Derived, llvm::Use>;
template<class Derived>
concept ConstDerivedFromUse = std::is_const_v<Derived>
and DerivedFromUse<Derived>;
template<class Derived>
concept MutableDerivedFromUse = not std::is_const_v<Derived>
and DerivedFromUse<Derived>;
//
// Use-based deduction guide for LocalValue
//
template<ConstDerivedFromUse ConstUse>
LocalValue(ConstUse *U) -> LocalValue</*IsConst*/ true>;
template<MutableDerivedFromUse MutableUse>
LocalValue(MutableUse *U) -> LocalValue</*IsConst*/ false>;
//
// Graph traits for LocalValue
//
template<bool IsConst>
struct llvm::GraphTraits<LocalValue<IsConst>> {
private:
using EagerRange = EagerMaterializationRangeIterator<LocalValue<IsConst>>;
public:
using NodeRef = LocalValue<IsConst>;
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<IsConst>{ nullptr };
}
};
class PointersFinder {
private:
using Help = llvm::CalculateSmallVectorDefaultInlinedElements<LocalValue<>>;
static constexpr unsigned SmallSize = Help::value;
llvm::ModuleSlotTracker MST;
llvm::SmallVector<LocalValue<>, SmallSize> Pointers;
llvm::SmallSet<LocalValue<>, SmallSize> UniquePointers;
public:
PointersFinder(llvm::Function &F) : MST(F.getParent()) {}
llvm::SmallVector<LocalValue<>> 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<llvm::Instruction>(I)->getOpcode() == llvm::Instruction::Add;
}
RecursiveCoroutine<bool> cannotBePointer(llvm::Use &U) const {
// Constants can't be pointers
if (isa<llvm::Constant>(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<llvm::Argument>(U.get()))
rc_return false;
// Other things that are not instructions cannot be pointers.
auto *I = dyn_cast<llvm::Instruction>(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<llvm::PHINode>(I)->incoming_values())
if (rc_recur cannotBePointer(Op))
rc_return true;
rc_return false;
case llvm::Instruction::Select:
if (U.getOperandNo() == 0)
rc_return false;
// If any of the other operands cannot be a pointer we consider it
// evidence strong enough to say that this cannot be an operand
for (llvm::Use &Incoming : llvm::drop_begin(I->operands()))
if (rc_recur cannotBePointer(Incoming))
rc_return true;
rc_return false;
// Assuming the result is a pointer, every operand that is not the first
// cannot be a pointer itself.
case llvm::Instruction::GetElementPtr:
case llvm::Instruction::Sub:
rc_return U.getOperandNo() != 0;
// An Add is the only ambiguous instruction we recurse through here:
// when both of its operands cannot be pointers, the result of the Add
// cannot be a pointer either.
// This lets disambiguation in canDisambiguatePointerOperand see through
// a sub-tree of nested Adds whose leaves are all clearly-non-pointer
// values.
case llvm::Instruction::Add:
rc_return rc_recur cannotBePointer(I->getOperandUse(0))
and rc_recur cannotBePointer(I->getOperandUse(1));
// Floats cannot be pointers.
case llvm::Instruction::FCmp:
case llvm::Instruction::FAdd:
case llvm::Instruction::FSub:
case llvm::Instruction::FDiv:
case llvm::Instruction::FMul:
rc_return true;
// Operands of multiplications and divisions cannot be pointers.
// TODO: In principle the first operand of a division or reminder could be a
// pointer, if the code is trying to reason about alignment, but for now we
// don't consider it.
case llvm::Instruction::Mul:
case llvm::Instruction::UDiv:
case llvm::Instruction::URem:
case llvm::Instruction::SDiv:
case llvm::Instruction::SRem:
rc_return true;
// Operands of bitwise operations cannot be pointers.
// TODO: In principle the first operand of shifts could be a pointer, but we
// don't consider that for now.
// TODO: In principle also operands of masks could be pointers, but either
// operand could be a pointer, so they would have to be treated as
// ambiguous, which we don't for now.
case llvm::Instruction::AShr:
case llvm::Instruction::LShr:
case llvm::Instruction::Shl:
case llvm::Instruction::And:
case llvm::Instruction::Or:
case llvm::Instruction::Xor:
rc_return true;
// The fact that an ICmp is transitively used as a pointer is no strong sign
// that any of the operand can be a pointer. On the contrary, I can only
// think of pathologic code that would use the result of a ICmp in a way
// that looks like a pointer.
case llvm::Instruction::ICmp:
rc_return true;
}
rc_return false;
}
// Returns true if U is a pointer operand of its user, who has ambiguous
// pointer operands.
bool canDisambiguatePointerOperand(llvm::Use &U) const {
revng_log(FindLog, "canDisambiguatePointerOperand");
LoggerIndent Indent{ FindLog };
revng_assert(not alreadyFoundPointer(U));
auto *User = U.getUser();
revng_assert(hasAmbiguousPointerOperands(User));
// If it's already clear that U cannot be a pointer, just bail out.
if (cannotBePointer(U))
return false;
// Otherwise look at all the other operands.
for (llvm::Use &Operand : User->operands()) {
if (&Operand == &U)
continue;
// If we find even one for which we aren't sure that it's definitely not a
// pointer we return false because if it was a pointer then User would
// have 2 pointer operands, which means that we failed disambiguating.
if (not cannotBePointer(Operand))
return false;
}
// If we reach this point we haven't ruled out the fact that U can be a
// pointer, and we have ruled out that all the other operands can be
// pointers. So the pointer must definitely be U, which means we've
// successfully disambiguated it.
return true;
}
bool isPointerOperandIfUserIsPointer(llvm::Use &U) const {
auto *User = U.getUser();
// For some users, like AddInst, we cannot say for sure if any of the
// operands is a pointer, even if we have strong evidence that the value of
// the user is a pointer, because only one of the operands is a pointer and
// we have no way of knowing which one without inspecting the operands.
if (hasAmbiguousPointerOperands(User))
return false;
auto *UI = cast<llvm::Instruction>(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<llvm::BlockAddress>(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<bool> 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<llvm::Instruction>(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<llvm::Instruction>(U.get()))
for (llvm::Use &OperandUse : I->operands())
rc_recur propagatePointersBackwards(OperandUse);
// We want to insert U after recurring on its operands because in we want
// to insert operands in Pointers before users. Doing this induces an
// order in Pointers, which leads to the same order in the replacements.
bool New = insertUniquePointer(U);
revng_assert(New);
}
rc_return IsPointer;
}
void findLikelyPointers(llvm::Function &F) {
revng_log(FindLog, "findLikelyPointers: " << F.getName());
LoggerIndent Indent{ FindLog };
// From pointer uses, we want to walk backwards to find potential LLVM
// values whose LLVM type is not a pointer, who aren't pointers on the
// model, but that are likely to be pointers nevertheless.
// The idea is that we want to rewrite also integer arithmetic starting from
// such likely pointers as GEPs.
// We'll be discovering new Pointers while we iterate on this, but we only
// want to start new searches from the Pointers that were present
// originally. So let's just count how many there are and iterate only on
// those.
auto NumPointers = Pointers.size();
for (decltype(NumPointers) K = 0; K < NumPointers; ++K) {
LocalValue<> &Pointer = Pointers[K];
// Skip globals because they have no local operands.
llvm::Value *V = Pointer.value();
if (isGlobal(V))
continue;
revng_assert(isLocal(V));
if (auto *I = dyn_cast<llvm::Instruction>(V)) {
for (llvm::Use &U : I->operands()) {
propagatePointersBackwards(U);
}
}
}
auto NewNumPointers = Pointers.size();
if (NewNumPointers != NumPointers) {
revng_log(FindLog,
"found " << (NewNumPointers - NumPointers)
<< " likely pointers");
LoggerIndent MoreIndent{ FindLog };
for (decltype(NewNumPointers) I = NumPointers; I < NewNumPointers; ++I) {
LocalValue<> &Pointer = Pointers[I];
revng_log(FindLog,
"likely pointer: " << dumpToString(*Pointer.value(), MST));
}
}
}
};
static bool foldPointerCasts(llvm::Function &F) {
llvm::SmallVector<llvm::WeakTrackingVH, 8> Dead;
for (llvm::Instruction &I : llvm::instructions(F)) {
if (auto *PtrToInt = dyn_cast<llvm::PtrToIntInst>(&I)) {
for (auto &U : PtrToInt->uses()) {
if (auto *IntToPtr = dyn_cast<llvm::IntToPtrInst>(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<llvm::IntToPtrInst>(&I)) {
for (auto &U : IntToPtr->uses()) {
if (auto *PtrToInt = dyn_cast<llvm::PtrToIntInst>(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<WTVH, llvm::SmallVector<WTVH, 8>, llvm::SmallSet<WTVH, 8>>
HadUsesReplaced;
public:
GEPRewriter(llvm::Function &F) : F{ F }, B{ F.getContext() } {}
~GEPRewriter() {
llvm::SmallVector<llvm::WeakTrackingVH> 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<llvm::Instruction>(U->getUser());
revng_assert(not isa<llvm::AllocaInst>(UserInstruction));
if (auto *PHI = dyn_cast<llvm::PHINode>(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<llvm::PHINode>(NextInstr))
B.SetInsertPoint(NextInstr->getParent()->getFirstNonPHI());
else if (isa<llvm::AllocaInst>(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<llvm::Instruction>(LV.uses().front()->getUser()));
} else if (auto *A = dyn_cast<llvm::Argument>(V)) {
B.SetInsertPointPastAllocas(A->getParent());
} else {
setInsertPointAfter(cast<llvm::Instruction>(V));
}
}
llvm::Instruction *replaceAddWithGEP(llvm::Use *PointerOperandInAdd,
llvm::Value *BasePointer) {
auto *Add = cast<llvm::Instruction>(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<llvm::Instruction>(B.CreatePtrToInt(GEP, AddType));
Add->replaceAllUsesWith(GEPToInt);
HadUsesReplaced.insert(Add);
return GEPToInt;
}
llvm::SmallVector<llvm::Use *> snapshotUses(llvm::Instruction *V) const {
llvm::SmallVector<llvm::Use *> Uses;
const auto ToPointer = [](llvm::Use &U) { return &U; };
llvm::transform(V->uses(), std::back_inserter(Uses), ToPointer);
return Uses;
}
RecursiveCoroutine<bool> replaceImpl(llvm::Use *U, llvm::Value *BasePointer) {
revng_assert(BasePointer->getType()->isIntOrPtrTy());
revng_assert(U->get()->getType()->isIntOrPtrTy());
bool Changed = false;
auto *UserInstruction = cast<llvm::Instruction>(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<llvm::GetElementPtrInst>(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<llvm::PHINode>(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<LocalValue<>> Pointers = Finder.findPointers(F);
if (Pointers.empty())
return false;
{
GEPRewriter Rewriter(F);
std::set<LocalValue<>> 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<ArithmeticToGEPPass> X{ ArithmeticToGEPFlag,
Description,
false,
false };