mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
2292 lines
82 KiB
C++
2292 lines
82 KiB
C++
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <compare>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "llvm/ADT/APInt.h"
|
|
#include "llvm/ADT/MapVector.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/IR/Argument.h"
|
|
#include "llvm/IR/BasicBlock.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/DebugLoc.h"
|
|
#include "llvm/IR/DerivedTypes.h"
|
|
#include "llvm/IR/Function.h"
|
|
#include "llvm/IR/GlobalVariable.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/Verifier.h"
|
|
#include "llvm/Pass.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/GraphWriter.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include "revng/ABI/FunctionType/Layout.h"
|
|
#include "revng/ABI/ModelHelpers.h"
|
|
#include "revng/ADT/RecursiveCoroutine.h"
|
|
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
|
|
#include "revng/InitModelTypes/InitModelTypes.h"
|
|
#include "revng/Model/Architecture.h"
|
|
#include "revng/Model/Binary.h"
|
|
#include "revng/Model/FunctionTags.h"
|
|
#include "revng/Model/IRHelpers.h"
|
|
#include "revng/Model/LoadModelPass.h"
|
|
#include "revng/Model/TypeDefinition.h"
|
|
#include "revng/Model/TypeDefinitionKind.h"
|
|
#include "revng/Model/VerifyHelper.h"
|
|
#include "revng/Support/BlockType.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/IRBuilder.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
#include "revng/Support/YAMLTraits.h"
|
|
|
|
using llvm::AnalysisUsage;
|
|
using llvm::APInt;
|
|
using llvm::cast;
|
|
using llvm::Constant;
|
|
using llvm::ConstantExpr;
|
|
using llvm::ConstantInt;
|
|
using llvm::dyn_cast;
|
|
using llvm::FunctionPass;
|
|
using llvm::Instruction;
|
|
using llvm::isa;
|
|
using llvm::LLVMContext;
|
|
using llvm::PHINode;
|
|
using llvm::RegisterPass;
|
|
using llvm::ReversePostOrderTraversal;
|
|
using llvm::SmallVector;
|
|
using llvm::Use;
|
|
using llvm::User;
|
|
using llvm::Value;
|
|
using model::CABIFunctionDefinition;
|
|
using model::RawFunctionDefinition;
|
|
|
|
static Logger ModelGEPLog{ "make-model-gep" };
|
|
|
|
// This struct represents an llvm::Value for which it has been determined that
|
|
// it has pointer semantic on the model, along with the type of the pointee.
|
|
class ModelTypedIRAddress {
|
|
|
|
private:
|
|
model::UpcastableType PointeeType = {};
|
|
Value *Address = nullptr;
|
|
|
|
public:
|
|
// The default constructor constructs an invalid ModelTypedIRAddress
|
|
ModelTypedIRAddress() = default;
|
|
|
|
ModelTypedIRAddress(const ModelTypedIRAddress &) = default;
|
|
ModelTypedIRAddress &operator=(const ModelTypedIRAddress &) = default;
|
|
|
|
ModelTypedIRAddress(ModelTypedIRAddress &) = default;
|
|
ModelTypedIRAddress &operator=(ModelTypedIRAddress &) = default;
|
|
|
|
public:
|
|
ModelTypedIRAddress(model::UpcastableType &&Pointee, Value *A) :
|
|
PointeeType(std::move(Pointee)), Address(A) {
|
|
|
|
revng_assert(isValid());
|
|
}
|
|
|
|
static ModelTypedIRAddress invalid() { return ModelTypedIRAddress(); }
|
|
|
|
bool isValid() const {
|
|
return nullptr != Address and not PointeeType.isEmpty()
|
|
and PointeeType->verify();
|
|
}
|
|
|
|
const auto &getPointeeType() const { return PointeeType; }
|
|
const auto &getAddress() const { return Address; }
|
|
|
|
// Enable structured bindings, but only the const version, so that the
|
|
// internal state cannot be inadvertently changed (and possibly invalidated)
|
|
// through bindings.
|
|
template<int I>
|
|
auto const &get() const {
|
|
if constexpr (I == 0)
|
|
return PointeeType;
|
|
else if constexpr (I == 1)
|
|
return Address;
|
|
else
|
|
static_assert(value_always_false_v<I>);
|
|
}
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "ModelTypedIRAddress = ";
|
|
if (not isValid()) {
|
|
OS << "invalid";
|
|
return;
|
|
}
|
|
|
|
OS << "{\nPointeeType:\n";
|
|
serialize(OS, PointeeType);
|
|
|
|
OS << "\nAddress: ";
|
|
Address->print(OS);
|
|
|
|
OS << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
// Enable structured bindings for ModelTypedIRAddress
|
|
namespace std {
|
|
|
|
template<>
|
|
struct tuple_size<ModelTypedIRAddress> : std::integral_constant<size_t, 2> {};
|
|
|
|
template<int I>
|
|
struct tuple_element<I, ModelTypedIRAddress> {
|
|
using GetReturnType = decltype(std::declval<ModelTypedIRAddress>().get<I>());
|
|
using type = std::remove_reference_t<GetReturnType>;
|
|
};
|
|
|
|
} // end namespace std
|
|
|
|
static std::string toDecimal(const APInt &Number) {
|
|
llvm::SmallString<16> Result;
|
|
Number.toString(Result, 10, true);
|
|
return Result.str().str();
|
|
}
|
|
|
|
static ConstantInt *getOneWithSameType(const Value *IntOrPtr) {
|
|
llvm::Type *TheType = IntOrPtr->getType();
|
|
revng_assert(TheType->isIntOrPtrTy());
|
|
unsigned BitWidth = TheType->isIntegerTy() ? TheType->getIntegerBitWidth() :
|
|
64;
|
|
|
|
APInt TheOne = APInt(/*NumBits*/ BitWidth, /*Value*/ 1);
|
|
return ConstantInt::get(IntOrPtr->getContext(), TheOne);
|
|
}
|
|
|
|
// This struct represents an addend of a summation on the LLVM IR in the form
|
|
// Coefficient * Index, where Coefficient is constant and Index can be whatever
|
|
// but not a constant.
|
|
class IRAddend {
|
|
|
|
private:
|
|
ConstantInt *Coefficient = nullptr;
|
|
Value *Index = nullptr;
|
|
|
|
public:
|
|
// Delete default constructor to forbid creation of invalid IRAddend.
|
|
IRAddend() = delete;
|
|
|
|
// IRAddends can be created only with this constructor, that ensures they're
|
|
// always valid.
|
|
IRAddend(ConstantInt *C, Value *I) : Coefficient(C), Index(I) {
|
|
revng_assert(nullptr != C and nullptr != I);
|
|
// The index must always be non-constant
|
|
revng_assert(not isa<Constant>(I));
|
|
// The index must always be an integer or a pointer
|
|
revng_assert(I->getType()->isIntOrPtrTy());
|
|
// The Coefficient must be non negative
|
|
revng_assert(C->getValue().isNonNegative());
|
|
}
|
|
IRAddend(Value *V) : IRAddend(getOneWithSameType(V), V) {}
|
|
|
|
IRAddend(const IRAddend &) = default;
|
|
IRAddend &operator=(const IRAddend &) = default;
|
|
|
|
IRAddend(IRAddend &&) = default;
|
|
IRAddend &operator=(IRAddend &&) = default;
|
|
|
|
// Accessors that prevents mutations.
|
|
const auto &coefficient() const { return Coefficient; }
|
|
const auto &index() const { return Index; }
|
|
|
|
// Enable structured bindings, but only the const version, so that the
|
|
// internal state cannot be inadvertently changed (and possibly invalidated)
|
|
// through structured bindings.
|
|
template<int I>
|
|
auto const &get() const {
|
|
if constexpr (I == 0)
|
|
return Coefficient;
|
|
else if constexpr (I == 1)
|
|
return Index;
|
|
else
|
|
static_assert(value_always_false_v<I>);
|
|
}
|
|
|
|
// Debug prints
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "IRAddend {\nCofficient:\n";
|
|
OS << toDecimal(Coefficient->getValue());
|
|
OS << "\nIndex:\n";
|
|
Index->print(OS);
|
|
OS << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
// Enable structured bindings for IRAddend
|
|
namespace std {
|
|
|
|
template<>
|
|
struct tuple_size<IRAddend> : std::integral_constant<size_t, 2> {};
|
|
|
|
template<int I>
|
|
struct tuple_element<I, IRAddend> {
|
|
using GetReturnType = decltype(std::declval<IRAddend>().get<I>());
|
|
using type = std::remove_reference_t<GetReturnType>;
|
|
};
|
|
|
|
} // end namespace std
|
|
|
|
static bool coefficientUGT(const APInt &LHS, const APInt &RHS) {
|
|
revng_assert(LHS.isNonNegative());
|
|
revng_assert(RHS.isNonNegative());
|
|
auto MaxBitWidth = std::max(LHS.getBitWidth(), RHS.getBitWidth());
|
|
APInt LargeLHS = LHS.zext(MaxBitWidth);
|
|
APInt LargeRHS = RHS.zext(MaxBitWidth);
|
|
return LargeLHS.ugt(LargeRHS);
|
|
};
|
|
|
|
static bool hasCoefficientUGT(const IRAddend &LHS, const IRAddend &RHS) {
|
|
return coefficientUGT(LHS.coefficient()->getValue(),
|
|
RHS.coefficient()->getValue());
|
|
}
|
|
|
|
static bool hasCoefficientULT(const IRAddend &LHS, const IRAddend &RHS) {
|
|
return coefficientUGT(RHS.coefficient()->getValue(),
|
|
LHS.coefficient()->getValue());
|
|
};
|
|
|
|
class IRSummation {
|
|
private:
|
|
static APInt getZeroAPInt() { return APInt(/*NumBits*/ 64, /*Value*/ 0); }
|
|
|
|
APInt Constant = getZeroAPInt();
|
|
SmallVector<IRAddend> Indices = {};
|
|
|
|
public:
|
|
// Construct from constants
|
|
explicit IRSummation(size_t C) : Constant(APInt(64, C)), Indices(){};
|
|
explicit IRSummation(APInt C) : Constant(C), Indices(){};
|
|
IRSummation() : IRSummation(getZeroAPInt()){};
|
|
|
|
// Construct from vectors of indices
|
|
explicit IRSummation(APInt C, const SmallVector<IRAddend> &I) :
|
|
Constant(C), Indices(I) {}
|
|
explicit IRSummation(APInt C, SmallVector<IRAddend> &&I) :
|
|
Constant(C), Indices(std::move(I)) {}
|
|
|
|
explicit IRSummation(const SmallVector<IRAddend> &I) :
|
|
IRSummation(getZeroAPInt(), I) {}
|
|
explicit IRSummation(SmallVector<IRAddend> &&I) :
|
|
IRSummation(getZeroAPInt(), std::move(I)) {}
|
|
|
|
// Construct from single addends
|
|
explicit IRSummation(APInt C, const IRAddend &Addend) :
|
|
IRSummation(C, SmallVector<IRAddend>{ Addend }) {}
|
|
explicit IRSummation(APInt C, IRAddend &&Addend) :
|
|
IRSummation(C, SmallVector<IRAddend>{ std::move(Addend) }) {}
|
|
|
|
explicit IRSummation(const IRAddend &Addend) :
|
|
IRSummation(getZeroAPInt(), Addend) {}
|
|
explicit IRSummation(IRAddend &&Addend) :
|
|
IRSummation(getZeroAPInt(), std::move(Addend)) {}
|
|
|
|
// Construct from single non-constant Value
|
|
explicit IRSummation(Value *V) : IRSummation(IRAddend(V)) {}
|
|
|
|
public:
|
|
IRSummation(const IRSummation &) = default;
|
|
IRSummation &operator=(const IRSummation &) = default;
|
|
|
|
IRSummation(IRSummation &&) = default;
|
|
IRSummation &operator=(IRSummation &&) = default;
|
|
|
|
// Helpers
|
|
|
|
bool isValid() const debug_function {
|
|
// Coefficients should be non-negative and increasing.
|
|
std::optional<APInt> Previous = std::nullopt;
|
|
for (const auto &I : Indices) {
|
|
APInt Current = I.coefficient()->getValue();
|
|
if (Current.isNegative())
|
|
return false;
|
|
if (Previous.has_value()) {
|
|
auto MaxBitWidth = std::max(Previous->getBitWidth(),
|
|
Current.getBitWidth());
|
|
if (Current.zext(MaxBitWidth).ugt(Previous->zext(MaxBitWidth)))
|
|
return false;
|
|
}
|
|
Previous = Current;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isConstant() const {
|
|
revng_assert(isValid());
|
|
return Indices.empty();
|
|
}
|
|
|
|
bool isZero() const { return Indices.empty() and Constant.isZero(); }
|
|
|
|
const auto &getConstant() const { return Constant; }
|
|
const auto &getIndices() const { return Indices; }
|
|
|
|
// Enable structured bindings, but only the const version, so that the
|
|
// internal state cannot be inadvertently changed (and possibly invalidated)
|
|
// through bindings.
|
|
template<int I>
|
|
auto const &get() const {
|
|
if constexpr (I == 0)
|
|
return Constant;
|
|
else if constexpr (I == 1)
|
|
return Indices;
|
|
else
|
|
static_assert(value_always_false_v<I>);
|
|
}
|
|
|
|
// Arithmetic operators
|
|
IRSummation operator+(const IRSummation &RHS) const {
|
|
IRSummation Result = *this;
|
|
if (this->Constant.getBitWidth() == RHS.Constant.getBitWidth()) {
|
|
Result.Constant += RHS.Constant;
|
|
} else {
|
|
auto MaxBitWidth = std::max(this->Constant.getBitWidth(),
|
|
RHS.Constant.getBitWidth());
|
|
Result.Constant = Result.Constant.zext(MaxBitWidth)
|
|
+ RHS.Constant.zext(MaxBitWidth);
|
|
}
|
|
revng_assert(not Result.Constant.isNegative());
|
|
Result.Indices.append(RHS.Indices);
|
|
llvm::sort(Result.Indices, hasCoefficientUGT);
|
|
revng_assert(Result.isValid());
|
|
return Result;
|
|
}
|
|
|
|
IRSummation &operator+=(const IRSummation &RHS) {
|
|
*this = *this + RHS;
|
|
return *this;
|
|
}
|
|
|
|
IRSummation operator-(size_t RHS) const {
|
|
revng_assert(this->Constant.uge(RHS),
|
|
"Subtracting offset from IRSummation would underflow");
|
|
IRSummation Result = *this;
|
|
Result.Constant -= RHS;
|
|
return Result;
|
|
}
|
|
|
|
IRSummation &operator-=(size_t RHS) {
|
|
*this = *this - RHS;
|
|
return *this;
|
|
}
|
|
|
|
bool operator==(const IRSummation &) const = default;
|
|
|
|
// Debug prints
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "IRSummation = {" << toDecimal(Constant);
|
|
|
|
for (const auto &I : Indices) {
|
|
OS << " + ";
|
|
I.dump(OS);
|
|
}
|
|
|
|
OS << "}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
// Enable structured bindings for IRSummation
|
|
namespace std {
|
|
|
|
template<>
|
|
struct tuple_size<IRSummation> : std::integral_constant<size_t, 2> {};
|
|
|
|
template<int I>
|
|
struct tuple_element<I, IRSummation> {
|
|
using GetReturnType = decltype(std::declval<IRSummation>().get<I>());
|
|
using type = std::remove_reference_t<GetReturnType>;
|
|
};
|
|
|
|
} // end namespace std
|
|
|
|
// This struct represents an "expression" of the form:
|
|
// BaseAddress + sum( const_i * index_i)
|
|
// This "expression" is actually tied to the LLVM IR, because const_i is an
|
|
// llvm::ConstantInt, index_i is an llvm::Value.
|
|
// When BaseAddress holds a valid value, it's a pointer to the llvm::Value
|
|
// representing the base address of the pointer arithmetic along with its model
|
|
// type. Otherwise it represents zero, so there's only the Summation term and
|
|
// the arithmetic is plain integer.
|
|
struct IRArithmetic {
|
|
|
|
// If this is nullopt this summation does not represent an address, but simply
|
|
// a summation of offsets.
|
|
ModelTypedIRAddress BaseAddress = {};
|
|
|
|
// If this is empty it means a zero offset.
|
|
IRSummation Summation = {};
|
|
|
|
IRArithmetic() = default;
|
|
|
|
IRArithmetic(const IRArithmetic &) = default;
|
|
IRArithmetic &operator=(const IRArithmetic &) = default;
|
|
|
|
IRArithmetic(IRArithmetic &&) = default;
|
|
IRArithmetic &operator=(IRArithmetic &&) = default;
|
|
|
|
private:
|
|
// Force use of factories for more advanced constructors
|
|
|
|
// Construct an IRArithmetic with only the base address, and no actual
|
|
// arithmetic.
|
|
IRArithmetic(ModelTypedIRAddress &&BA) :
|
|
BaseAddress(std::move(BA)), Summation() {}
|
|
|
|
// Construct an IRArithmetic without base address, that represents only an
|
|
// addend without
|
|
IRArithmetic(IRAddend &&Addend) :
|
|
BaseAddress(ModelTypedIRAddress::invalid()),
|
|
Summation(IRSummation(std::move(Addend))) {}
|
|
|
|
IRArithmetic(APInt Constant) :
|
|
BaseAddress(ModelTypedIRAddress::invalid()),
|
|
Summation(IRSummation(Constant)) {}
|
|
|
|
public:
|
|
bool isAddress() const { return BaseAddress.isValid(); }
|
|
|
|
//
|
|
// Factories
|
|
//
|
|
|
|
static IRArithmetic address(const model::Type &T, Value *Address) {
|
|
return IRArithmetic(ModelTypedIRAddress(T.getPointee(), Address));
|
|
}
|
|
|
|
static IRArithmetic index(ConstantInt *Coefficient, Value *Index) {
|
|
// The summation has only one element, with a constant coefficient
|
|
return IRArithmetic(IRAddend(Coefficient, Index));
|
|
}
|
|
|
|
static IRArithmetic constant(ConstantInt *C) {
|
|
return IRArithmetic(C->getValue());
|
|
}
|
|
|
|
static IRArithmetic unknown(Value *Unknown) { return IRArithmetic(Unknown); }
|
|
|
|
//
|
|
// Debug prints
|
|
//
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "IRArithmetic {\nBaseAddress: ";
|
|
BaseAddress.dump(OS);
|
|
OS << "\nSummation: {\n";
|
|
Summation.dump(OS);
|
|
OS << "\n}\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
using ModelTypesMap = std::map<const Value *, const model::UpcastableType>;
|
|
|
|
static RecursiveCoroutine<std::optional<IRArithmetic>>
|
|
getIRArithmetic(Use &AddressUse, const ModelTypesMap &PointerTypes) {
|
|
revng_log(ModelGEPLog,
|
|
"getIRArithmetic for use of: " << dumpToString(AddressUse.get()));
|
|
LoggerIndent Indent{ ModelGEPLog };
|
|
|
|
Value *AddressArith = AddressUse.get();
|
|
// If the used value and we know it has a pointer type, we already know
|
|
// both the base address and the pointer type.
|
|
if (auto TypeIt = PointerTypes.find(AddressArith);
|
|
TypeIt != PointerTypes.end()) {
|
|
|
|
auto &[AddressVal, Type] = *TypeIt;
|
|
|
|
revng_assert(Type->isPointer());
|
|
revng_log(ModelGEPLog, "Use is typed!");
|
|
|
|
rc_return IRArithmetic::address(*Type, AddressArith);
|
|
|
|
} else if (isa<ConstantExpr>(AddressArith)
|
|
or isa<Instruction>(AddressArith)) {
|
|
|
|
auto *ConstantValue = dyn_cast<ConstantInt>(skipCasts(AddressArith));
|
|
if (ConstantValue) {
|
|
revng_log(ModelGEPLog, "Constant !");
|
|
rc_return IRArithmetic::constant(ConstantValue);
|
|
}
|
|
|
|
unsigned int Opcode = 0;
|
|
auto *AddrArithmeticUser = dyn_cast<User>(AddressArith);
|
|
if (auto *I = dyn_cast<Instruction>(AddressArith))
|
|
Opcode = I->getOpcode();
|
|
else if (auto *CE = dyn_cast<ConstantExpr>(AddressArith))
|
|
Opcode = CE->getOpcode();
|
|
|
|
switch (Opcode) {
|
|
|
|
case Instruction::Add: {
|
|
Use &LHSUse = AddrArithmeticUser->getOperandUse(0);
|
|
auto LHSOrNone = rc_recur getIRArithmetic(LHSUse, PointerTypes);
|
|
|
|
Use &RHSUse = AddrArithmeticUser->getOperandUse(1);
|
|
auto RHSOrNone = rc_recur getIRArithmetic(RHSUse, PointerTypes);
|
|
|
|
if (not RHSOrNone.has_value() or not LHSOrNone.has_value())
|
|
break;
|
|
|
|
auto &LHS = LHSOrNone.value();
|
|
auto &RHS = RHSOrNone.value();
|
|
|
|
bool LHSIsAddress = LHS.isAddress();
|
|
bool RHSIsAddress = RHS.isAddress();
|
|
|
|
if (LHSIsAddress and RHSIsAddress) {
|
|
// In principle we should not expect to have many base address.
|
|
// If we do, at the moment we don't have a better policy than to bail
|
|
// out, and in principle this is totally safe, even if we give up a
|
|
// chance to emit good model geps for this case.
|
|
// In any case, we might want to devise smarter policies to
|
|
// discriminate between different base addresses. Anyway it's not
|
|
// clear if we can ever do something better than this.
|
|
rc_return std::nullopt;
|
|
}
|
|
|
|
// Here at least one among LHS and RHS is not an address.
|
|
|
|
IRArithmetic Result = LHSIsAddress ? LHS : RHS;
|
|
Result.Summation += LHSIsAddress ? RHS.Summation : LHS.Summation;
|
|
rc_return Result;
|
|
}
|
|
|
|
case Instruction::ZExt: {
|
|
// Zero extension is the only thing we traverse that allows the size to
|
|
// shrink if we go backwards. We have to detect this case, because if
|
|
// something is the result of zero extension of boolean value it cannot be
|
|
// an address for sure.
|
|
auto *Operand = AddrArithmeticUser->getOperand(0);
|
|
if (Operand->getType()->getIntegerBitWidth() == 1)
|
|
rc_return IRArithmetic::unknown(AddrArithmeticUser);
|
|
} break;
|
|
|
|
case Instruction::IntToPtr:
|
|
case Instruction::PtrToInt:
|
|
case Instruction::BitCast:
|
|
case Instruction::Freeze: {
|
|
// casts are traversed
|
|
revng_log(ModelGEPLog, "Traverse cast!");
|
|
rc_return rc_recur getIRArithmetic(AddrArithmeticUser->getOperandUse(0),
|
|
PointerTypes);
|
|
}
|
|
|
|
case Instruction::Mul: {
|
|
|
|
auto *Op0 = AddrArithmeticUser->getOperand(0);
|
|
auto *Op0Const = dyn_cast<ConstantInt>(Op0);
|
|
auto *Op1 = AddrArithmeticUser->getOperand(1);
|
|
auto *Op1Const = dyn_cast<ConstantInt>(Op1);
|
|
auto *ConstOp = Op1Const ? Op1Const : Op0Const;
|
|
auto *OtherOp = Op1Const ? Op0 : Op1;
|
|
|
|
if (ConstOp and ConstOp->getValue().isNonNegative()) {
|
|
// The constant operand is the coefficient, while the other is the
|
|
// index.
|
|
revng_assert(not ConstOp->isNegative());
|
|
rc_return IRArithmetic::index(ConstOp, OtherOp);
|
|
|
|
} else {
|
|
// In all the other cases, fall back to treating this as a
|
|
// non-address and non-strided instruction, just like e.g. division.
|
|
rc_return IRArithmetic::unknown(AddrArithmeticUser);
|
|
}
|
|
}
|
|
|
|
case Instruction::Shl: {
|
|
|
|
auto *ShiftedBits = AddrArithmeticUser->getOperand(1);
|
|
if (auto *ConstShift = dyn_cast<ConstantInt>(ShiftedBits)) {
|
|
|
|
if (ConstShift->getValue().isNonNegative()) {
|
|
// Build the stride
|
|
auto *AddrType = AddrArithmeticUser->getType();
|
|
auto *ArithTy = cast<llvm::IntegerType>(AddrType);
|
|
auto *Stride = ConstantInt::get(ArithTy,
|
|
1ULL << ConstShift->getZExtValue());
|
|
if (not Stride->isNegative()) {
|
|
// The first operand of the shift is the index
|
|
auto *IndexForStridedAccess = AddrArithmeticUser->getOperand(0);
|
|
|
|
rc_return IRArithmetic::index(Stride, IndexForStridedAccess);
|
|
}
|
|
}
|
|
}
|
|
|
|
// In all the other cases, fall back to treating this as a non-address
|
|
// and non-strided instruction, just like e.g. division.
|
|
|
|
rc_return IRArithmetic::unknown(AddrArithmeticUser);
|
|
}
|
|
|
|
case Instruction::Alloca: {
|
|
rc_return std::nullopt;
|
|
}
|
|
|
|
case Instruction::GetElementPtr: {
|
|
revng_abort("TODO: gep is not supported by make-model-gep yet");
|
|
}
|
|
|
|
case Instruction::Load:
|
|
case Instruction::Call:
|
|
case Instruction::PHI:
|
|
case Instruction::Trunc:
|
|
case Instruction::Select:
|
|
case Instruction::SExt:
|
|
case Instruction::Sub:
|
|
case Instruction::LShr:
|
|
case Instruction::AShr:
|
|
case Instruction::And:
|
|
case Instruction::Or:
|
|
case Instruction::Xor:
|
|
case Instruction::ICmp:
|
|
case Instruction::UDiv:
|
|
case Instruction::SDiv:
|
|
case Instruction::URem:
|
|
case Instruction::SRem: {
|
|
// If we reach one of these instructions, it definitely cannot be an
|
|
// address, but it's just considered as regular offset arithmetic of
|
|
// an unknown offset.
|
|
rc_return IRArithmetic::unknown(AddrArithmeticUser);
|
|
}
|
|
|
|
default: {
|
|
revng_abort("Unexpected instruction for address arithmetic");
|
|
} break;
|
|
}
|
|
|
|
} else if (auto *Const = dyn_cast<ConstantInt>(AddressArith)) {
|
|
|
|
// If we reach this point the constant int does not represent a pointer
|
|
// so we initialize the result as if it was an offset
|
|
if (Const->getValue().isNonNegative())
|
|
rc_return IRArithmetic::constant(Const);
|
|
|
|
} else if (auto *Arg = dyn_cast<llvm::Argument>(AddressArith)) {
|
|
|
|
// If we reach this point the argument does not represent a pointer so
|
|
// we initialize the result as if it was an offset
|
|
rc_return IRArithmetic::unknown(Arg);
|
|
|
|
} else if (isa<llvm::GlobalVariable>(AddressArith)
|
|
or isa<llvm::UndefValue>(AddressArith)
|
|
or isa<llvm::PoisonValue>(AddressArith)
|
|
or isa<llvm::Function>(AddressArith)) {
|
|
|
|
rc_return std::nullopt;
|
|
|
|
} else {
|
|
// We don't expect other stuff. This abort is mainly intended to be a
|
|
// safety net during development. It can eventually be dropped.
|
|
AddressArith->dump();
|
|
revng_abort();
|
|
}
|
|
|
|
rc_return std::nullopt;
|
|
}
|
|
|
|
enum MismatchScore {
|
|
PerfectTypeMatch = 0,
|
|
SameSize = 1,
|
|
InRange = 2,
|
|
OutOfBound = 3,
|
|
};
|
|
|
|
static std::string print(const MismatchScore &Match) {
|
|
switch (Match) {
|
|
case PerfectTypeMatch:
|
|
return "PerfectTypeMatch";
|
|
case SameSize:
|
|
return "SameSize";
|
|
case InRange:
|
|
return "InRange";
|
|
case OutOfBound:
|
|
return "OutOfBound";
|
|
default:
|
|
return "Invalid";
|
|
}
|
|
return "Invalid";
|
|
}
|
|
|
|
class DifferenceScore {
|
|
// The lower the mismatch score, the better.
|
|
MismatchScore Mismatch = PerfectTypeMatch;
|
|
|
|
// Higher Difference are for stuff that is farther apart from a perfect match
|
|
// from the beginning of the type.
|
|
IRSummation UnmatchedIR = {};
|
|
|
|
// This field represents how deep the type system was traversed to compute the
|
|
// score. Scores with a lower depth are considered better (so lower
|
|
// difference).
|
|
uint64_t Depth = std::numeric_limits<uint64_t>::min();
|
|
|
|
private:
|
|
// Force to use factories
|
|
DifferenceScore() = default;
|
|
|
|
DifferenceScore(MismatchScore M, const IRSummation &Unmatched, uint64_t D) :
|
|
Mismatch(M), UnmatchedIR(Unmatched), Depth(D) {}
|
|
|
|
private:
|
|
// Comparison operations among IRSummation that sorts before things that are
|
|
// "nicer" e.g they should be selected first for ModelGEP.
|
|
std::strong_ordering compare(const IRSummation &LHS,
|
|
const IRSummation &RHS) const {
|
|
revng_assert(LHS.isValid() and RHS.isValid());
|
|
|
|
// If the summations have different length, the shorter summation is
|
|
// considered lower.
|
|
if (auto Cmp = LHS.getIndices().size() <=> RHS.getIndices().size();
|
|
Cmp != 0)
|
|
return Cmp;
|
|
|
|
// Lexicographical compare the summations.
|
|
// The one which is lexicographically lower is considered lower.
|
|
for (const auto &[LHSAddend, RHSAddend] :
|
|
llvm::zip_equal(LHS.getIndices(), RHS.getIndices())) {
|
|
if (hasCoefficientULT(LHSAddend, RHSAddend))
|
|
return std::strong_ordering::less;
|
|
if (hasCoefficientUGT(LHSAddend, RHSAddend))
|
|
return std::strong_ordering::greater;
|
|
}
|
|
|
|
// If the two summations are equal, the one with lower Constant is lower.
|
|
if (LHS.getConstant().ult(RHS.getConstant()))
|
|
return std::strong_ordering::less;
|
|
if (LHS.getConstant().ugt(RHS.getConstant()))
|
|
return std::strong_ordering::greater;
|
|
|
|
return std::strong_ordering::equal;
|
|
}
|
|
|
|
public:
|
|
std::strong_ordering operator<=>(const DifferenceScore &Other) const {
|
|
|
|
// The one that is a better match has a lower difference.
|
|
if (auto Cmp = Mismatch <=> Other.Mismatch; Cmp != 0)
|
|
return Cmp;
|
|
|
|
// Here both are have the same type of match.
|
|
|
|
// If the Difference is not the same, one of the two has a lower difference,
|
|
// so we give precedence to that.
|
|
if (auto Cmp = compare(UnmatchedIR, Other.UnmatchedIR); Cmp != 0)
|
|
return Cmp;
|
|
|
|
// Here both have the same difference, e.g. they reach the same offset.
|
|
|
|
// At this point we consider the shallowest (i.e. lower Depth) to be the
|
|
// best.
|
|
return Depth <=> Other.Depth;
|
|
}
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "DifferenceScore { Mismatch = " << print(Mismatch)
|
|
<< "\nUnmatchedIR = ";
|
|
UnmatchedIR.dump(OS);
|
|
OS << "\nDepth = " << Depth << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
|
|
//
|
|
// Factories
|
|
//
|
|
|
|
static DifferenceScore min() { return DifferenceScore(); }
|
|
|
|
static DifferenceScore perfectMatch(uint64_t Depth) {
|
|
return DifferenceScore(PerfectTypeMatch, IRSummation(), Depth);
|
|
}
|
|
|
|
static DifferenceScore sameSizeMatch(uint64_t Depth) {
|
|
return DifferenceScore(SameSize, IRSummation(), Depth);
|
|
}
|
|
|
|
static DifferenceScore inRange(uint64_t Depth) {
|
|
return DifferenceScore(InRange, IRSummation(), Depth);
|
|
}
|
|
|
|
static DifferenceScore nestedOutOfBound(const IRSummation &DiffScore,
|
|
uint64_t Depth) {
|
|
return DifferenceScore(OutOfBound, DiffScore, Depth);
|
|
}
|
|
};
|
|
|
|
enum AggregateKind {
|
|
Invalid,
|
|
Struct,
|
|
Union,
|
|
Array
|
|
};
|
|
|
|
static std::string toString(AggregateKind K) {
|
|
switch (K) {
|
|
case Struct:
|
|
return "Struct";
|
|
case Union:
|
|
return "Union";
|
|
case Array:
|
|
return "Array";
|
|
default:
|
|
return "Invalid";
|
|
}
|
|
}
|
|
|
|
// This struct represents the information necessary for building an index of a
|
|
// ModelGEP. InductionVariable can be null.
|
|
struct ChildInfo {
|
|
IRSummation Index = IRSummation();
|
|
AggregateKind Type = AggregateKind::Invalid;
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "ChildInfo{\nIndex: ";
|
|
Index.dump(OS);
|
|
OS << "\nType: " << toString(Type) << "\n}";
|
|
}
|
|
|
|
bool verify() const {
|
|
if (Type == AggregateKind::Invalid)
|
|
return false;
|
|
if (Type == AggregateKind::Array)
|
|
return true;
|
|
return Index.isConstant();
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
using ChildIndexVector = SmallVector<ChildInfo>;
|
|
|
|
// A struct that represents all the information that are necessary to replace a
|
|
// use of an llvm::Value with a combination of ModelGEP + raw pointer
|
|
// arithmetic.
|
|
// Ideally the additional raw pointer arithmetic would not be necessary, but in
|
|
// practice we can't get rid of it because the underlying type system may be
|
|
// very poor, resulting in situations where not all the pointer arithmetic in
|
|
// LLVM IR can be neatly translated in a ModelGEP.
|
|
struct ModelGEPReplacementInfo {
|
|
model::UpcastableType BaseType;
|
|
ChildIndexVector IndexVector;
|
|
IRSummation Mismatched;
|
|
model::UpcastableType AccessedType;
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "ModelGEPReplacementInfo {\nBaseType:\n";
|
|
serialize(OS, BaseType);
|
|
OS << "\nIndexVector: {";
|
|
for (const auto &C : IndexVector) {
|
|
OS << "\n";
|
|
C.dump(OS);
|
|
}
|
|
OS << "}\nMismatched: {";
|
|
Mismatched.dump(OS);
|
|
OS << "}\nAccessedType: ";
|
|
serialize(OS, AccessedType);
|
|
OS << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
|
|
// Delete the default constructor. We want this to only be constructed with
|
|
// valid values.
|
|
ModelGEPReplacementInfo() = delete;
|
|
|
|
~ModelGEPReplacementInfo() = default;
|
|
|
|
ModelGEPReplacementInfo(const ModelGEPReplacementInfo &) = default;
|
|
ModelGEPReplacementInfo &operator=(const ModelGEPReplacementInfo &) = default;
|
|
|
|
ModelGEPReplacementInfo(ModelGEPReplacementInfo &&) = default;
|
|
ModelGEPReplacementInfo &operator=(ModelGEPReplacementInfo &&) = default;
|
|
|
|
ModelGEPReplacementInfo(const model::UpcastableType &Base,
|
|
const ChildIndexVector &Indices,
|
|
const IRSummation &Mismatch,
|
|
const model::UpcastableType &Accessed) :
|
|
BaseType(Base),
|
|
IndexVector(Indices),
|
|
Mismatched(Mismatch),
|
|
AccessedType(Accessed) {}
|
|
};
|
|
|
|
// Compute the lower bound of DifferenceScore if we can try to match more things
|
|
// in the GEPReplacementInfo
|
|
static DifferenceScore lowerBound(const ModelGEPReplacementInfo &GEPInfo,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH) {
|
|
|
|
const auto &[BaseOffset, Indices] = GEPInfo.Mismatched;
|
|
|
|
uint64_t AccessedSizeOnIR = 0ULL;
|
|
if (not AccessedTypeOnIR.isEmpty()) {
|
|
std::optional<uint64_t> AccessedSize = AccessedTypeOnIR->size(VH);
|
|
AccessedSizeOnIR = AccessedSize.value_or(0ULL);
|
|
}
|
|
|
|
// The depth of a match or mismatch will be at least long as the number of
|
|
// indices we have available.
|
|
size_t MatchDepth = GEPInfo.IndexVector.size();
|
|
|
|
// If the IRSum, along with the AccessedSizeOnIR is larger than the BaseType
|
|
// it means that all the accesses in the BaseSize based on the IRSum will go
|
|
// out ouf bound, at least with MatchDepth equal to now.
|
|
// We cannot foresee how big will be the Mismatched part.
|
|
// But we're estimating a lower bound, so we know that it will be out of bound
|
|
// for at least BaseOffset + AccessedSizeOnIR - BaseSize
|
|
uint64_t BaseSize = *rc_eval(GEPInfo.BaseType->size(VH));
|
|
if ((BaseOffset + AccessedSizeOnIR).ugt(BaseSize))
|
|
return DifferenceScore::nestedOutOfBound(IRSummation(BaseOffset
|
|
+ AccessedSizeOnIR
|
|
- BaseSize),
|
|
MatchDepth);
|
|
|
|
// In all the other cases the best outcome is a match.
|
|
|
|
// Given that we have the IRSum to consume to reach a match, we have to assume
|
|
// that all of it will be consumed. So we have to add to the already available
|
|
// MatchDepth, at least 1 for each distinct coefficient Indices, or at least
|
|
// one if we don't have any variable access.
|
|
if (not Indices.empty())
|
|
MatchDepth += Indices.size();
|
|
else
|
|
MatchDepth += BaseOffset.isZero() ? 0 : 1;
|
|
|
|
// If the IR doesn't have a pointee type the best case scenario is an inRange.
|
|
// We cannot have a perfectMatch or a sameSizeMatch if we don't have another
|
|
// type to compare it with.
|
|
if (AccessedTypeOnIR.isEmpty())
|
|
return DifferenceScore::inRange(MatchDepth);
|
|
|
|
// If the IR has a pointee type, the best that can happen we find a perfect
|
|
// match.
|
|
return DifferenceScore::perfectMatch(MatchDepth);
|
|
}
|
|
|
|
// This function computes, given a BaseType, the difference score between an
|
|
// access represented on the IR (IRSum + AccessedTypeOnIR) and a potential
|
|
// ModelGEP replacemente, represented by ModelGEPInfo.
|
|
// The smaller the difference, the best ModelGEPInfo represents the memory
|
|
// access as it is computed on the IR.
|
|
static DifferenceScore difference(const ModelGEPReplacementInfo &GEPInfo,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH) {
|
|
|
|
// The number of valid indices in IndexVector represents the depth we've
|
|
// reached so far without going out of bounds.
|
|
auto Depth = GEPInfo.IndexVector.size();
|
|
|
|
// If there is a non-zero mismatch, the GEPInfo represents an access that is
|
|
// out of bound w.r.t. to the model types, at the given Depth.
|
|
if (not GEPInfo.Mismatched.isZero())
|
|
return DifferenceScore::nestedOutOfBound(GEPInfo.Mismatched, Depth);
|
|
|
|
// Here Mismatch is zero
|
|
|
|
// If the GEPInfo has an AccessedType, given that the Mismatch is zero, it
|
|
// means that it has reached exactly the beginning of some type on the model.
|
|
// At this point there are the following scenarios.
|
|
|
|
// 1. If the AccessedTypeOnIR is NOT known we assume its not the same as
|
|
// GEPInfo.AccessedType BUT we assume it's not out of bound so we return
|
|
// inRange
|
|
if (AccessedTypeOnIR.isEmpty())
|
|
return DifferenceScore::inRange(Depth);
|
|
|
|
// 2. if the AccessedTypeOnIR is known, and is the same as the
|
|
// GEPInfo.AccessedType then we have perfectMatch
|
|
if (*AccessedTypeOnIR == *GEPInfo.AccessedType)
|
|
return DifferenceScore::perfectMatch(Depth);
|
|
|
|
std::optional<uint64_t> SizeOnIROrNOne = AccessedTypeOnIR->size(VH);
|
|
auto IRSize = SizeOnIROrNOne.value_or(0ULL);
|
|
std::optional<uint64_t> GEPSize = GEPInfo.AccessedType->size(VH);
|
|
revng_assert(GEPSize);
|
|
auto Cmp = IRSize <=> *GEPSize;
|
|
|
|
// 3. if the AccessedTypeOnIR is known, and is NOT the same as the
|
|
// GEPInfo.AccessedType BUT it has the same size then we have sameSizeMatch
|
|
if (Cmp == 0)
|
|
return DifferenceScore::sameSizeMatch(Depth);
|
|
|
|
// 4. if the AccessedTypeOnIR is known, and is NOT the same as the
|
|
// GEPInfo.AccessedType AND it has a smaller size then we have inRange
|
|
if (Cmp < 0)
|
|
return DifferenceScore::inRange(Depth);
|
|
|
|
// 5. if the AccessedTypeOnIR is known, and is NOT the same as the
|
|
// GEPInfo.AccessedType AND it has a larger size then we have an out of bound
|
|
return DifferenceScore::nestedOutOfBound(IRSummation(IRSize), Depth);
|
|
}
|
|
|
|
static RecursiveCoroutine<ModelGEPReplacementInfo>
|
|
computeBest(const model::UpcastableType &BaseType,
|
|
const IRSummation &IRSum,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH);
|
|
|
|
static RecursiveCoroutine<ModelGEPReplacementInfo>
|
|
computeBestInArray(const model::UpcastableType &BaseType,
|
|
const IRSummation &IRSum,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH) {
|
|
|
|
revng_log(ModelGEPLog, "computeBestInArray for IRSum: " << IRSum);
|
|
auto ArrayIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
const model::ArrayType &Array = BaseType->toArray();
|
|
uint64_t ElementSize = *rc_eval(Array.ElementType()->size(VH));
|
|
uint64_t ArraySize = ElementSize * Array.ElementCount();
|
|
|
|
const auto &[BaseOffset, Indices] = IRSum;
|
|
|
|
// Setup a return value for all the cases where we cannot traverse the array
|
|
// successfully. We basically emit no index and keep the whole IRSum as
|
|
// a Mismatch.
|
|
ModelGEPReplacementInfo ArrayNotTraversed(/* Base = */ BaseType,
|
|
/* Indices = */ {},
|
|
/* Mismatch = */ IRSum,
|
|
/* Accessed = */ BaseType);
|
|
|
|
uint64_t AccessedSizeOnIR = 0ULL;
|
|
if (not AccessedTypeOnIR.isEmpty()) {
|
|
std::optional<uint64_t> AccessedSize = AccessedTypeOnIR->size(VH);
|
|
AccessedSizeOnIR = AccessedSize.value_or(0ULL);
|
|
}
|
|
|
|
if ((BaseOffset + AccessedSizeOnIR).ugt(ArraySize)) {
|
|
revng_log(ModelGEPLog,
|
|
"Array not traversed for large offset. BestInArray: "
|
|
<< ArrayNotTraversed);
|
|
rc_return ArrayNotTraversed;
|
|
}
|
|
|
|
APInt APElementSize = APInt(/*NumBits*/ 64, /*value*/ ElementSize);
|
|
|
|
// Now, if Indices is not empty, we also have to compute the variable part of
|
|
// the array access.
|
|
std::optional<SmallVector<IRAddend>> InductionVariable = std::nullopt;
|
|
SmallVector<IRAddend> InnerIndices = Indices;
|
|
auto It = InnerIndices.begin();
|
|
// Find the first element in InnerIndices that has a Coefficient that is
|
|
// smaller than APElementSize.
|
|
auto End = llvm::upper_bound(InnerIndices,
|
|
APElementSize,
|
|
[](const APInt &LHS, const IRAddend &RHS) {
|
|
APInt RHSValue = RHS.coefficient()->getValue();
|
|
return coefficientUGT(LHS, RHSValue);
|
|
});
|
|
for (const auto &InnerIndex : llvm::make_range(It, End)) {
|
|
|
|
revng_log(ModelGEPLog, "Trying to traverse array index: " << InnerIndex);
|
|
const auto &[Coefficient, Index] = InnerIndex;
|
|
revng_assert(not isa<Constant>(Index));
|
|
|
|
// Unwrap the top layer of array indices, since we're traversing the
|
|
// array. In principle, we should unwrap it only if the Coefficient
|
|
// matches the size of the element of the array. In practice, this is
|
|
// overly restrictive, because it doesn't allow us to represent array
|
|
// accesses that access e.g. all the even elements in an array (for which
|
|
// the Coefficient would be a multiple of the element size).
|
|
//
|
|
// So we attempt to unwrap the top layer of array indices in all the cases
|
|
// where Coefficient is larger than the element size.
|
|
// At this point there are 2 scenarios.
|
|
// - the array access is done with a Coefficient that is a multiple of the
|
|
// element size: these are good and we want to handle them.
|
|
// - the array access is done with a Coefficient that is NOT a multiple of
|
|
// the element size: these are bad and we want to discard them.
|
|
//
|
|
// In all the cases where we can successfully unwrap, the non-constant
|
|
// Index is the induction variable and we have to track it.
|
|
//
|
|
// In all the other cases, where the Coefficient is lower than the element
|
|
// size, we don't unwrap, because the unwrap will occur in a more nested
|
|
// level, if necessary.
|
|
if (Coefficient->getValue().zext(64).ugt(APElementSize)) {
|
|
revng_log(ModelGEPLog, "Large coefficient");
|
|
revng_assert(Index);
|
|
|
|
APInt NumMultipleElements = APInt(/*NumBits*/ 64, /*value*/ 0);
|
|
APInt Remainder = APInt(/*NumBits*/ 64, /*value*/ 0);
|
|
|
|
APInt::udivrem(Coefficient->getValue().zext(64),
|
|
APElementSize,
|
|
NumMultipleElements,
|
|
Remainder);
|
|
if (Remainder.getBoolValue()) {
|
|
|
|
// This is not accessing an exact size of elements at each stride.
|
|
// Just skip this.
|
|
revng_log(ModelGEPLog,
|
|
"Array not traversed for large coefficient. BestInArray: "
|
|
<< ArrayNotTraversed);
|
|
rc_return ArrayNotTraversed;
|
|
} else {
|
|
|
|
revng_log(ModelGEPLog, "Coefficient is multiple of element size.");
|
|
if (not InductionVariable.has_value())
|
|
InductionVariable = SmallVector<IRAddend>{};
|
|
|
|
auto *Int64Type = llvm::IntegerType::getIntNTy(Index->getContext(), 64);
|
|
IRAddend NewAddend = IRAddend{
|
|
cast<ConstantInt>(ConstantInt::get(Int64Type, NumMultipleElements)),
|
|
Index
|
|
};
|
|
InductionVariable->push_back(std::move(NewAddend));
|
|
}
|
|
} else if (Coefficient->getValue() == ElementSize) {
|
|
|
|
revng_log(ModelGEPLog, "Coefficient is exact");
|
|
if (not InductionVariable.has_value())
|
|
InductionVariable = SmallVector<IRAddend>{};
|
|
|
|
InductionVariable->push_back(IRAddend(Index));
|
|
} else {
|
|
revng_abort();
|
|
}
|
|
}
|
|
InnerIndices.erase(It, End);
|
|
|
|
// Here BaseOffset might be larger than the ElementSize.
|
|
// Compute the minimum number of elements that will be skipped by pointer
|
|
// arithmetic, and also the new BaseOffset within the accessed element.
|
|
APInt BaseOffsetInElement;
|
|
APInt FixedElementIndex;
|
|
APInt::udivrem(BaseOffset,
|
|
APElementSize,
|
|
FixedElementIndex,
|
|
BaseOffsetInElement);
|
|
|
|
auto SummationInElement = IRSummation(BaseOffsetInElement,
|
|
std::move(InnerIndices));
|
|
IRSummation ElementSummation = IRSummation(FixedElementIndex);
|
|
if (InductionVariable.has_value())
|
|
ElementSummation += IRSummation(std::move(InductionVariable.value()));
|
|
auto ArrayAccessInfo = ChildInfo{ .Index = std::move(ElementSummation),
|
|
.Type = AggregateKind::Array };
|
|
|
|
ModelGEPReplacementInfo BestInArray(/* Base = */ BaseType,
|
|
/* Indices = */ { ArrayAccessInfo },
|
|
/* Mismatch = */ SummationInElement,
|
|
/* Accessed = */ Array.ElementType());
|
|
|
|
revng_log(ModelGEPLog, "BestInArray: " << BestInArray);
|
|
DifferenceScore BestScore = difference(BestInArray, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "BestInArray Score: " << BestScore);
|
|
|
|
DifferenceScore LowerBound = lowerBound(BestInArray, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
|
|
if (LowerBound >= BestScore) {
|
|
revng_log(ModelGEPLog, "Cannot improve: bail out");
|
|
rc_return BestInArray;
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "Analyze array element");
|
|
auto ElementIndex = LoggerIndent{ ModelGEPLog };
|
|
|
|
auto ElementResult = rc_recur computeBest(Array.ElementType(),
|
|
SummationInElement,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
// Fixup the ElementResult to be comparable with BestInArray
|
|
ElementResult.BaseType = Array;
|
|
ElementResult.IndexVector.insert(ElementResult.IndexVector.begin(),
|
|
ArrayAccessInfo);
|
|
|
|
revng_log(ModelGEPLog, "ElementResult: " << ElementResult);
|
|
|
|
DifferenceScore ElementScore = difference(ElementResult,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
revng_log(ModelGEPLog, "ElementResult Score: " << ElementScore);
|
|
|
|
if (ElementScore < BestScore) {
|
|
revng_log(ModelGEPLog, "New BestInArray: " << ElementResult);
|
|
BestInArray = ElementResult;
|
|
}
|
|
|
|
rc_return BestInArray;
|
|
}
|
|
|
|
static RecursiveCoroutine<ModelGEPReplacementInfo>
|
|
computeBestInStruct(const model::UpcastableType &BaseStruct,
|
|
const IRSummation &IRSum,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH) {
|
|
revng_log(ModelGEPLog, "computeBestInStruct for IRSum: " << IRSum);
|
|
auto StructIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
const auto &StructDefinition = BaseStruct->toStruct();
|
|
|
|
// Setup a return value for all the cases where we cannot traverse this struct
|
|
// successfully. We basically emit no index and keep the whole IRSum as
|
|
// a mismatch.
|
|
ModelGEPReplacementInfo BestInStruct(/* Base = */ BaseStruct,
|
|
/* Indices = */ {},
|
|
/* Mismatch = */ IRSum,
|
|
/* Accessed = */ BaseStruct);
|
|
|
|
uint64_t AccessedSizeOnIR = 0ULL;
|
|
if (not AccessedTypeOnIR.isEmpty()) {
|
|
std::optional<uint64_t> AccessedSize = AccessedTypeOnIR->size(VH);
|
|
AccessedSizeOnIR = AccessedSize.value_or(0ULL);
|
|
}
|
|
|
|
const auto &[BaseOffset, Indices] = IRSum;
|
|
|
|
uint64_t StructSize = *rc_eval(StructDefinition.size(VH));
|
|
if ((BaseOffset + AccessedSizeOnIR).ugt(StructSize)) {
|
|
revng_log(ModelGEPLog,
|
|
"Struct not traversed for large offset. BestInStruct: "
|
|
<< BestInStruct);
|
|
rc_return BestInStruct;
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "BestInStruct: " << BestInStruct);
|
|
DifferenceScore BestScore = difference(BestInStruct, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "BestInStruct Score: " << BestScore);
|
|
|
|
DifferenceScore LowerBound = lowerBound(BestInStruct, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
|
|
if (LowerBound >= BestScore) {
|
|
revng_log(ModelGEPLog, "Cannot improve: bail out");
|
|
rc_return BestInStruct;
|
|
}
|
|
|
|
// Now we try to unwrap the struct fields, and see if we can get better
|
|
// results on them.
|
|
|
|
auto FieldBegin = StructDefinition.Fields().begin();
|
|
// Let's detect the leftmost field that starts later than the maximum offset
|
|
// reachable by the IRSum. This is the first element that we don't want to
|
|
// compare because it's not a valid traversal for the given IRSum.
|
|
auto FEnd = StructDefinition.Fields().upper_bound(BaseOffset.getZExtValue());
|
|
|
|
enum {
|
|
InitiallyImproving,
|
|
StartedDegrading,
|
|
} Status = InitiallyImproving;
|
|
|
|
for (const model::StructField &Field :
|
|
llvm::reverse(llvm::make_range(FieldBegin, FEnd))) {
|
|
revng_log(ModelGEPLog, "Analyze Field with Offset: " << Field.Offset());
|
|
auto FieldIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
// At this point we assume we've accessed at least one field.
|
|
auto FieldAccessInfo = ChildInfo{ .Index = IRSummation(Field.Offset()),
|
|
.Type = AggregateKind::Struct };
|
|
|
|
IRSummation SumInField = IRSum;
|
|
SumInField -= Field.Offset();
|
|
|
|
ModelGEPReplacementInfo InField(/* Base = */ BaseStruct,
|
|
/* Indices = */ { FieldAccessInfo },
|
|
/* Mismatch = */ SumInField,
|
|
/* Accessed = */ Field.Type());
|
|
|
|
DifferenceScore ElementLowerBound = lowerBound(InField,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
|
|
|
|
// If we found a lower bound that is better, it should imply that we're
|
|
// still in the initial phase of improvement.
|
|
revng_assert(LowerBound >= BestScore or Status == InitiallyImproving);
|
|
|
|
if (LowerBound >= BestScore) {
|
|
revng_log(ModelGEPLog, "Cannot improve on this Field");
|
|
Status = StartedDegrading;
|
|
continue;
|
|
}
|
|
|
|
auto FieldResult = rc_recur computeBest(Field.Type(),
|
|
SumInField,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
// Fixup the FieldResult to be comparable with BestInStruct
|
|
FieldResult.BaseType = BaseStruct;
|
|
FieldResult.IndexVector.insert(FieldResult.IndexVector.begin(),
|
|
FieldAccessInfo);
|
|
|
|
revng_log(ModelGEPLog, "FieldResult: " << FieldResult);
|
|
|
|
DifferenceScore FieldScore = difference(FieldResult, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "FieldResult Score: " << FieldScore);
|
|
|
|
if (FieldScore < BestScore) {
|
|
BestInStruct = FieldResult;
|
|
BestScore = FieldScore;
|
|
revng_log(ModelGEPLog, "New BestInStruct: " << BestInStruct);
|
|
}
|
|
}
|
|
|
|
rc_return BestInStruct;
|
|
}
|
|
|
|
static RecursiveCoroutine<ModelGEPReplacementInfo>
|
|
computeBestInUnion(const model::UpcastableType &BaseUnion,
|
|
const IRSummation &IRSum,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH) {
|
|
|
|
revng_log(ModelGEPLog, "computeBestInUnion for IRSum: " << IRSum);
|
|
auto UnionIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
const model::UnionDefinition &UnionDefinition = BaseUnion->toUnion();
|
|
|
|
// Setup a return value for all the cases where we cannot traverse the union
|
|
// successfully. We basically emit no index and keep the whol IRSum as
|
|
// Mismatch.
|
|
ModelGEPReplacementInfo BestInUnion(/* Base = */ BaseUnion,
|
|
/* Indices = */ {},
|
|
/* Mismatch = */ IRSum,
|
|
/* Accessed = */ BaseUnion);
|
|
|
|
uint64_t AccessedSizeOnIR = 0ULL;
|
|
if (AccessedTypeOnIR) {
|
|
std::optional<uint64_t> AccessedSize = AccessedTypeOnIR->size(VH);
|
|
AccessedSizeOnIR = AccessedSize.value_or(0ULL);
|
|
}
|
|
|
|
const auto &[BaseOffset, Indices] = IRSum;
|
|
|
|
uint64_t UnionSize = *rc_eval(UnionDefinition.size(VH));
|
|
if ((BaseOffset + AccessedSizeOnIR).ugt(UnionSize)) {
|
|
revng_log(ModelGEPLog,
|
|
"Union not traversed for large offset. BestInUnion: "
|
|
<< BestInUnion);
|
|
rc_return BestInUnion;
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "BestInUnion: " << BestInUnion);
|
|
DifferenceScore BestScore = difference(BestInUnion, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "BestInUnion Score: " << BestScore);
|
|
|
|
DifferenceScore LowerBound = lowerBound(BestInUnion, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
|
|
if (LowerBound >= BestScore) {
|
|
revng_log(ModelGEPLog, "Cannot improve: bail out");
|
|
rc_return BestInUnion;
|
|
}
|
|
|
|
// Now we try to unwrap the union fields, and see if we can get better results
|
|
// on them.
|
|
|
|
for (const model::UnionField &Field : UnionDefinition.Fields()) {
|
|
revng_log(ModelGEPLog, "Analyze Field with ID: " << Field.Index());
|
|
auto FieldIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
// At this point we assume we've accessed at least one field.
|
|
auto FieldAccessInfo = ChildInfo{ .Index = IRSummation(Field.Index()),
|
|
.Type = AggregateKind::Union };
|
|
|
|
ModelGEPReplacementInfo InField(/* Base = */ BaseUnion,
|
|
/* Indices = */ { FieldAccessInfo },
|
|
/* Mismatch = */ IRSum,
|
|
/* Accessed = */ Field.Type());
|
|
|
|
DifferenceScore ElementLowerBound = lowerBound(InField,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
|
|
if (LowerBound >= BestScore) {
|
|
revng_log(ModelGEPLog, "Cannot improve on this Field");
|
|
continue;
|
|
}
|
|
|
|
auto FieldResult = rc_recur computeBest(Field.Type(),
|
|
IRSum,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
// Fixup the FieldResult to be comparable with BestInUnion
|
|
FieldResult.BaseType = BaseUnion;
|
|
FieldResult.IndexVector.insert(FieldResult.IndexVector.begin(),
|
|
FieldAccessInfo);
|
|
|
|
revng_log(ModelGEPLog, "FieldResult: " << FieldResult);
|
|
|
|
DifferenceScore FieldScore = difference(FieldResult, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "FieldResult Score: " << FieldScore);
|
|
|
|
if (FieldScore < BestScore) {
|
|
BestInUnion = FieldResult;
|
|
BestScore = FieldScore;
|
|
revng_log(ModelGEPLog, "New BestInUnion: " << BestInUnion);
|
|
}
|
|
}
|
|
|
|
rc_return BestInUnion;
|
|
}
|
|
|
|
static RecursiveCoroutine<ModelGEPReplacementInfo>
|
|
computeBest(const model::UpcastableType &BaseType,
|
|
const IRSummation &IRSum,
|
|
const model::UpcastableType &AccessedTypeOnIR,
|
|
model::VerifyHelper &VH) {
|
|
revng_log(ModelGEPLog, "Computing Best ModelGEP for IRSum: " << IRSum);
|
|
|
|
const auto &UnwrappedBaseType = *BaseType->skipConstAndTypedefs();
|
|
revng_assert(UnwrappedBaseType.isObject());
|
|
|
|
// This Result models no access, and leaves all the IRSum as a mismatch.
|
|
// It's handful for easy bail out for obvious failures to traverse this
|
|
// BaseType properly.
|
|
ModelGEPReplacementInfo Result(/* Base = */ BaseType,
|
|
/* Indices = */ {},
|
|
/* Mismatch = */ IRSum,
|
|
/* Accessed = */ BaseType);
|
|
|
|
// If we've reached a type that cannot be "traversed" by a ModelGEP we have
|
|
// nothing to do. This might be subject to change in the future when we'll
|
|
// need to support "accessing" pointer with the square brackets as if they
|
|
// were arrays.
|
|
if (UnwrappedBaseType.isScalar()) {
|
|
revng_log(ModelGEPLog,
|
|
"We never replace pointer arithmetic from a type that is not "
|
|
"a struct, a union, or an array, with a ModelGEP");
|
|
rc_return Result;
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "Result: " << Result);
|
|
DifferenceScore BestScore = difference(Result, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "BestScore: " << BestScore);
|
|
|
|
DifferenceScore LowerBound = lowerBound(Result, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
|
|
|
|
if (LowerBound >= BestScore) {
|
|
revng_log(ModelGEPLog, "Cannot improve: bail out");
|
|
rc_return Result;
|
|
}
|
|
|
|
if (UnwrappedBaseType.isArray()) {
|
|
revng_log(ModelGEPLog, "Array");
|
|
ModelGEPReplacementInfo ArrayResult = rc_recur
|
|
computeBestInArray(UnwrappedBaseType, IRSum, AccessedTypeOnIR, VH);
|
|
revng_log(ModelGEPLog, "ArrayResult: " << ArrayResult);
|
|
|
|
DifferenceScore ArrayBestScore = difference(ArrayResult,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
revng_log(ModelGEPLog, "ArrayBestScore: " << ArrayBestScore);
|
|
|
|
if (ArrayBestScore < BestScore) {
|
|
revng_log(ModelGEPLog, "New BestScore: " << ArrayResult);
|
|
rc_return ArrayResult;
|
|
}
|
|
|
|
rc_return Result;
|
|
|
|
} else if (UnwrappedBaseType.isPointer()) {
|
|
// TODO: we're gonna need to handle pointers in the future.
|
|
revng_abort();
|
|
|
|
} else if (UnwrappedBaseType.isStruct()) {
|
|
rc_return rc_recur computeBestInStruct(UnwrappedBaseType,
|
|
IRSum,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
|
|
} else if (UnwrappedBaseType.isUnion()) {
|
|
rc_return rc_recur computeBestInUnion(UnwrappedBaseType,
|
|
IRSum,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
|
|
} else {
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
static const model::Type &getType(const model::Type &BaseType,
|
|
const ChildIndexVector &IndexVector,
|
|
model::VerifyHelper &VH) {
|
|
const model::Type *CurrType = &BaseType;
|
|
bool TopLevel = true;
|
|
for (const auto &[Index, AggregateType] : IndexVector) {
|
|
|
|
switch (AggregateType) {
|
|
|
|
case AggregateKind::Struct: {
|
|
|
|
const auto &Fields = CurrType->toStruct().Fields();
|
|
CurrType = Fields.at(Index.getConstant().getZExtValue()).Type().get();
|
|
|
|
} break;
|
|
|
|
case AggregateKind::Union: {
|
|
|
|
const auto &Fields = CurrType->toUnion().Fields();
|
|
CurrType = Fields.at(Index.getConstant().getZExtValue()).Type().get();
|
|
|
|
} break;
|
|
|
|
case AggregateKind::Array: {
|
|
|
|
// For arrays and pointers we don't need to look at the value of
|
|
// the index, we just unwrap them.
|
|
const model::Type &Unwrapped = *CurrType->skipConstAndTypedefs();
|
|
if (auto *Pointer = Unwrapped.getPointer()) {
|
|
revng_assert(TopLevel);
|
|
CurrType = Pointer->PointeeType().get();
|
|
|
|
} else if (auto *Array = Unwrapped.getArray()) {
|
|
auto IndexValue = Index.getConstant().getZExtValue();
|
|
revng_assert(Array->ElementCount() > IndexValue);
|
|
|
|
CurrType = Array->ElementType().get();
|
|
|
|
} else {
|
|
revng_abort("An array or a pointer is expected.");
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
TopLevel = false;
|
|
}
|
|
|
|
return *CurrType;
|
|
}
|
|
|
|
using UseTypeMap = std::map<Use *, const model::UpcastableType>;
|
|
|
|
static model::UpcastableType
|
|
getAccessedTypeOnIR(const llvm::Use &U,
|
|
const model::Binary &Model,
|
|
const ModelTypesMap &PointerTypes,
|
|
const UseTypeMap &GEPifiedUseTypes) {
|
|
|
|
auto *UserInstr = dyn_cast<Instruction>(U.getUser());
|
|
if (nullptr == UserInstr)
|
|
return model::UpcastableType::empty();
|
|
|
|
switch (UserInstr->getOpcode()) {
|
|
|
|
case llvm::Instruction::Load: {
|
|
auto *Load = cast<llvm::LoadInst>(UserInstr);
|
|
revng_log(ModelGEPLog, "User is Load");
|
|
// If the user of U is a load, we know that the pointee's size is equal to
|
|
// the size of the loaded value
|
|
|
|
model::UpcastableType Result;
|
|
auto *PtrOp = Load->getPointerOperand();
|
|
if (auto It = PointerTypes.find(PtrOp); It != PointerTypes.end()) {
|
|
// If it's a known pointer, unwrap it before matching the type.
|
|
Result = It->second->getPointee();
|
|
}
|
|
|
|
auto *PtrOpUse = &Load->getOperandUse(Load->getPointerOperandIndex());
|
|
if (auto It = GEPifiedUseTypes.find(PtrOpUse);
|
|
It != GEPifiedUseTypes.end()) {
|
|
Result = It->second->getPointee();
|
|
|
|
} else {
|
|
revng_assert(Load->getType()->isIntOrPtrTy());
|
|
const llvm::DataLayout &DL = UserInstr->getModule()->getDataLayout();
|
|
auto PointeeSize = DL.getTypeStoreSize(Load->getType());
|
|
Result = model::PrimitiveType::makeGeneric(PointeeSize);
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "AccessedTypeOnIR: " << toString(Result));
|
|
return Result;
|
|
}
|
|
|
|
case llvm::Instruction::Store: {
|
|
auto *Store = cast<llvm::StoreInst>(UserInstr);
|
|
revng_log(ModelGEPLog, "User is Store");
|
|
// If the user of U is a store, and U is the pointer operand, we know
|
|
// that the pointee's size is equal to the size of the stored value.
|
|
if (U.getOperandNo() == llvm::StoreInst::getPointerOperandIndex()) {
|
|
revng_log(ModelGEPLog, "Use is pointer operand");
|
|
model::UpcastableType Result;
|
|
auto *PtrOp = Store->getPointerOperand();
|
|
if (auto It = PointerTypes.find(PtrOp); It != PointerTypes.end()) {
|
|
// If it's a known pointer, unwrap it before matching the type.
|
|
Result = It->second->getPointee();
|
|
}
|
|
|
|
auto *PtrOpUse = &Store->getOperandUse(Store->getPointerOperandIndex());
|
|
if (auto It = GEPifiedUseTypes.find(PtrOpUse);
|
|
It != GEPifiedUseTypes.end()) {
|
|
Result = It->second->getPointee();
|
|
}
|
|
|
|
if (Result.isEmpty() or Result->isStruct() or Result->isUnion()) {
|
|
// Do not allow structs or unions.
|
|
auto *Stored = Store->getValueOperand();
|
|
revng_assert(Stored->getType()->isIntOrPtrTy());
|
|
const llvm::DataLayout &DL = UserInstr->getModule()->getDataLayout();
|
|
unsigned long PointeeSize = DL.getTypeStoreSize(Stored->getType());
|
|
Result = model::PrimitiveType::makeGeneric(PointeeSize);
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "AccessedTypeOnIR: " << toString(Result));
|
|
return Result;
|
|
} else {
|
|
revng_log(ModelGEPLog, "Use is pointer operand");
|
|
}
|
|
|
|
} break;
|
|
|
|
case llvm::Instruction::Ret: {
|
|
auto *Ret = cast<llvm::ReturnInst>(UserInstr);
|
|
llvm::Function *ReturningF = Ret->getFunction();
|
|
|
|
// If the user is a ret, we want to look at the return type of the
|
|
// function we're returning from, and use it as a pointee type.
|
|
|
|
const model::Function &MF = *llvmToModelFunction(Model, *ReturningF);
|
|
const auto &Prototype = *Model.prototypeOrDefault(MF.prototype());
|
|
const auto Layout = abi::FunctionType::Layout::make(Prototype);
|
|
|
|
const model::Type *SingleReturnType = nullptr;
|
|
switch (Layout.returnMethod()) {
|
|
case abi::FunctionType::ReturnMethod::ModelAggregate:
|
|
SingleReturnType = &Layout.returnValueAggregateType();
|
|
break;
|
|
|
|
case abi::FunctionType::ReturnMethod::Scalar:
|
|
SingleReturnType = Layout.ReturnValues[0].Type.get();
|
|
break;
|
|
|
|
case abi::FunctionType::ReturnMethod::Void:
|
|
// If the callee function does not return anything, skip to the next
|
|
// instruction.
|
|
revng_log(ModelGEPLog, "Does not return values in the model. Skip ...");
|
|
revng_assert(not Ret->getReturnValue());
|
|
break;
|
|
|
|
case abi::FunctionType::ReturnMethod::RegisterSet: {
|
|
auto *RetVal = Ret->getReturnValue();
|
|
auto *StructTy = cast<llvm::StructType>(RetVal->getType());
|
|
revng_log(ModelGEPLog, "Has many return types.");
|
|
auto ReturnValuesCount = Layout.ReturnValues.size();
|
|
revng_assert(StructTy->getNumElements() == ReturnValuesCount);
|
|
|
|
// Assert that we're returning a proper struct, initialized with
|
|
// struct initializers, but don't do anything here.
|
|
const auto *Returned = getCalledFunction(cast<llvm::CallInst>(RetVal));
|
|
revng_assert(FunctionTags::StructInitializer.isTagOf(Returned));
|
|
} break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
|
|
if (SingleReturnType) {
|
|
revng_log(ModelGEPLog, "Has a single return value.");
|
|
|
|
revng_assert(Ret->getReturnValue()->getType()->isVoidTy()
|
|
or Ret->getReturnValue()->getType()->isIntOrPtrTy());
|
|
|
|
// If the returned type is a pointer, we unwrap it and set the pointee
|
|
// type of IRPattern to the pointee of the return type.
|
|
// Otherwise the Function is not returning a pointer, and we can skip
|
|
// it.
|
|
if (const auto *Pointer = SingleReturnType->getPointer()) {
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "llvm::ReturnInst: " << dumpToString(Ret));
|
|
revng_log(ModelGEPLog, "Pointee: " << toString(*Pointer));
|
|
return SingleReturnType->getPointee();
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
case llvm::Instruction::Call: {
|
|
auto *Call = dyn_cast<llvm::CallInst>(UserInstr);
|
|
// If the user is a call, and it's calling an isolated function we want to
|
|
// look at the argument types of the callee on the model, and use info
|
|
// coming from them for initializing IRPattern.AccessedType
|
|
|
|
revng_log(ModelGEPLog, "Call");
|
|
|
|
if (isCallToTagged(Call, FunctionTags::StructInitializer)) {
|
|
|
|
const llvm::Function &CalledF = *getCalledFunction(Call);
|
|
|
|
// Special case for struct initializers
|
|
unsigned ArgNum = Call->getArgOperandNo(&U);
|
|
|
|
const model::Function &CalledFType = *llvmToModelFunction(Model, CalledF);
|
|
if (const auto *RFT = CalledFType.rawPrototype()) {
|
|
revng_log(ModelGEPLog, "Has RawFunctionDefinition prototype.");
|
|
revng_assert(RFT->ReturnValues().size() > 1);
|
|
|
|
auto *StructTy = cast<llvm::StructType>(CalledF.getReturnType());
|
|
revng_log(ModelGEPLog, "Has many return types.");
|
|
auto ValuesCount = RFT->ReturnValues().size();
|
|
revng_assert(StructTy->getNumElements() == ValuesCount);
|
|
|
|
auto RetTy = std::next(RFT->ReturnValues().begin(), ArgNum)->Type();
|
|
if (const model::PointerType *Pointer = RetTy->getPointer()) {
|
|
revng_log(ModelGEPLog,
|
|
"Pointee: " << toString(Pointer->PointeeType()));
|
|
return Pointer->PointeeType();
|
|
}
|
|
} else if (CalledFType.cabiPrototype() != nullptr) {
|
|
revng_log(ModelGEPLog, "Has CABIFunctionDefinition prototype.");
|
|
// TODO: we haven't handled return values of CABIFunctions yet
|
|
revng_abort();
|
|
} else {
|
|
revng_abort("Function should have RawFunctionDefinition or "
|
|
"CABIFunctionDefinition");
|
|
}
|
|
|
|
} else if (isCallToIsolatedFunction(Call)) {
|
|
auto *ProtoT = getCallSitePrototype(Model, Call);
|
|
revng_assert(ProtoT != nullptr);
|
|
|
|
if (const auto *RFT = llvm::dyn_cast<RawFunctionDefinition>(ProtoT)) {
|
|
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
auto ModelArgSize = RFT->Arguments().size();
|
|
bool HasStackArgs = not RFT->StackArgumentsType().isEmpty();
|
|
revng_assert((ModelArgSize == Call->arg_size() - 1 and HasStackArgs)
|
|
or ModelArgSize == Call->arg_size());
|
|
revng_log(ModelGEPLog, "model::RawFunctionDefinition");
|
|
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
if (not Call->isCallee(&U)) {
|
|
unsigned N = Call->getArgOperandNo(&U);
|
|
revng_log(ModelGEPLog, "ArgOpNum: " << N);
|
|
revng_log(ModelGEPLog, "ArgOperand: " << U.get());
|
|
|
|
// The only case in which the RawFunctionDefinition's argument index
|
|
// can be greater than the number of register arguments in the model
|
|
// is when the function has stack arguments.
|
|
// Stack arguments are passed as the last argument of the llvm
|
|
// function, but they do not have a corresponding argument in the
|
|
// model. In this case, we have to retrieve the StackArgumentsType
|
|
// from the function prototype.
|
|
const auto &ArgTy = N >= ModelArgSize ?
|
|
RFT->StackArgumentsType() :
|
|
std::next(RFT->Arguments().begin(), N)->Type();
|
|
|
|
revng_log(ModelGEPLog, "Type: " << toString(ArgTy));
|
|
if (const model::PointerType *Pointer = ArgTy->getPointer()) {
|
|
revng_log(ModelGEPLog,
|
|
"Pointee: " << toString(Pointer->PointeeType()));
|
|
return Pointer->PointeeType();
|
|
}
|
|
} else {
|
|
revng_log(ModelGEPLog, "IsCallee");
|
|
}
|
|
|
|
} else if (const auto *CFT = dyn_cast<CABIFunctionDefinition>(ProtoT)) {
|
|
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
revng_assert(CFT->Arguments().size() == Call->arg_size());
|
|
revng_log(ModelGEPLog, "model::CABIFunctionDefinition");
|
|
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
if (not Call->isCallee(&U)) {
|
|
unsigned ArgOpNum = Call->getArgOperandNo(&U);
|
|
revng_log(ModelGEPLog, "ArgOpNum: " << ArgOpNum);
|
|
revng_log(ModelGEPLog, "ArgOperand: " << U.get());
|
|
const auto &ArgTy = CFT->Arguments().at(ArgOpNum).Type();
|
|
revng_log(ModelGEPLog, "Type: " << toString(ArgTy));
|
|
if (const model::PointerType *Pointer = ArgTy->getPointer()) {
|
|
revng_log(ModelGEPLog,
|
|
"Pointee: " << toString(Pointer->PointeeType()));
|
|
return Pointer->PointeeType();
|
|
}
|
|
} else {
|
|
revng_log(ModelGEPLog, "IsCallee");
|
|
}
|
|
|
|
} else {
|
|
revng_abort("Functions are only allowed to have `RawFunctionDefinition`"
|
|
" or `CABIFunctionDefinition` prototypes");
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
default:
|
|
return model::UpcastableType::empty();
|
|
}
|
|
|
|
return model::UpcastableType::empty();
|
|
}
|
|
|
|
struct UseReplacementWithModelGEP {
|
|
Use *U = nullptr;
|
|
Value *BaseAddress = nullptr;
|
|
ModelGEPReplacementInfo ReplacementInfo;
|
|
};
|
|
|
|
static std::vector<UseReplacementWithModelGEP>
|
|
makeGEPReplacements(llvm::Function &F,
|
|
const model::Binary &Model,
|
|
model::VerifyHelper &VH) {
|
|
|
|
std::vector<UseReplacementWithModelGEP> Result;
|
|
|
|
const model::Function *ModelF = llvmToModelFunction(Model, F);
|
|
revng_assert(ModelF);
|
|
|
|
// First, try to initialize a map for the known model types of llvm::Values
|
|
// that are reachable from F. If this fails, we just bail out because we
|
|
// cannot infer any modelGEP in F, if we have no type information to rely
|
|
// on.
|
|
ModelTypesMap
|
|
PointerTypes = initModelTypesConsideringUses(F,
|
|
ModelF,
|
|
Model,
|
|
/* PointersOnly = */ true);
|
|
if (PointerTypes.empty()) {
|
|
revng_log(ModelGEPLog, "Model Types not found for " << F.getName());
|
|
return Result;
|
|
}
|
|
|
|
UseTypeMap GEPifiedUsedTypes;
|
|
|
|
auto RPOT = ReversePostOrderTraversal(&F.getEntryBlock());
|
|
for (auto *BB : RPOT) {
|
|
for (auto &I : *BB) {
|
|
revng_log(ModelGEPLog, "Instruction " << dumpToString(&I));
|
|
auto Indent = LoggerIndent{ ModelGEPLog };
|
|
|
|
if (auto *CallI = dyn_cast<llvm::CallInst>(&I)) {
|
|
if (not isCallToIsolatedFunction(CallI)) {
|
|
revng_log(ModelGEPLog, "Skipping call to non-isolated function");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (Use &U : I.operands()) {
|
|
|
|
// Skip everything that is not a pointer or an integer, since it
|
|
// cannot be an any kind of pointer arithmetic that we handle
|
|
if (not U.get()->getType()->isIntOrPtrTy()) {
|
|
revng_log(ModelGEPLog,
|
|
"Skipping operand that cannot be pointer arithmetic");
|
|
continue;
|
|
}
|
|
|
|
// Skip non-pointer-sized integers, since they cannot be addresses
|
|
if (auto *IntTy = dyn_cast<llvm::IntegerType>(U.get()->getType())) {
|
|
using model::Architecture::getPointerSize;
|
|
auto PtrBitSize = getPointerSize(Model.Architecture()) * 8;
|
|
if (IntTy->getIntegerBitWidth() != PtrBitSize) {
|
|
revng_log(ModelGEPLog, "Skipping i1 value");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Skip callee operands in CallInst if it's not an llvm::Instruction.
|
|
// If it is an Instruction we should handle it.
|
|
if (auto *CallUser = dyn_cast<llvm::CallInst>(U.getUser())) {
|
|
if (not isa<llvm::Instruction>(CallUser->getCalledOperand())) {
|
|
if (&U == &CallUser->getCalledOperandUse()) {
|
|
revng_log(ModelGEPLog, "Skipping callee operand in CallInst");
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip null pointer constants, and undefs, since they cannot be valid
|
|
// addresses
|
|
// TODO: if we ever need to support memory mapped at address 0 we can
|
|
// probably work around this, but this is not top priority for now.
|
|
if (isa<llvm::UndefValue>(U.get()) or isa<llvm::PoisonValue>(U.get())
|
|
or isa<llvm::ConstantPointerNull>(U.get())) {
|
|
revng_log(ModelGEPLog, "Skipping null pointer address");
|
|
continue;
|
|
}
|
|
|
|
std::optional<IRArithmetic>
|
|
IRArithmeticOrNone = getIRArithmetic(U, PointerTypes);
|
|
if (not IRArithmeticOrNone.has_value())
|
|
continue;
|
|
|
|
IRArithmetic &IRPointerArithmetic = IRArithmeticOrNone.value();
|
|
revng_log(ModelGEPLog, "IRPointerArithmetic " << IRPointerArithmetic);
|
|
|
|
if (not IRPointerArithmetic.isAddress())
|
|
continue;
|
|
|
|
const auto &[BaseAddress, IRSum] = IRPointerArithmetic;
|
|
const model::Type &PointeeType = *BaseAddress.getPointeeType();
|
|
|
|
const auto &Unwrapped = *PointeeType.skipConstAndTypedefs();
|
|
if (Unwrapped.isVoidPrimitive() or Unwrapped.isPrototype())
|
|
continue;
|
|
|
|
// Compute the type accessed by this use if any.
|
|
auto AccessedTypeOnIR = getAccessedTypeOnIR(U,
|
|
Model,
|
|
PointerTypes,
|
|
GEPifiedUsedTypes);
|
|
|
|
// Now, compute the best GEP arguments for traversing the PointeeType.
|
|
//
|
|
// Notice that we have to try and access the pointer both via a
|
|
// regular dereference and with the [] operator.
|
|
// In order to do this, we have to create a fake array to wrap
|
|
// the `PointeeType` into, and compute the best access in there.
|
|
//
|
|
// TODO: MaxArrayLength is arbitrary. On the one hand we should make
|
|
// it large enough to enable traversals with large indices without
|
|
// falling back to out-of-bound raw-integer-arithmetic. On the other
|
|
// hand if we make it too large we hit overflows on computations of
|
|
// the total size of the array. So UINT32_MAX seemed like a good
|
|
// compromise, but at some point we might need to handle this
|
|
// properly.
|
|
const uint64_t MaxArrayLength = std::numeric_limits<uint32_t>::max();
|
|
auto FakeArray = model::ArrayType::make(PointeeType, MaxArrayLength);
|
|
|
|
// Select among the computed TAPIndices the one which best fits the
|
|
// IRPattern
|
|
ModelGEPReplacementInfo GEPArgs = computeBest(FakeArray,
|
|
IRSum,
|
|
AccessedTypeOnIR,
|
|
VH);
|
|
|
|
// Fix up the BaseType. This needs to contain the base type as per the
|
|
// ModelGEP specification, not the fake array.
|
|
GEPArgs.BaseType = PointeeType;
|
|
revng_log(ModelGEPLog, "GEPArgs: " << GEPArgs);
|
|
|
|
// If the result doesn't have any mismatches we can use it to
|
|
// propagate information in GEPifiedUsedTypes and PointerTypes.
|
|
if (GEPArgs.Mismatched.isZero()) {
|
|
auto Ptr = model::PointerType::make(GEPArgs.AccessedType.copy(),
|
|
Model.Architecture());
|
|
GEPifiedUsedTypes.insert({ &U, std::move(Ptr) });
|
|
|
|
revng_log(ModelGEPLog, "Best GEPArgs: " << GEPArgs);
|
|
|
|
// If IRSum is an address and I is an "address barrier"
|
|
// instruction (e.g. an instruction such that pointer arithmetic does
|
|
// not propagate through it), we need to check if we can still deduce
|
|
// a rich pointer type for I starting from IRSum. An example of an
|
|
// "address barrier" is a xor instruction (where we cannot deduce the
|
|
// type of the xored value even if one of the operands has a known
|
|
// pointer type); another example is a phi, where we can always deduce
|
|
// that the phi has a rich pointer type if one of the incoming values
|
|
// has a rich pointer type. The example of the xor is particularly
|
|
// interesting, because one day we can think of starting to support it
|
|
// for addresses that are built with masks, with small analyses. So
|
|
// this is good customization point.
|
|
//
|
|
// In particular, we need to take care at least of the following
|
|
// cases:
|
|
// DONE:
|
|
// - if I is a load and the loaded stuff is a pointer we have to set
|
|
// the type of the load
|
|
// TODO:
|
|
// - if I is a phi, we need to set the phi type
|
|
// - if one of the incoming has pointer type, we can take that. but
|
|
// what happens if many incoming have different pointer types, can
|
|
// we use a pointer to the parent type (the one that all should
|
|
// inherit from)?
|
|
// - if I is a select instruction we can do something like the PHI
|
|
// - if I is an alloca, I'm not sure what we can do
|
|
if (auto *Load = dyn_cast<llvm::LoadInst>(&I)) {
|
|
const auto &GEPType = getType(*FakeArray, GEPArgs.IndexVector, VH);
|
|
if (GEPType.isPointer())
|
|
PointerTypes.insert({ Load, GEPType });
|
|
}
|
|
}
|
|
|
|
Result.push_back({ &U, BaseAddress.getAddress(), std::move(GEPArgs) });
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
class ModelGEPArgCache {
|
|
|
|
std::map<model::UpcastableType, Constant *> GlobalModelGEPTypeArgs;
|
|
|
|
public:
|
|
Constant *get(const model::UpcastableType &Type, llvm::Module &M) {
|
|
auto &&[It, Success] = GlobalModelGEPTypeArgs.try_emplace(Type, nullptr);
|
|
if (Success)
|
|
It->second = toLLVMString(Type, M);
|
|
|
|
return It->second;
|
|
}
|
|
};
|
|
|
|
static llvm::BasicBlock *getUniqueIncoming(Value *V, PHINode *Phi) {
|
|
llvm::BasicBlock *Result = nullptr;
|
|
|
|
for (auto &&[IncomingValue, IncomingBlock] :
|
|
zip(Phi->incoming_values(), Phi->blocks())) {
|
|
if (IncomingValue == V) {
|
|
if (Result == nullptr) {
|
|
// First matching incoming value, register incoming block
|
|
Result = IncomingBlock;
|
|
} else if (Result != IncomingBlock) {
|
|
// We different incoming blocks for the same value
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
revng_assert(Result != nullptr, "V is not an incoming value of Phi");
|
|
|
|
return Result;
|
|
}
|
|
|
|
struct MakeModelGEPPass : public FunctionPass {
|
|
public:
|
|
static char ID;
|
|
|
|
MakeModelGEPPass() : FunctionPass(ID) {}
|
|
|
|
bool runOnFunction(llvm::Function &F) override;
|
|
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
|
AU.setPreservesCFG();
|
|
AU.addRequired<LoadModelWrapperPass>();
|
|
}
|
|
};
|
|
|
|
bool MakeModelGEPPass::runOnFunction(llvm::Function &F) {
|
|
bool Changed = false;
|
|
|
|
revng_log(ModelGEPLog, "Make ModelGEP for " << F.getName());
|
|
auto Indent = LoggerIndent(ModelGEPLog);
|
|
|
|
auto &Model = getAnalysis<LoadModelWrapperPass>().get().getReadOnlyModel();
|
|
|
|
model::VerifyHelper VH;
|
|
auto GEPReplacements = makeGEPReplacements(F, *Model, VH);
|
|
|
|
llvm::Module &M = *F.getParent();
|
|
LLVMContext &Context = M.getContext();
|
|
ModelGEPArgCache TypeArgCache;
|
|
|
|
// Here we should definitely use the builder that checks the debug info,
|
|
// but since this going to go away soon, let it stay as is.
|
|
revng::NonDebugInfoCheckingIRBuilder Builder(Context);
|
|
|
|
// Create a function pool for AddressOf calls
|
|
auto AddressOfPool = FunctionTags::AddressOf.getPool(M);
|
|
|
|
llvm::IntegerType
|
|
*PtrSizedInteger = getPointerSizedInteger(Context, Model->Architecture());
|
|
|
|
std::map<std::pair<Instruction *, Value *>, Value *> PhiIncomingsMaps;
|
|
|
|
for (auto &[TheUseToGEPify, BaseAddress, GEPArgs] : GEPReplacements) {
|
|
|
|
const auto &[BaseType, IndexVector, Mismatched, AccessedType] = GEPArgs;
|
|
const auto &[MismatchedOffset, MismatchedIndices] = Mismatched;
|
|
|
|
// Early exit for avoiding to emit a lot of &* in C.
|
|
{
|
|
if (IndexVector.empty())
|
|
continue;
|
|
|
|
const auto &[Index, AggregateTy] = IndexVector.front();
|
|
revng_assert(AggregateTy == AggregateKind::Array);
|
|
|
|
// This is not necessary or wrong strictly speaking, but if we don't bail
|
|
// out on this case we end up generating a lot of &* in C.
|
|
if (IndexVector.size() == 1 and Index.isZero())
|
|
continue;
|
|
}
|
|
|
|
auto *UserInstr = cast<Instruction>(TheUseToGEPify->getUser());
|
|
auto *V = TheUseToGEPify->get();
|
|
|
|
bool IsPhi = isa<PHINode>(UserInstr);
|
|
if (IsPhi) {
|
|
auto It = PhiIncomingsMaps.find({ UserInstr, V });
|
|
if (It != PhiIncomingsMaps.end()) {
|
|
TheUseToGEPify->set(It->second);
|
|
revng_log(ModelGEPLog,
|
|
" `-> replaced with: " << dumpToString(It->second));
|
|
Changed = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "GEPify use of: " << dumpToString(V));
|
|
revng_log(ModelGEPLog, " `-> use in: " << dumpToString(UserInstr));
|
|
|
|
llvm::Type *UseType = TheUseToGEPify->get()->getType();
|
|
llvm::Type *BaseAddrType = BaseAddress->getType();
|
|
|
|
auto *ModelGEPFunction = getModelGEP(M, PtrSizedInteger, BaseAddrType);
|
|
|
|
// Build the arguments for the call to modelGEP
|
|
SmallVector<Value *, 4> Args;
|
|
Args.reserve(GEPArgs.IndexVector.size() + 2);
|
|
|
|
// The first argument is always a pointer to a constant global variable
|
|
// that holds the string representing the yaml serialization of
|
|
// the base type of the modelGEP
|
|
Args.push_back(TypeArgCache.get(*BaseType, M));
|
|
|
|
// The second argument is the base address
|
|
Args.push_back(BaseAddress);
|
|
|
|
// Set the insert point for building the other arguments
|
|
if (auto *PHIUser = dyn_cast<PHINode>(UserInstr)) {
|
|
if (isa<llvm::Argument>(TheUseToGEPify->get())) {
|
|
// Insert at the beginning of the function
|
|
Builder.SetInsertPoint(F.getEntryBlock().getFirstNonPHI());
|
|
} else if (llvm::BasicBlock *Incoming = getUniqueIncoming(V, PHIUser)) {
|
|
// Insert at the end of the incoming block
|
|
Builder.SetInsertPoint(Incoming->getTerminator());
|
|
} else {
|
|
// Insert at the end of the block of the user
|
|
auto *IncomingInstruction = cast<Instruction>(TheUseToGEPify->get());
|
|
auto *Terminator = IncomingInstruction->getParent()->getTerminator();
|
|
Builder.SetInsertPoint(Terminator);
|
|
}
|
|
} else {
|
|
Builder.SetInsertPoint(UserInstr);
|
|
}
|
|
|
|
if (auto *InstrToGEPify = dyn_cast<Instruction>(TheUseToGEPify->get()))
|
|
if (llvm::DebugLoc DebugLocation = InstrToGEPify->getDebugLoc())
|
|
Builder.SetCurrentDebugLocation(DebugLocation);
|
|
|
|
// The other arguments are the indices in IndexVector
|
|
for (auto &Group : llvm::enumerate(GEPArgs.IndexVector)) {
|
|
const auto &[Index, AggregateTy] = Group.value();
|
|
const auto &[ConstantIndex, InductionVariables] = Index;
|
|
revng_assert(AggregateTy == AggregateKind::Array
|
|
or InductionVariables.empty());
|
|
|
|
Value *IndexValue = nullptr;
|
|
if (InductionVariables.empty() or not ConstantIndex.isNullValue()) {
|
|
auto *Int64Type = llvm::IntegerType::get(Context, 64 /*NumBits*/);
|
|
IndexValue = ConstantInt::get(Int64Type, ConstantIndex);
|
|
}
|
|
|
|
for (const auto &[Coefficient, InductionVariable] : InductionVariables) {
|
|
Value *Addend = InductionVariable;
|
|
if (not Coefficient->isOne()) {
|
|
auto *
|
|
CoefficientConstant = ConstantInt::get(InductionVariable->getType(),
|
|
Coefficient->getZExtValue());
|
|
Addend = Builder.CreateMul(CoefficientConstant, InductionVariable);
|
|
}
|
|
|
|
auto AddendBitWidth = Addend->getType()->getIntegerBitWidth();
|
|
revng_assert(AddendBitWidth);
|
|
|
|
if (Group.index() == 0 and AddendBitWidth != 64) {
|
|
auto *Int64Type = llvm::IntegerType::getIntNTy(Addend->getContext(),
|
|
64);
|
|
Addend = Builder.CreateZExtOrTrunc(Addend, Int64Type);
|
|
}
|
|
|
|
if (IndexValue) {
|
|
|
|
if (auto BitWidth = IndexValue->getType()->getIntegerBitWidth();
|
|
BitWidth > Addend->getType()->getIntegerBitWidth())
|
|
Addend = Builder.CreateZExt(Addend, IndexValue->getType());
|
|
|
|
IndexValue = Builder.CreateAdd(Addend, IndexValue);
|
|
} else {
|
|
IndexValue = Addend;
|
|
}
|
|
}
|
|
|
|
Args.push_back(IndexValue);
|
|
}
|
|
|
|
llvm::CallInst *ModelGEPRef = Builder.CreateCall(ModelGEPFunction, Args);
|
|
|
|
// If there is a remaining offset, we are returning something more
|
|
// similar to a pointer than the actual value
|
|
auto AddrOfReturnedType = Mismatched.isZero() ? UseType : PtrSizedInteger;
|
|
|
|
// Inject a call to AddressOf
|
|
auto *AddressOfFunctionType = getAddressOfType(AddrOfReturnedType,
|
|
PtrSizedInteger);
|
|
auto *AddressOfFunction = AddressOfPool.get({ AddrOfReturnedType,
|
|
PtrSizedInteger },
|
|
AddressOfFunctionType,
|
|
"AddressOf");
|
|
auto *Cached = TypeArgCache.get(*AccessedType, M);
|
|
Value *ModelGEPPtr = Builder.CreateCall(AddressOfFunction,
|
|
{ Cached, ModelGEPRef });
|
|
|
|
if (not Mismatched.isZero()) {
|
|
// If the GEPArgs have a RestOff that is strictly positive, we have to
|
|
// inject the remaining part of the pointer arithmetic as normal sums
|
|
auto GEPResultBitWidth = ModelGEPPtr->getType()->getIntegerBitWidth();
|
|
APInt OffsetToAdd = MismatchedOffset.zextOrTrunc(GEPResultBitWidth);
|
|
if (not OffsetToAdd.isNullValue()) {
|
|
ModelGEPPtr = Builder.CreateAdd(ModelGEPPtr,
|
|
ConstantInt::get(Context, OffsetToAdd));
|
|
}
|
|
for (const auto &[Coefficient, Index] : MismatchedIndices) {
|
|
ModelGEPPtr = Builder.CreateAdd(ModelGEPPtr,
|
|
Builder.CreateMul(Index, Coefficient));
|
|
}
|
|
|
|
if (UseType->isPointerTy()) {
|
|
// Convert the `AddressOf` result to a pointer in the IR if needed
|
|
ModelGEPPtr = Builder.CreateIntToPtr(ModelGEPPtr, UseType);
|
|
} else if (UseType != ModelGEPPtr->getType() and UseType->isIntegerTy()) {
|
|
ModelGEPPtr = Builder.CreateZExt(ModelGEPPtr, UseType);
|
|
}
|
|
|
|
revng_assert(UseType == ModelGEPPtr->getType());
|
|
}
|
|
|
|
// Finally, replace the use to gepify with the call to the address of
|
|
// modelGEP, plus the potential arithmetic we've just build.
|
|
TheUseToGEPify->set(ModelGEPPtr);
|
|
|
|
revng_log(ModelGEPLog,
|
|
" `-> replaced with: " << dumpToString(ModelGEPPtr));
|
|
|
|
if (IsPhi)
|
|
PhiIncomingsMaps[{ UserInstr, V }] = ModelGEPPtr;
|
|
|
|
Changed = true;
|
|
}
|
|
|
|
revng::verify(F.getParent());
|
|
|
|
return Changed;
|
|
}
|
|
|
|
char MakeModelGEPPass::ID = 0;
|
|
|
|
using Pass = MakeModelGEPPass;
|
|
static RegisterPass<Pass> X("make-model-gep",
|
|
"Pass that transforms address arithmetic into "
|
|
"calls to ModelGEP ",
|
|
false,
|
|
false);
|