Files
revng-revng/lib/IRCanonicalization/MakeModelGEPPass.cpp
T
2023-04-14 14:54:14 +02:00

2275 lines
86 KiB
C++

//
// Copyright rev.ng Labs Srl. See LICENSE.md for details.
//
#include <limits>
#include "llvm/ADT/APInt.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/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/raw_ostream.h"
#include "revng/ABI/FunctionType/Layout.h"
#include "revng/ADT/RecursiveCoroutine.h"
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
#include "revng/EarlyFunctionAnalysis/FunctionMetadataCache.h"
#include "revng/Model/Architecture.h"
#include "revng/Model/Binary.h"
#include "revng/Model/LoadModelPass.h"
#include "revng/Model/Qualifier.h"
#include "revng/Model/Type.h"
#include "revng/Model/TypeKind.h"
#include "revng/Model/VerifyHelper.h"
#include "revng/Support/Debug.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/YAMLTraits.h"
#include "revng-c/InitModelTypes/InitModelTypes.h"
#include "revng-c/Support/FunctionTags.h"
#include "revng-c/Support/IRHelpers.h"
#include "revng-c/Support/ModelHelpers.h"
using llvm::AnalysisUsage;
using llvm::APInt;
using llvm::Argument;
using llvm::BinaryOperator;
using llvm::CallInst;
using llvm::cast;
using llvm::Constant;
using llvm::ConstantExpr;
using llvm::ConstantInt;
using llvm::dyn_cast;
using llvm::FunctionPass;
using llvm::GlobalVariable;
using llvm::Instruction;
using llvm::IRBuilder;
using llvm::isa;
using llvm::LLVMContext;
using llvm::LoadInst;
using llvm::PHINode;
using llvm::RegisterPass;
using llvm::ReturnInst;
using llvm::ReversePostOrderTraversal;
using llvm::SmallVector;
using llvm::StoreInst;
using llvm::Use;
using llvm::User;
using llvm::Value;
using model::CABIFunctionType;
using model::Qualifier;
using model::RawFunctionType;
using ModelTypesMap = std::map<const llvm::Value *, const model::QualifiedType>;
static Logger<> ModelGEPLog{ "make-model-gep" };
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>();
AU.addRequired<FunctionMetadataCachePass>();
}
};
// We're trying to build a GEP summation in the form:
// BaseAddress + sum( const_i * index_i)
// where BaseAddress is an llvm::Value, const_i are llvm::ConstantInt, and
// index_i are llvm::Values.
// This struct represent an element of the summation: const_i * index_i
struct ModelGEPSummationElement {
ConstantInt *Coefficient = nullptr;
Value *Index = nullptr;
static bool isValid(const ModelGEPSummationElement &A) {
return A.Coefficient != nullptr and A.Index != nullptr;
}
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "ModelGEPSummationElement {\nCofficient:\n";
if (Coefficient)
OS << Coefficient->getValue().toString(10, true);
else
OS << "nullptr";
OS << "\nIndex:\n";
if (Index)
Index->print(OS);
else
OS << "nullptr";
OS << "\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
using GEPSummationVector = SmallVector<ModelGEPSummationElement, 4>;
struct TypedBaseAddress {
model::QualifiedType Type = {};
Value *Address = nullptr;
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "TypedBaseAddress{\nType:\n";
serialize(OS, Type);
OS << "\nAddress: ";
if (Address)
Address->print(OS);
else
OS << "nullptr";
OS << "\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
// This struct represents an expression of the form:
// BaseAddress + sum( const_i * index_i)
struct ModelGEPSummation {
// If this has nullptr Address it means this summation does not represent an
// address, but simply a summation of offsets.
TypedBaseAddress BaseAddress = {};
// If this is empty it means a zero offset.
GEPSummationVector Summation = {};
bool isAddress() const { return BaseAddress.Address != nullptr; }
bool isValid() const {
return llvm::all_of(Summation, ModelGEPSummationElement::isValid);
}
static ModelGEPSummation invalid() {
return ModelGEPSummation{
// The base address is unknown
.BaseAddress = TypedBaseAddress{ .Type = {}, .Address = nullptr },
// The summation has only one element, which is not valid, because it
// does
// not have a valid Index nor a valid Coefficient.
.Summation = { ModelGEPSummationElement{ .Coefficient = nullptr,
.Index = nullptr } }
};
}
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "ModelGEPSummation {\nBaseAddress: ";
BaseAddress.dump(OS);
OS << "\nSummation: {\n";
for (const auto &SumElem : Summation) {
SumElem.dump(OS);
OS << '\n';
}
OS << "}\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
using UseGEPSummationMap = std::map<Use *, ModelGEPSummation>;
struct IRAccessPattern {
APInt BaseOffset = APInt(/*NumBits*/ 64, /*Value*/ 0);
GEPSummationVector Indices = {};
std::optional<model::QualifiedType> PointeeType = std::nullopt;
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "IRAccessPattern {\nBaseOffset: " << BaseOffset.toString(10, true)
<< "\nIndices = {";
for (const auto &I : Indices) {
OS << "\n";
I.dump(OS);
}
OS << "}\nPointeeType: ";
if (PointeeType.has_value())
serialize(OS, PointeeType.value());
else
OS << "std::nullopt";
OS << "\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
using UseTypeMap = std::map<llvm::Use *, model::QualifiedType>;
static IRAccessPattern
computeIRAccessPattern(FunctionMetadataCache &Cache,
const Use &U,
const ModelGEPSummation &GEPSum,
const model::Binary &Model,
const ModelTypesMap &PointerTypes,
const UseTypeMap &GEPifiedUseTypes) {
using namespace model;
revng_assert(GEPSum.isAddress());
// First, prepare the BaseOffset and the Indices for the IRAccessPattern.
GEPSummationVector IRPatternIndices;
APInt BaseOff = APInt(/*NumBits*/ 64, /*Value*/ 0);
// Accumulate all the constant SumElements into BaseOff, and all the others in
// IRPatternIndices.
for (const auto &SumElement : GEPSum.Summation) {
const auto &[Coeff, Idx] = SumElement;
revng_assert(Coeff->getValue().isNonNegative());
// If this SumElement is a constant, update the BaseOff
if (const auto &ConstIdx = dyn_cast<ConstantInt>(Idx)) {
APInt Idx = ConstIdx->getValue();
revng_assert(Coeff->getValue().isOneValue());
revng_assert(Idx.isStrictlyPositive());
revng_assert(Idx.getActiveBits() <= BaseOff.getBitWidth());
BaseOff += Idx.zextOrTrunc(BaseOff.getBitWidth());
} else {
// Otherwise append the index to IRPatternIndices
IRPatternIndices.push_back(SumElement);
}
}
// Sort the IRPatternIndices so that strided accesses with larger strides come
// first.
const auto HasLargerStride = [](const ModelGEPSummationElement &LHS,
const ModelGEPSummationElement &RHS) {
return LHS.Coefficient->getValue().ugt(RHS.Coefficient->getValue());
};
llvm::sort(IRPatternIndices, HasLargerStride);
// Now we're ready to initialize the IRAccessPattern
IRAccessPattern IRPattern{ .BaseOffset = BaseOff.zextOrSelf(64),
.Indices = IRPatternIndices,
// Intially PointeeType is set to None, then we
// fill it if in some special cases where we have
// interesting information on the pointee
.PointeeType = std::nullopt };
// The IRAccessPattern we've just initialized is not necessarily complete now.
// We want to look at the user of U, to see if it gives us more information
// about the PointeeType.
if (auto *UserInstr = dyn_cast<Instruction>(U.getUser())) {
if (auto *Load = dyn_cast<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
revng_assert(Load->getType()->isIntOrPtrTy());
const llvm::DataLayout &DL = UserInstr->getModule()->getDataLayout();
auto PointeeSize = DL.getTypeAllocSize(Load->getType());
model::QualifiedType QPointee;
auto *PtrOp = Load->getPointerOperand();
auto *PtrOpUse = &Load->getOperandUse(Load->getPointerOperandIndex());
if (auto It = PointerTypes.find(PtrOp); It != PointerTypes.end()) {
QPointee = dropPointer(It->second);
}
if (auto It = GEPifiedUseTypes.find(PtrOpUse);
It != GEPifiedUseTypes.end()) {
QPointee = dropPointer(It->second);
} else {
model::TypePath
Pointee = Model.getPrimitiveType(model::PrimitiveTypeKind::Generic,
PointeeSize);
QPointee = model::QualifiedType(Pointee, {});
}
revng_log(ModelGEPLog, "QPointee: " << serializeToString(QPointee));
IRPattern.PointeeType = QPointee;
} else if (auto *Store = dyn_cast<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() == StoreInst::getPointerOperandIndex()) {
revng_log(ModelGEPLog, "Use is pointer operand");
auto *Stored = Store->getValueOperand();
revng_assert(Stored->getType()->isIntOrPtrTy());
const llvm::DataLayout &DL = UserInstr->getModule()->getDataLayout();
unsigned long PointeeSize = DL.getTypeAllocSize(Stored->getType());
model::TypePath
Pointee = Model.getPrimitiveType(model::PrimitiveTypeKind::Generic,
PointeeSize);
model::QualifiedType Generic = model::QualifiedType(Pointee, {});
model::QualifiedType QPointee = Generic;
auto *PtrOp = Store->getPointerOperand();
auto *PtrOpUse = &Store->getOperandUse(Store->getPointerOperandIndex());
if (auto It = PointerTypes.find(PtrOp); It != PointerTypes.end()) {
QPointee = dropPointer(It->second);
if (QPointee.is(model::TypeKind::StructType)
or QPointee.is(model::TypeKind::UnionType)) {
QPointee = Generic;
}
}
if (auto It = GEPifiedUseTypes.find(PtrOpUse);
It != GEPifiedUseTypes.end()) {
QPointee = dropPointer(It->second);
if (QPointee.is(model::TypeKind::StructType)
or QPointee.is(model::TypeKind::UnionType)) {
QPointee = Generic;
}
}
revng_log(ModelGEPLog, "QPointee: " << serializeToString(QPointee));
IRPattern.PointeeType = QPointee;
} else {
revng_log(ModelGEPLog, "Use is pointer operand");
}
} else if (auto *Ret = dyn_cast<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);
revng_assert(MF);
const auto Layout = abi::FunctionType::Layout::make(MF->Prototype());
bool HasNoReturnValues = (Layout.ReturnValues.empty()
and not Layout.returnsAggregateType());
const model::QualifiedType *SingleReturnType = nullptr;
if (Layout.returnsAggregateType()) {
SingleReturnType = &Layout.Arguments[0].Type;
} else if (Layout.ReturnValues.size() == 1) {
SingleReturnType = &Layout.ReturnValues[0].Type;
}
// If the callee function does not return anything, skip to the next
// instruction.
if (HasNoReturnValues) {
revng_log(ModelGEPLog, "Does not return values in the model. Skip ...");
revng_assert(not Ret->getReturnValue());
} else if (SingleReturnType != nullptr) {
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 (SingleReturnType->isPointer()) {
auto _ = LoggerIndent(ModelGEPLog);
revng_log(ModelGEPLog, "llvm::ReturnInst: " << dumpToString(Ret));
revng_log(ModelGEPLog,
"Pointee: model::QualifiedType: "
<< serializeToString(*SingleReturnType));
IRPattern.PointeeType = dropPointer(*SingleReturnType);
}
} else {
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 = cast<CallInst>(RetVal)->getCalledFunction();
revng_assert(FunctionTags::StructInitializer.isTagOf(Returned));
}
} else if (auto *Call = dyn_cast<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.PointeeType
revng_log(ModelGEPLog, "Call");
const llvm::Function *CalledF = Call->getCalledFunction();
if (CalledF and FunctionTags::StructInitializer.isTagOf(CalledF)) {
// special case for struct initializers
unsigned ArgNum = Call->getArgOperandNo(&U);
const model::Function *CalledFType = llvmToModelFunction(Model,
*CalledF);
const model::Type *CalledPrototype = CalledFType->Prototype()
.getConst();
if (auto *RFT = dyn_cast<RawFunctionType>(CalledPrototype)) {
revng_log(ModelGEPLog, "Has RawFunctionType 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);
model::QualifiedType
RetTy = std::next(RFT->ReturnValues().begin(), ArgNum)->Type();
if (RetTy.isPointer()) {
model::QualifiedType Pointee = dropPointer(RetTy);
revng_log(ModelGEPLog, "Pointee: " << serializeToString(Pointee));
IRPattern.PointeeType = Pointee;
}
} else if (auto *CFT = dyn_cast<CABIFunctionType>(CalledPrototype)) {
revng_log(ModelGEPLog, "Has CABIFunctionType prototype.");
// TODO: we haven't handled return values of CABIFunctions yet
revng_abort();
} else {
revng_abort("Function should have RawFunctionType or "
"CABIFunctionType");
}
} else if (FunctionTags::CallToLifted.isTagOf(Call)) {
auto Proto = Cache.getCallSitePrototype(Model, Call);
revng_assert(Proto.isValid());
if (const auto *RFT = dyn_cast<RawFunctionType>(Proto.get())) {
auto MoreIndent = LoggerIndent(ModelGEPLog);
auto ModelArgSize = RFT->Arguments().size();
revng_assert(RFT->StackArgumentsType().Qualifiers().empty());
auto &Type = RFT->StackArgumentsType().UnqualifiedType();
revng_assert((ModelArgSize == Call->arg_size() - 1 and Type.isValid())
or ModelArgSize == Call->arg_size());
revng_log(ModelGEPLog, "model::RawFunctionType");
auto _ = LoggerIndent(ModelGEPLog);
if (not Call->isCallee(&U)) {
unsigned ArgOpNum = Call->getArgOperandNo(&U);
revng_log(ModelGEPLog, "ArgOpNum: " << ArgOpNum);
revng_log(ModelGEPLog, "ArgOperand: " << U.get());
model::QualifiedType ArgTy;
if (ArgOpNum >= ModelArgSize) {
// The only case in which the argument's index can be greater than
// the number of arguments in the model is for RawFunctionType
// functions that have 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.
ArgTy = RFT->StackArgumentsType();
revng_assert(ArgTy.UnqualifiedType().isValid());
} else {
auto ArgIt = std::next(RFT->Arguments().begin(), ArgOpNum);
ArgTy = ArgIt->Type();
}
revng_log(ModelGEPLog,
"model::QualifiedType: " << serializeToString(ArgTy));
if (ArgTy.isPointer()) {
model::QualifiedType Pointee = dropPointer(ArgTy);
revng_log(ModelGEPLog, "Pointee: " << serializeToString(Pointee));
IRPattern.PointeeType = Pointee;
}
} else {
revng_log(ModelGEPLog, "IsCallee");
}
} else if (const auto *CFT = dyn_cast<CABIFunctionType>(Proto.get())) {
auto MoreIndent = LoggerIndent(ModelGEPLog);
revng_assert(CFT->Arguments().size() == Call->arg_size());
revng_log(ModelGEPLog, "model::CABIFunctionType");
auto _ = LoggerIndent(ModelGEPLog);
if (not Call->isCallee(&U)) {
unsigned ArgOpNum = Call->getArgOperandNo(&U);
revng_log(ModelGEPLog, "ArgOpNum: " << ArgOpNum);
revng_log(ModelGEPLog, "ArgOperand: " << U.get());
model::QualifiedType ArgTy = CFT->Arguments().at(ArgOpNum).Type();
revng_log(ModelGEPLog,
"model::QualifiedType: " << serializeToString(ArgTy));
if (ArgTy.isPointer()) {
model::QualifiedType Pointee = dropPointer(ArgTy);
revng_log(ModelGEPLog, "Pointee: " << serializeToString(Pointee));
IRPattern.PointeeType = Pointee;
}
} else {
revng_log(ModelGEPLog, "IsCallee");
}
} else {
revng_abort("Function should have RawFunctionType or "
"CABIFunctionType");
}
}
} else if (auto *PHI = dyn_cast<PHINode>(UserInstr)) {
}
}
return IRPattern;
}
struct ArrayInfo {
APInt Stride = APInt(/*NumBits*/ 64, /*Value*/ 0);
APInt NumElems = APInt(/*NumBits*/ 64, /*Value*/ 0);
bool operator==(const ArrayInfo &) const = default;
bool operator<(const ArrayInfo &Other) const {
if (Stride.ult(Other.Stride))
return true;
if (Stride.ugt(Other.Stride))
return false;
if (NumElems.ult(Other.NumElems))
return true;
return false;
}
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "ArrayInfo { .Stride = " << Stride.toString(10, true)
<< ", .NumElems = " << NumElems.toString(10, true) << "}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
using ArrayInfoVector = SmallVector<ArrayInfo, 4>;
struct TypedAccessPattern {
APInt BaseOffset = APInt(/*NumBits*/ 64, /*Value*/ 0);
ArrayInfoVector Arrays = {};
model::QualifiedType AccessedType = {};
bool operator==(const TypedAccessPattern &) const = default;
bool operator<(const TypedAccessPattern &Other) const {
if (BaseOffset.ult(Other.BaseOffset))
return true;
if (BaseOffset.ugt(Other.BaseOffset))
return false;
if (Arrays < Other.Arrays)
return true;
if (Other.Arrays < Arrays)
return false;
return AccessedType < Other.AccessedType;
}
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "TypedAccessPattern {\nBaseOffset: " << BaseOffset.toString(10, true)
<< "\n";
OS << "Arrays: {";
for (const auto &AI : Arrays) {
OS << "\n";
AI.dump(OS);
}
OS << "\n}\nType: ";
serialize(OS, AccessedType);
OS << "\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
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";
}
}
struct ChildInfo {
size_t ConstantIndex = 0ULL;
llvm::Value *InductionVariable = nullptr;
AggregateKind Type = AggregateKind::Invalid;
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "ChildInfo{\nConstantIndex: " << ConstantIndex
<< "\nType: " << toString(Type) << "\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
using ChildIndexVector = SmallVector<ChildInfo, 4>;
class DifferenceScore {
// Among two DifferenceScore, the one with PerfectTypeMatch set to true is
// always the lowest.
bool PerfectTypeMatch = false;
// Boolean to mark out-of-range accesses
bool InRange = false;
// Higher Difference are for stuff that is farther apart from a perfect match
// from the beginning of the type.
size_t Difference = std::numeric_limits<size_t>::max();
// Among two DifferenceScore, the one with ExactSize is considered closer.
bool ExactSize = false;
// This field represents how deep the type system was traversed to compute the
// score. Scores with a higher depth are considered better (so lower
// difference) because it means that the type system was traversed deeply.
size_t Depth = std::numeric_limits<size_t>::min();
public:
constexpr std::strong_ordering
operator<=>(const DifferenceScore &Other) const {
// If only one of the two is a perfect match, the difference is always
// lower.
if (PerfectTypeMatch != Other.PerfectTypeMatch)
return PerfectTypeMatch ? std::strong_ordering::less :
std::strong_ordering::greater;
// Here both are perfect matches or none is.
// If one out of range, the difference is always higher
if (InRange != Other.InRange)
return InRange ? std::strong_ordering::less :
std::strong_ordering::greater;
// Here both are in range or none is.
// If the Difference is not the same, one of the two is closer to exact, so
// we give precedence to that.
if (auto Cmp = Difference <=> Other.Difference; Cmp != 0)
return Cmp;
// Here both have the same difference, e.g. they reach the same offset.
// If the ExactSize is not the same, we give precedence to the one with
// ExactSize, which means that it's closer in size to the
// perfect match.
if (ExactSize != Other.ExactSize)
return ExactSize ? std::strong_ordering::less :
std::strong_ordering::greater;
// Notice that in the following line the terms are inverted, because lower
// depth needs to be scored "better" (so lower difference).
return Other.Depth <=> Depth;
}
constexpr bool operator==(const DifferenceScore &Other) const = default;
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "DifferenceScore { .PerfectTypeMatch = "
<< (PerfectTypeMatch ? "true" : "false")
<< ", .Difference = " << Difference
<< ", .ExactSize = " << (ExactSize ? "true" : "false")
<< ", .Depth = " << Depth
<< ", .InRange = " << (InRange ? "true" : "false") << "}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
static constexpr DifferenceScore max() { return DifferenceScore{}; }
static constexpr DifferenceScore min() {
DifferenceScore Result;
Result.PerfectTypeMatch = true;
Result.InRange = true;
Result.Difference = std::numeric_limits<size_t>::min();
Result.ExactSize = true;
Result.Depth = std::numeric_limits<size_t>::max();
return Result;
}
static constexpr DifferenceScore
nestedOutOfBound(size_t DiffScore, size_t Depth) {
DifferenceScore Result;
Result.PerfectTypeMatch = false;
Result.InRange = false;
Result.Difference = DiffScore;
Result.ExactSize = false;
Result.Depth = Depth;
return Result;
}
static constexpr DifferenceScore outOfBound(size_t DiffScore) {
return nestedOutOfBound(DiffScore, std::numeric_limits<size_t>::min());
}
static constexpr DifferenceScore
inRange(bool PerfectMatch, uint64_t DiffScore, bool SameSize, size_t Depth) {
DifferenceScore Result;
Result.PerfectTypeMatch = PerfectMatch;
Result.InRange = true;
Result.Difference = DiffScore;
Result.ExactSize = SameSize;
Result.Depth = Depth;
return Result;
}
};
static constexpr auto nestedOutOfBound = &DifferenceScore::nestedOutOfBound;
static constexpr auto outOfBound = &DifferenceScore::outOfBound;
// Some static asserts about ordering of DifferenceScore
//
// Basically we have:
// 1) outOfBound(x) == nestedOutOfBound(x, 0)
// 2) min < nestedOutOfBound(x, y) <= max
// 3) at the previous point, the <= with max is == when:
// 3.1) y == 0 and
// 3.2) x == std::numeric_limits<size_t>::max()
static_assert(DifferenceScore::max() == nestedOutOfBound(SIZE_MAX, 0));
static_assert(DifferenceScore::max() == outOfBound(SIZE_MAX));
static_assert(DifferenceScore::min() < DifferenceScore::max());
static_assert(outOfBound(0) == nestedOutOfBound(0, 0));
static_assert(outOfBound(1) == nestedOutOfBound(1, 0));
static_assert(outOfBound(1) > nestedOutOfBound(1, 2));
static_assert(outOfBound(5) > nestedOutOfBound(1, 2));
static_assert(outOfBound(0) < nestedOutOfBound(1, 2));
static_assert(DifferenceScore::min() < nestedOutOfBound(1, 1));
static_assert(DifferenceScore::max() > nestedOutOfBound(1, 1));
static_assert(DifferenceScore::min() < outOfBound(0));
static_assert(DifferenceScore::max() > outOfBound(0));
struct ScoredIndices {
// The score is optional, nullopt means that the difference score is infinity
DifferenceScore Score = DifferenceScore::max();
ChildIndexVector Indices = {};
static ScoredIndices outOfBound(size_t DiffScore) {
return ScoredIndices{ .Score = DifferenceScore::outOfBound(DiffScore),
.Indices{} };
}
static ScoredIndices
nestedOutOfBound(size_t DiffScore, size_t Depth, ChildIndexVector &&Indices) {
revng_assert(Depth <= Indices.size());
if (Depth < Indices.size())
Indices.resize(Depth);
return ScoredIndices{ .Score = DifferenceScore::nestedOutOfBound(DiffScore,
Depth),
.Indices = std::move(Indices) };
}
};
using TAPIndicesPair = std::pair<TypedAccessPattern, ChildIndexVector>;
static ScoredIndices differenceScore(const model::QualifiedType &BaseType,
const TAPIndicesPair &TAPWithIndices,
const IRAccessPattern &IRAP,
model::VerifyHelper &VH) {
const auto &[TAP, ChildIndices] = TAPWithIndices;
ChildIndexVector ResultIndices = ChildIndices;
APInt RestOff = IRAP.BaseOffset;
size_t BaseSize = *BaseType.size(VH);
if (IRAP.BaseOffset.uge(BaseSize))
return ScoredIndices::outOfBound(RestOff.getZExtValue());
revng_assert(TAP.BaseOffset.ult(BaseSize));
revng_assert((TAP.BaseOffset + *TAP.AccessedType.size(VH)).ule(BaseSize));
auto ArrayInfoIt = TAP.Arrays.begin();
auto ArrayInfoEnd = TAP.Arrays.end();
auto IRAPIndicesIt = IRAP.Indices.begin();
auto IRAPIndicesEnd = IRAP.Indices.end();
model::QualifiedType NestedType = BaseType;
size_t Depth = 0;
for (auto &ChildID : ResultIndices) {
model::QualifiedType Normalized = peelConstAndTypedefs(NestedType);
// Should not be a pointer, because pointers don't have children on the
// type system, which means that we shouldn't have a ChildId at this
// point.
revng_assert(not Normalized.isPointer());
switch (ChildID.Type) {
case Struct: {
revng_assert(not Normalized.isArray());
auto *Const = Normalized.UnqualifiedType().getConst();
auto *S = cast<model::StructType>(Const);
revng_assert(not ChildID.InductionVariable);
size_t FieldOffset = ChildID.ConstantIndex;
// If the RestOff is less than the field offset, it means that the IRAP
// does not have enough offset to reach the field of the struct that is
// required from the TAPWithIndices.
// This should never happen.
revng_assert(RestOff.uge(FieldOffset));
RestOff -= FieldOffset;
NestedType = S->Fields().at(FieldOffset).Type();
} break;
case Union: {
revng_assert(not Normalized.isArray());
auto *Const = Normalized.UnqualifiedType().getConst();
auto *U = cast<model::UnionType>(Const);
revng_assert(not ChildID.InductionVariable);
size_t FieldID = ChildID.ConstantIndex;
NestedType = U->Fields().at(FieldID).Type();
} break;
case Array: {
revng_assert(Normalized.isArray());
revng_assert(ArrayInfoIt != ArrayInfoEnd);
const auto ArrayQualEnd = Normalized.Qualifiers().end();
const auto ArrayQualIt = llvm::find_if(Normalized.Qualifiers(),
model::Qualifier::isArray);
revng_assert(ArrayQualIt != ArrayQualEnd);
revng_assert(ArrayQualIt->Size() == ArrayInfoIt->NumElems);
// Compute the index of the accessed element of the array.
auto ArrayStride = ArrayInfoIt->Stride;
auto MaxBitWidth = std::max(RestOff.getBitWidth(),
ArrayStride.getBitWidth());
APInt ElemIndex = APInt(MaxBitWidth, 0);
APInt OffInElem = APInt(MaxBitWidth, 0);
APInt::udivrem(RestOff.zextOrTrunc(MaxBitWidth),
ArrayStride.zextOrTrunc(MaxBitWidth),
ElemIndex,
OffInElem);
if (ElemIndex.uge(ArrayInfoIt->NumElems)) {
// If IRAP is trying to access an element that is larger than the
// array size, we have to bail out, marking this as out of bound.
return ScoredIndices::nestedOutOfBound(RestOff.getZExtValue(),
Depth,
std::move(ResultIndices));
}
if (IRAPIndicesIt == IRAPIndicesEnd) {
// If the IRAP does not have strided accesses anymore, we have to
// perform the access at a constant offset.
// Assert that there's no Induction Variable.
revng_assert(not ChildID.InductionVariable);
} else {
// Here we still hav some IRAPIndices, so we're modeling a strided
// access and we must have a variable Index.
const auto &[IRCoefficient, IRIndex] = *IRAPIndicesIt;
revng_assert(IRIndex, "IRAPIndex not present");
revng_assert(not llvm::isa<Constant>(IRIndex),
"IRAPIndex does not represent a strided access");
// The IRAccessPattern should always have a Coefficient that is lower
// than or equal than the array stride, otherwise we'de be modeling a
// traversal that breakes type safety, which is what we're trying to
// avoid.
auto &IRCoefficientVal = IRCoefficient->getValue();
auto IRCoefficientResized = IRCoefficientVal.zextOrTrunc(MaxBitWidth);
auto ArrayStrideResized = ArrayStride.zextOrTrunc(MaxBitWidth);
revng_assert(IRCoefficientResized.ule(ArrayStrideResized));
if (IRCoefficientResized == ArrayStrideResized) {
// If we're traversing an array with the same stride on the IR and on
// the model, then the induction variable must be the same.
revng_assert(IRIndex == ChildID.InductionVariable);
} else {
// If we're traversing an array with a smaller stride on the IR,
// than the model, then the InductionVariable on the IR should be
// ignored, and we should be accessing the model array at a fixed
// index, without an induction variable on the model.
revng_assert(not ChildID.InductionVariable);
}
++IRAPIndicesIt;
}
RestOff = OffInElem;
NestedType = model::QualifiedType(Normalized.UnqualifiedType(),
{ std::next(ArrayQualIt),
ArrayQualEnd });
++ArrayInfoIt;
} break;
default:
revng_abort();
}
// Increase the depth for each element in the type system that was traversed
// succesfully.
++Depth;
}
bool PerfectMatch = IRAP.PointeeType.has_value()
and IRAP.PointeeType.value() == TAP.AccessedType;
bool SameSize = false;
if (IRAP.PointeeType.has_value()) {
std::optional<uint64_t> TAPSize = TAP.AccessedType.trySize(VH);
std::optional<uint64_t> IRAPSize = IRAP.PointeeType.value().trySize(VH);
SameSize = TAPSize.value_or(0ull) == IRAPSize.value_or(0ull);
}
return ScoredIndices{
.Score = DifferenceScore::inRange(PerfectMatch,
RestOff.getZExtValue(),
SameSize,
Depth),
.Indices = std::move(ResultIndices),
};
}
struct ModelGEPArgs {
TypedBaseAddress BaseAddress = {};
ChildIndexVector IndexVector = {};
APInt RestOff;
model::QualifiedType PointeeType = {};
void dump(llvm::raw_ostream &OS) const debug_function {
OS << "ModelGEPArgs {\nBaseAddress:\n";
BaseAddress.dump(OS);
OS << "\nIndexVector: {";
for (const auto &C : IndexVector) {
OS << "\n";
C.dump(OS);
}
OS << "}\nRestOff: " << RestOff << "\nPointeeType: ";
serialize(OS, PointeeType);
OS << "\n}";
}
void dump() const debug_function { dump(llvm::dbgs()); }
};
static std::optional<model::QualifiedType>
getType(const model::QualifiedType &BaseType,
const ChildIndexVector &IndexVector,
APInt RestOff,
model::VerifyHelper &VH) {
std::optional<model::QualifiedType> CurrType = std::nullopt;
if (RestOff.ugt(0))
return CurrType;
CurrType = BaseType;
for (const auto &[FixedIndex, InductionVariable, AggregateType] :
IndexVector) {
switch (AggregateType) {
case AggregateKind::Struct: {
CurrType = peelConstAndTypedefs(CurrType.value());
auto *S = cast<model::StructType>(CurrType->UnqualifiedType().getConst());
revng_assert(not InductionVariable);
CurrType = S->Fields().at(FixedIndex).Type();
} break;
case AggregateKind::Union: {
CurrType = peelConstAndTypedefs(CurrType.value());
auto *U = cast<model::UnionType>(CurrType->UnqualifiedType().getConst());
revng_assert(not InductionVariable);
CurrType = U->Fields().at(FixedIndex).Type();
} break;
case AggregateKind::Array: {
auto ArrayQualIt = CurrType->Qualifiers().begin();
do {
CurrType = peelConstAndTypedefs(CurrType.value());
ArrayQualIt = llvm::find_if(CurrType->Qualifiers(),
model::Qualifier::isArray);
// Assert that we're not skipping any pointer qualifier.
// That would mean that the GEPArgs.IndexVector is broken w.r.t. the
// GEPArgs.BaseAddress.
revng_assert(not std::any_of(CurrType->Qualifiers().begin(),
ArrayQualIt,
model::Qualifier::isPointer));
} while (ArrayQualIt == CurrType->Qualifiers().end());
revng_assert(ArrayQualIt->Size() > FixedIndex);
// For arrays we don't need to look at the value of the index, we just
// unwrap the array and go on.
CurrType = model::QualifiedType(CurrType->UnqualifiedType(),
{ std::next(ArrayQualIt),
CurrType->Qualifiers().end() });
} break;
default:
revng_abort();
}
}
return CurrType;
}
static DifferenceScore lowerBound(const model::QualifiedType &BaseType,
const IRAccessPattern &IRPattern,
model::VerifyHelper &VH) {
auto BaseSizeOrNone = static_cast<std::optional<size_t>>(BaseType.size(VH));
revng_assert(BaseSizeOrNone.has_value());
size_t BaseSize = BaseSizeOrNone.value();
revng_assert(BaseSize);
bool IRHasPointee = IRPattern.PointeeType.has_value();
auto PointeeSize = IRHasPointee ? *IRPattern.PointeeType->trySize(VH) : 0ULL;
if (IRPattern.BaseOffset.uge(BaseSize))
return DifferenceScore::outOfBound(IRPattern.BaseOffset.getZExtValue());
if ((IRPattern.BaseOffset + PointeeSize).ugt(BaseSize)) {
llvm::APInt HowMuchOut = IRPattern.BaseOffset + PointeeSize - BaseSize;
return DifferenceScore::outOfBound(HowMuchOut.getZExtValue());
}
// Assume we can find a perfect match with the same size unless we find strong
// evidence of the opposite.
bool PerfectMatch = true;
bool SameSize = true;
// If the IRPattern has not pointee information, we'll never reach a situation
// where SameSize nor PerfectMatch is true
if (not IRHasPointee) {
PerfectMatch = false;
SameSize = false;
}
return DifferenceScore::inRange(PerfectMatch,
/*DiffScore*/ 0,
SameSize,
/*Depth*/ std::numeric_limits<size_t>::max());
}
static RecursiveCoroutine<TAPIndicesPair>
computeBestTAP(model::QualifiedType BaseType,
const IRAccessPattern &IRPattern,
llvm::LLVMContext &Ctxt,
model::VerifyHelper &VH) {
revng_log(ModelGEPLog, "Computing Best TAP for IRAP: " << IRPattern);
revng_assert(not BaseType.is(model::TypeKind::RawFunctionType)
and not BaseType.is(model::TypeKind::CABIFunctionType));
TypedAccessPattern BaseTAP = {
// The BaseOffset is 0, since this TAP represents an access to the
// entire BaseType starting from BaseType itself.
.BaseOffset = APInt(/*NumBits*/ 64, /*Value*/ 0),
// We have no arrays info, since this TAP represents an access to the
// entire BaseType starting from BaseType itself.
.Arrays = {},
// The pointee is just the BaseType
.AccessedType = BaseType,
};
// Baseline TAP with empty ChildIndexVector, representing the access to the
// BaseType.
auto Result = std::make_pair(BaseTAP, ChildIndexVector{});
// Running variable to hold the best score. It will guide the branch-and-bound
// algorithm for computing the best TypedAccessPattern with indices.
// Compute the BaseScore e.g. the score of the traversal that ends at
// BaseType (which is basically a non-traversal). This is the baseline to
// compare with the scores of the children.
auto BestScore = DifferenceScore::max();
const auto &[BaseScore,
BaseIndices] = differenceScore(BaseType, Result, IRPattern, VH);
BestScore = std::move(BaseScore);
Result.second = std::move(BaseIndices);
// If we've reached a primitive type or an enum type we're done. The
// BaseScore computed above is enough and we don't need to traverse
// anything.
// The same holds if we've reached a pointer, because the pointee does not
// reside into the BaseType, it's only referenced by it.
if (BaseType.isPrimitive() or BaseType.isPointer()
or BaseType.is(model::TypeKind::EnumType))
rc_return Result;
// Here BaseType is either an array, a struct, a union, or a typedef.
// Unwrap const and typedefs, because ModelGEPs just see through them.
// Unwrapping const and typedefs can potentially lose information, but it's
// fine to do it here, because after traversing stuff we only ever use either
// the root type or the leaf type, and both are the same.
BaseType = peelConstAndTypedefs(BaseType);
// In all the other cases (arrays and const) we need to unwrap the first layer
// (qualifier) and keep looking for other TAPs that might be generated.
auto QBeg = BaseType.Qualifiers().begin();
auto QEnd = BaseType.Qualifiers().end();
if (QBeg != QEnd) {
revng_log(ModelGEPLog, "Array, look at elements");
auto ArrayIndent = LoggerIndent{ ModelGEPLog };
auto &ArrayQualifier = *QBeg;
revng_assert(model::Qualifier::isArray(ArrayQualifier));
auto ElementType = model::QualifiedType(BaseType.UnqualifiedType(),
{ std::next(QBeg), QEnd });
uint64_t NElems = ArrayQualifier.Size();
std::optional<uint64_t> MaybeElementTypeSize = ElementType.size(VH);
revng_assert(MaybeElementTypeSize.has_value()
and MaybeElementTypeSize.value());
uint64_t ElementSize = MaybeElementTypeSize.value();
uint64_t ArraySize = ElementSize * NElems;
if (IRPattern.BaseOffset.uge(ArraySize))
rc_return Result;
// Set up the IRPattern we need for computing scores in the inner array
// element. We initialize it to the IRPattern but we'll have to adjust it to
// peel away the array access.
revng_assert(IRPattern.BaseOffset.getBitWidth() == 64);
IRAccessPattern ElemIRPattern = IRPattern;
APInt APElementSize = APInt(/*NumBits*/ 64, /*value*/ ElementSize);
// Set up the auxiliary variables that will later be used for updating the
// result if the array traversal turns out to be the new best TAP.
// We compute them here, while also updating ElemIRPattern, because the two
// computations are related and we want to avoid redoing the work later on.
APInt FixedElementIndex = APInt(/*NumBits*/ 64, /*value*/ ElementSize);
llvm::Value *InductionVariable = nullptr;
if (not ElemIRPattern.Indices.empty()) {
// Unwrap the top layer of array indices, since we're traversing the
// array, but only do it if the current layer of array accesses we're
// peeling has a Coefficient that matches the element size of the array.
// If we unwrap an array traversal with non-constant index, that's
// the induction variable, and we have to track it.
const auto &[Coefficient, Index] = ElemIRPattern.Indices.front();
// Coefficient represents the stride of the pointer arithemtic pattern on
// the IR. If it's larger than ElementSize on the model it means that
// we're not accessing the array with an induction variable that
// increments one by one.
// This can happen in two scenarios:
// - wild access, that we want to discard
// - every n element, instead of every element, that we want to handle.
if (Coefficient->getValue().ugt(ElementSize)) {
revng_assert(Index);
APInt NumMultipleElements = APInt(/*NumBits*/ 64, /*value*/ 0);
APInt Remainder = APInt(/*NumBits*/ 64, /*value*/ 0);
APInt::udivrem(Coefficient->getValue().zextOrSelf(64),
APElementSize,
NumMultipleElements,
Remainder);
if (Remainder.getBoolValue()) {
// This is not accessing an exact size of elements at each stride.
// Just skip this.
rc_return Result;
} else {
// TDOO: in principle we'd like to be able to save the
// NumMultipleElement in the Result, so that we can emit array
// accesses in the form: array[constant + induction_var * multiplier].
rc_return Result;
}
} else {
revng_assert(not isa<ConstantInt>(Index));
if (Coefficient->getValue() == ElementSize) {
InductionVariable = Index;
ElemIRPattern.Indices.erase(ElemIRPattern.Indices.begin());
}
}
}
// Update ElemIRPattern.BaseOffset while also computing FixedElemIndex if
// necessary.
APInt::udivrem(IRPattern.BaseOffset,
APElementSize,
FixedElementIndex,
ElemIRPattern.BaseOffset);
DifferenceScore LowerBound = lowerBound(ElementType, ElemIRPattern, VH);
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
if (bool CanImprove = LowerBound <= BestScore; CanImprove) {
// We have estimated that accessing the element of the array might
// yield a better score.
// Let's compute the best TAP with indices, for accessing an element.
TAPIndicesPair ElementResult = rc_recur computeBestTAP(ElementType,
ElemIRPattern,
Ctxt,
VH);
// Now let's see if the best TAP of on the array element is actually
// better than the current best.
const auto &[ElementScore,
ElementIndices] = differenceScore(ElementType,
ElementResult,
ElemIRPattern,
VH);
revng_assert(ElementScore >= LowerBound);
// If it's better or equal, udpate the running best.
// The "or equal" is important, because we score better the traversals
// that go deeper in the type system.
if (auto ElementCmp = ElementScore <=> BestScore; ElementCmp <= 0) {
if (ElementCmp < 0) {
revng_log(ModelGEPLog, "New BestScore: " << ElementScore);
BestScore = ElementScore;
}
// Set Result to ElementResult. Then we'll have to update it with the
// information about the array we've just traversed.
Result = ElementResult;
// Build the ArrayInfo associated to the array we're handling, and
// prepend it to the Arrays of the Result, to represent the traversal
// of this array.
Result.first.Arrays.insert(Result.first.Arrays.begin(),
ArrayInfo{
.Stride = APInt(/*NumBits*/ 64,
/*Value*/ ElementSize),
.NumElems = APInt(/*NumBits*/ 64,
/*Value*/ NElems) });
// Build the child info associated to the array we're
// handling, and prepend it to the child ids in the Result.
Result.second
.insert(Result.second.begin(),
ChildInfo{ .ConstantIndex = FixedElementIndex.getZExtValue(),
.InductionVariable = InductionVariable,
.Type = AggregateKind::Array });
}
}
rc_return Result;
}
// We have no qualifiers here, just match struct and unions
const model::Type *BaseT = BaseType.UnqualifiedType().getConst();
switch (BaseT->Kind()) {
case model::TypeKind::StructType: {
revng_log(ModelGEPLog, "Struct, look at fields");
auto StructIndent = LoggerIndent{ ModelGEPLog };
const auto *S = cast<model::StructType>(BaseT);
auto FieldBegin = S->Fields().begin();
// Let's detect the leftmost field that starts later than the maximum offset
// reachable by the IRPattern. This is the first element that we don't want
// to compare, because it's not a valid traversal for the given IR pattern.
auto FieldIt = S->Fields().upper_bound(IRPattern.BaseOffset.getZExtValue());
enum {
InitiallyImproving,
StartedDegrading,
} Status = InitiallyImproving;
for (const model::StructField &Field :
llvm::reverse(llvm::make_range(FieldBegin, FieldIt))) {
revng_log(ModelGEPLog, "Field at offset: " << Field.Offset());
auto FieldIndent = LoggerIndent{ ModelGEPLog };
auto &FieldType = Field.Type();
const auto FieldOffset = Field.Offset();
revng_assert(IRPattern.BaseOffset.uge(FieldOffset));
IRAccessPattern FieldAccessPattern = IRPattern;
FieldAccessPattern.BaseOffset -= FieldOffset;
DifferenceScore LowerBound = lowerBound(FieldType,
FieldAccessPattern,
VH);
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
bool CanImprove = LowerBound <= BestScore;
// TODO: if this assertion never trigger, we could inject an early exit
// from this loop on struct fields, so that when we start degrading we
// just exit.
// Even better would be to actually verify that this cannot happen by
// design, and just remove the assertion and add the early exit.
revng_assert(not CanImprove or Status == InitiallyImproving);
if (not CanImprove) {
Status = StartedDegrading;
} else {
// We have estimated that accessing the struct field may yield a better
// score. Let's compute the best TAP with indices, for accessing the
// field.
TAPIndicesPair FieldResult = rc_recur computeBestTAP(FieldType,
FieldAccessPattern,
Ctxt,
VH);
// Now let's see if the best TAP of the field is actually better than
// the current best.
const auto &[FieldScore,
FieldIndices] = differenceScore(FieldType,
FieldResult,
FieldAccessPattern,
VH);
revng_assert(FieldScore >= LowerBound);
// If it's better or equal, udpate the running best.
// The "or equal" is important, because we score better the traversals
// that go deeper in the type system.
if (auto FieldCmp = FieldScore <=> BestScore; FieldCmp <= 0) {
if (FieldCmp < 0) {
revng_log(ModelGEPLog, "New BestScore: " << FieldScore);
BestScore = FieldScore;
}
// Set Result to FieldResult. Then we'll have to update it with the
// information about the struct we've just traversed.
Result = FieldResult;
// Fixup the base offset
Result.first.BaseOffset += FieldOffset;
// Build the child info associated to the array we're
// handling, and prepend it to the child ids in the Result.
Result.second.insert(Result.second.begin(),
ChildInfo{ .ConstantIndex = Field.Offset(),
.Type = AggregateKind::Struct });
}
}
}
} break;
case model::TypeKind::UnionType: {
revng_log(ModelGEPLog, "Union, look at fields");
auto UnionIndent = LoggerIndent{ ModelGEPLog };
const auto *U = cast<model::UnionType>(BaseT);
for (const model::UnionField &Field : U->Fields()) {
revng_log(ModelGEPLog, "Field ID: " << Field.Index());
auto FieldIndent = LoggerIndent{ ModelGEPLog };
auto &FieldType = Field.Type();
DifferenceScore LowerBound = lowerBound(FieldType, IRPattern, VH);
revng_log(ModelGEPLog, "lowerBound: " << LowerBound);
if (bool CanImprove = LowerBound <= BestScore; CanImprove) {
// We have estimated that accessing the union field may yield a better
// score. Let's compute the best TAP with indices, for accessing the
// field.
TAPIndicesPair FieldResult = rc_recur computeBestTAP(FieldType,
IRPattern,
Ctxt,
VH);
// Now let's see if the best TAP of the field is actually better than
// the current best.
const auto &[FieldScore, FieldIndices] = differenceScore(FieldType,
FieldResult,
IRPattern,
VH);
revng_assert(FieldScore >= LowerBound);
// If it's better or equal, udpate the running best.
// The "or equal" is important, because we score better the traversals
// that go deeper in the type system.
if (auto FieldCmp = FieldScore <=> BestScore; FieldCmp <= 0) {
if (FieldCmp < 0) {
revng_log(ModelGEPLog, "New BestScore: " << FieldScore);
BestScore = FieldScore;
}
// Set Result to FieldResult. Then we'll have to update it with the
// information about the union we've just traversed.
Result = FieldResult;
// Build the child info associated to the array we're
// handling, and prepend it to the child ids in the Result.
Result.second.insert(Result.second.begin(),
ChildInfo{ .ConstantIndex = Field.Index(),
.Type = AggregateKind::Union });
}
}
}
} break;
default:
revng_abort();
}
rc_return Result;
}
static ModelGEPArgs makeBestGEPArgs(const TypedBaseAddress &TBA,
const IRAccessPattern &IRPattern,
const model::Binary &Model,
model::VerifyHelper &VH) {
LLVMContext &Ctxt = TBA.Address->getContext();
revng_log(ModelGEPLog, "===============================");
revng_log(ModelGEPLog, "makeBestGEPArgs for TBA: " << TBA);
auto MakeBestGEPArgsIndent = LoggerIndent(ModelGEPLog);
const TAPIndicesPair BestTAPsWithIndices = computeBestTAP(TBA.Type,
IRPattern,
Ctxt,
VH);
const auto &[BestTAP, BestIndices] = BestTAPsWithIndices;
// Setup a vector of indices to fill up. Most of them will be copies
// straight from BestIndices, except for those representing array accesses,
// that will need to be filled up with the actual Value, representing the
// index of the array access.
ChildIndexVector Indices = {};
Indices.reserve(BestIndices.size());
// A variable to hold the current type we have reached while traversing the
// type system starting from TBA, while looking for the proper indices to
// represent the array accesses.
model::QualifiedType CurrentType = TBA.Type;
// Holds the remaning constant offset we need to traverse to complete the
// traversal of IRAccessPattern
APInt RestOff = IRPattern.BaseOffset;
auto TAPArrayIt = BestTAP.Arrays.begin();
auto TAPArrayEnd = BestTAP.Arrays.end();
revng_log(ModelGEPLog, "Initial RestOff: " << RestOff.toString(10, true));
revng_log(ModelGEPLog, "Num indices: " << BestIndices.size());
for (const auto &Id : BestIndices) {
revng_log(ModelGEPLog, "RestOff: " << RestOff.toString(10, true));
revng_log(ModelGEPLog, "Id: " << Id);
Indices.push_back(Id);
auto &Back = Indices.back();
switch (Id.Type) {
case AggregateKind::Array: {
revng_assert(Back.Type == AggregateKind::Array);
revng_assert(CurrentType.isArray());
model::QualifiedType Array = peelConstAndTypedefs(CurrentType);
auto ArrayQualIt = Array.Qualifiers().begin();
auto QEnd = Array.Qualifiers().end();
revng_assert(ArrayQualIt != QEnd);
revng_assert(model::Qualifier::isArray(*ArrayQualIt));
auto *Unqualified = Array.UnqualifiedType().get();
model::QualifiedType
ElementType = model::QualifiedType(Model.getTypePath(Unqualified),
{ std::next(ArrayQualIt), QEnd });
// We've found the first array qualifier, for which we don't know the
// index that is being accessed. That information is stored in the
// IRAccessPattern indices.
// We have to unwrap it and put data about it into Indices.
// First of all, the rest of the offset needs to be smaller than the
// array type size.
revng_assert(RestOff.ule(*CurrentType.size(VH)));
// Second, the TAP needs to still have non-consumed info associated to
// arrays
revng_assert(TAPArrayIt != TAPArrayEnd);
// The array in BestTAP that we're unwrapping has a stride equal to
// the size of this array element.
uint64_t ElementSize = *ElementType.size(VH);
revng_assert(TAPArrayIt->Stride == ElementSize);
// If the remaining offset is larger than or equal to an element size,
// we have to compute the exact index of the element that is being
// accessed
auto MaxBitWidth = std::max(RestOff.getBitWidth(), 64U);
APInt ElementIndex = APInt(MaxBitWidth, 0);
APInt::udivrem(RestOff.zextOrTrunc(MaxBitWidth),
APInt(/*bitwidth*/ MaxBitWidth, /*value*/ ElementSize),
ElementIndex,
RestOff);
revng_assert(Back.ConstantIndex == ElementIndex.getZExtValue());
// After we're done with an array, we update CurrentType and continue to
// the next iteration of the for loop on BestIndices, because we could
// have another array index and another array qualifier left in
// CurrentType
CurrentType = ElementType;
revng_assert(RestOff.ule(*CurrentType.size(VH)));
// We also omve the TAPArrayIt to point to the next array info available
// in BestTAP
++TAPArrayIt;
continue;
} break;
case AggregateKind::Struct: {
revng_assert(CurrentType.is(model::TypeKind::StructType));
auto QualifiedStruct = peelConstAndTypedefs(CurrentType);
const auto *Unqualified = QualifiedStruct.UnqualifiedType().getConst();
const auto *Struct = llvm::cast<model::StructType>(Unqualified);
// Index represents the offset of a field in the struct
revng_assert(not Back.InductionVariable);
uint64_t FieldOff = Back.ConstantIndex;
// The offset of the field should be smaller or equal to the remaining
// offset. If it's not it means that the IRAP has not sufficient offset
// to reach the pattern described by TAP. This should never happen.
revng_assert(RestOff.uge(FieldOff));
APInt OffsetInField = RestOff - FieldOff;
auto &FieldType = Struct->Fields().at(FieldOff).Type();
if (OffsetInField.uge(*FieldType.size(VH))) {
return ModelGEPArgs{ .BaseAddress = TBA,
.IndexVector = std::move(Indices),
.RestOff = RestOff,
.PointeeType = FieldType };
}
// Then we subtract the field offset from the remaining offset
RestOff = OffsetInField;
CurrentType = FieldType;
} break;
case AggregateKind::Union: {
revng_assert(CurrentType.is(model::TypeKind::UnionType));
auto QualifiedUnion = peelConstAndTypedefs(CurrentType);
const auto *Unqualified = QualifiedUnion.UnqualifiedType().getConst();
const auto *Union = llvm::cast<model::UnionType>(Unqualified);
// Index represents the number of the field in the union, this does not
// affect the RestOff, since traversing union fields does not increase
// the offset.
revng_assert(not Back.InductionVariable);
uint64_t FieldId = Back.ConstantIndex;
auto &FieldType = Union->Fields().at(FieldId).Type();
if (RestOff.uge(*FieldType.size(VH))) {
return ModelGEPArgs{ .BaseAddress = TBA,
.IndexVector = std::move(Indices),
.RestOff = RestOff,
.PointeeType = FieldType };
}
CurrentType = FieldType;
} break;
default:
revng_abort();
}
}
revng_assert(RestOff.isNonNegative());
return ModelGEPArgs{ .BaseAddress = TBA,
.IndexVector = std::move(Indices),
.RestOff = RestOff,
.PointeeType = CurrentType };
}
class GEPSummationCache {
const model::Binary &Model;
// This maps Uses to ModelGEPSummations so that in consecutive iterations on
// consecutive instructions we can reuse parts of them without walking the
// entire def-use chain.
UseGEPSummationMap UseGEPSummations = {};
RecursiveCoroutine<ModelGEPSummation>
getGEPSumImpl(Use &AddressUse, const ModelTypesMap &PointerTypes) {
revng_log(ModelGEPLog,
"getGEPSumImpl for use of: " << dumpToString(AddressUse.get()));
LoggerIndent Indent{ ModelGEPLog };
ModelGEPSummation Result = {};
// If it's already been handled, we already know if it can be
// modelGEPified or not, so we stick to that decision.
auto GEPItHint = UseGEPSummations.lower_bound(&AddressUse);
if (GEPItHint != UseGEPSummations.end()
and not(&AddressUse < GEPItHint->first)) {
revng_log(ModelGEPLog, "Found!");
rc_return GEPItHint->second;
}
revng_log(ModelGEPLog, "Not found. Compute one!");
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!");
Result = ModelGEPSummation{
.BaseAddress = TypedBaseAddress{ .Type = dropPointer(Type),
.Address = AddressArith },
// The summation is empty since AddressArith
// has exactly the type we're looking at here.
.Summation = {}
};
} else if (isa<ConstantExpr>(AddressArith)) {
revng_log(ModelGEPLog, "Traverse cast!");
Result = makeOffsetGEPSummation(AddressArith);
} else if (isa<Instruction>(AddressArith)) {
auto *AddrArithmeticInst = dyn_cast<Instruction>(AddressArith);
auto *ConstExprAddrArith = dyn_cast<ConstantExpr>(AddressArith);
if (ConstExprAddrArith)
AddrArithmeticInst = ConstExprAddrArith->getAsInstruction();
switch (AddrArithmeticInst->getOpcode()) {
case Instruction::Add: {
auto *Add = cast<BinaryOperator>(AddrArithmeticInst);
Use &LHSUse = Add->getOperandUse(0);
auto LHSSummation = rc_recur getGEPSumImpl(LHSUse, PointerTypes);
Use &RHSUse = Add->getOperandUse(1);
auto RHSSummation = rc_recur getGEPSumImpl(RHSUse, PointerTypes);
if (not RHSSummation.isValid() or not LHSSummation.isValid())
break;
bool LHSIsAddress = LHSSummation.isAddress();
bool RHSIsAddress = RHSSummation.isAddress();
// 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.
if (LHSIsAddress and RHSIsAddress) {
Result = ModelGEPSummation::invalid();
} else {
// If both LHS and RHS are not addresses (both are plain offset
// arithmetic) or just one is an address (and the other is offset
// arithmetic) we take the address (if present) as starting point,
// and add up the two summations.
Result = LHSIsAddress ? LHSSummation : RHSSummation;
Result.Summation.append(LHSIsAddress ? RHSSummation.Summation :
LHSSummation.Summation);
}
} break;
case Instruction::ZExt:
case Instruction::IntToPtr:
case Instruction::PtrToInt:
case Instruction::BitCast: {
// casts are traversed
revng_log(ModelGEPLog, "Traverse cast!");
Result = rc_recur getGEPSumImpl(AddrArithmeticInst->getOperandUse(0),
PointerTypes);
} break;
case Instruction::Mul: {
auto *Op0 = AddrArithmeticInst->getOperand(0);
auto *Op0Const = dyn_cast<ConstantInt>(Op0);
auto *Op1 = AddrArithmeticInst->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());
Result = ModelGEPSummation{
// The base address is unknown
.BaseAddress = TypedBaseAddress{ .Type = {}, .Address = nullptr },
// The summation has only one element, with a constant coefficient,
// and the index is the current instructions.
.Summation = { ModelGEPSummationElement{ .Coefficient = ConstOp,
.Index = 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.
Result = makeOffsetGEPSummation(AddrArithmeticInst);
}
} break;
case Instruction::Shl: {
auto *ShiftedBits = AddrArithmeticInst->getOperand(1);
if (auto *ConstShift = dyn_cast<ConstantInt>(ShiftedBits)) {
if (ConstShift->getValue().isNonNegative()) {
// Build the stride
auto *AddrType = AddrArithmeticInst->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 = AddrArithmeticInst->getOperand(0);
Result = ModelGEPSummation{
// The base address is unknown
.BaseAddress = TypedBaseAddress{ .Type = {},
.Address = nullptr },
// The summation has only one element, with a coefficient of 1,
// and the index is the current instructions.
.Summation = { ModelGEPSummationElement{
.Coefficient = Stride, .Index = IndexForStridedAccess } }
};
// Then we're done, break from the switch
break;
}
}
}
// In all the other cases, fall back to treating this as a non-address
// and non-strided instruction, just like e.g. division.
Result = makeOffsetGEPSummation(AddrArithmeticInst);
} break;
case Instruction::Alloca: {
Result = ModelGEPSummation::invalid();
} break;
case Instruction::GetElementPtr: {
revng_abort("TODO: gep is not supported by make-model-gep yet");
} break;
case Instruction::Load:
case Instruction::Call:
case Instruction::PHI:
case Instruction::ExtractValue:
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.
Result = makeOffsetGEPSummation(AddrArithmeticInst);
} break;
case Instruction::Unreachable:
case Instruction::Store:
case Instruction::InsertValue:
case Instruction::Invoke:
case Instruction::Resume:
case Instruction::CleanupRet:
case Instruction::CatchRet:
case Instruction::CatchPad:
case Instruction::CatchSwitch:
case Instruction::AtomicCmpXchg:
case Instruction::AtomicRMW:
case Instruction::Fence:
case Instruction::FAdd:
case Instruction::FSub:
case Instruction::FMul:
case Instruction::FDiv:
case Instruction::FRem:
case Instruction::FPTrunc:
case Instruction::FPExt:
case Instruction::FCmp:
case Instruction::FPToUI:
case Instruction::FPToSI:
case Instruction::UIToFP:
case Instruction::SIToFP:
case Instruction::AddrSpaceCast:
case Instruction::VAArg:
case Instruction::ExtractElement:
case Instruction::InsertElement:
case Instruction::ShuffleVector:
case Instruction::LandingPad:
case Instruction::CleanupPad:
case Instruction::Br:
case Instruction::IndirectBr:
case Instruction::Ret:
case Instruction::Switch: {
revng_abort("unexpected instruction for address arithmetic");
} break;
default: {
revng_abort("Unexpected operation");
} break;
}
if (ConstExprAddrArith)
AddrArithmeticInst->deleteValue();
} 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())
Result = makeOffsetGEPSummation(Const);
} else if (auto *Arg = dyn_cast<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
Result = makeOffsetGEPSummation(Arg);
} else if (isa<GlobalVariable>(AddressArith)
or isa<llvm::UndefValue>(AddressArith)
or isa<llvm::PoisonValue>(AddressArith)) {
Result = ModelGEPSummation::invalid();
} 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();
}
UseGEPSummations.insert(GEPItHint, { &AddressUse, Result });
rc_return Result;
}
ModelGEPSummation makeOffsetGEPSummation(Value *V) const {
// 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.
auto *VType = V->getType();
auto *ArithTy = dyn_cast<llvm::IntegerType>(VType);
if (not ArithTy) {
// If we're dealing with something whose type is a pointer, it cannot be
// an offset. So return an invalid ModelGEPSummation.
return ModelGEPSummation::invalid();
}
using model::Architecture::getPointerSize;
size_t PointerBytes = getPointerSize(Model.Architecture());
APInt TheOne = APInt(/*NumBits*/ 8 * PointerBytes, /*Value*/ 1);
auto *One = ConstantInt::get(V->getContext(), TheOne);
return ModelGEPSummation{
// The base address is unknown
.BaseAddress = TypedBaseAddress{ .Type = {}, .Address = nullptr },
// The summation has only one element, with a coefficient of 1, and
// the
// index is the current instructions.
.Summation = { ModelGEPSummationElement{ .Coefficient = One,
.Index = V } }
};
}
public:
GEPSummationCache(const model::Binary &M) : Model(M), UseGEPSummations() {}
void clear() { UseGEPSummations.clear(); }
ModelGEPSummation
getGEPSummation(Use &AddressUse, const ModelTypesMap &PointerTypes) {
return getGEPSumImpl(AddressUse, PointerTypes);
}
};
using UseGEPInfoMap = std::map<Use *, ModelGEPArgs>;
static UseGEPInfoMap makeGEPReplacements(llvm::Function &F,
const model::Binary &Model,
model::VerifyHelper &VH,
FunctionMetadataCache &Cache) {
UseGEPInfoMap 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 = initModelTypes(Cache,
F,
ModelF,
Model,
/*PointersOnly=*/true);
if (PointerTypes.empty()) {
revng_log(ModelGEPLog, "Model Types not found for " << F.getName());
return Result;
}
GEPSummationCache GEPSumCache{ Model };
// TypedAccessCache TAPCache;
UseTypeMap GEPifiedUsedTypes;
// LLVMContext &Ctxt = F.getContext();
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<CallInst>(&I)) {
if (not FunctionTags::CallToLifted.isTagOf(CallI)) {
revng_log(ModelGEPLog, "Skipping call to non-isolated function");
continue;
}
}
for (Use &U : I.operands()) {
// Skip BasicBlocks, they cannot be arithmetic
if (isa<llvm::BasicBlock>(U.get())) {
revng_log(ModelGEPLog, "Skipping basic block operand");
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<CallInst>(U.getUser())) {
if (not isa<llvm::Instruction>(CallUser->getCalledOperand())) {
if (&U == &CallUser->getCalledOperandUse()) {
revng_log(ModelGEPLog, "Skipping callee operand in CallInst");
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 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;
}
ModelGEPSummation GEPSum = GEPSumCache.getGEPSummation(U, PointerTypes);
revng_log(ModelGEPLog, "GEPSum " << GEPSum);
if (not GEPSum.isAddress())
continue;
// Pre-compute all the typed access patterns from the base address of
// the GEPSum, or get them from the caches if we've already computed
// them.
const model::QualifiedType &BaseTy = GEPSum.BaseAddress.Type;
// If the base type is a funcion type we have nothing to do, because
// function types cannot be "traversed" with ModelGEP.
if (BaseTy.is(model::TypeKind::RawFunctionType)
or BaseTy.is(model::TypeKind::CABIFunctionType))
continue;
// Now we extract an IRAccessPattern from the ModelGEPSummation
IRAccessPattern IRPattern = computeIRAccessPattern(Cache,
U,
GEPSum,
Model,
PointerTypes,
GEPifiedUsedTypes);
// Select among the computed TAPIndices the one which best fits the
// IRPattern
ModelGEPArgs GEPArgs = makeBestGEPArgs(GEPSum.BaseAddress,
IRPattern,
Model,
VH);
const model::Architecture::Values &Architecture = Model.Architecture();
auto PointerToGEPArgs = GEPArgs.PointeeType.getPointerTo(Architecture);
GEPifiedUsedTypes.insert({ &U, PointerToGEPArgs });
revng_log(ModelGEPLog, "Best GEPArgs: " << GEPArgs);
// If GEPSum is an address and I is an "address barrier"
// instruction (e.g. an instruction such that pointer arithmetics does
// not propagate through it), we need to check if we can still deduce
// a rich pointer type for I starting from GEPSum. 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<LoadInst>(&I)) {
std::optional<model::QualifiedType>
GEPTypeOrNone = getType(GEPArgs.BaseAddress.Type,
GEPArgs.IndexVector,
GEPArgs.RestOff,
VH);
if (GEPTypeOrNone.has_value()) {
model::QualifiedType &GEPType = GEPTypeOrNone.value();
if (GEPType.isPointer())
PointerTypes.insert({ Load, GEPType });
}
}
Result[&U] = GEPArgs;
}
}
}
return Result;
}
class ModelGEPArgCache {
std::map<model::QualifiedType, Constant *> GlobalModelGEPTypeArgs;
public:
Value *getQualifiedTypeArg(model::QualifiedType &QT, llvm::Module &M) {
auto It = GlobalModelGEPTypeArgs.find(QT);
if (It != GlobalModelGEPTypeArgs.end())
return It->second;
It = GlobalModelGEPTypeArgs.insert({ QT, serializeToLLVMString(QT, M) })
.first;
return It->second;
}
};
bool MakeModelGEPPass::runOnFunction(llvm::Function &F) {
bool Changed = false;
// Skip non-isolated functions
if (not FunctionTags::Isolated.isTagOf(&F))
return Changed;
revng_log(ModelGEPLog, "Make ModelGEP for " << F.getName());
auto Indent = LoggerIndent(ModelGEPLog);
auto &Model = getAnalysis<LoadModelWrapperPass>().get().getReadOnlyModel();
auto &Cache = getAnalysis<FunctionMetadataCachePass>().get();
model::VerifyHelper VH;
UseGEPInfoMap GEPReplacementMap = makeGEPReplacements(F, *Model, VH, Cache);
llvm::Module &M = *F.getParent();
LLVMContext &Ctxt = M.getContext();
IRBuilder<> Builder(Ctxt);
ModelGEPArgCache TypeArgCache;
// Create a function pool for AddressOf calls
OpaqueFunctionsPool<TypePair> AddressOfPool(&M,
/* PurgeOnDestruction */ false);
if (not GEPReplacementMap.empty())
initAddressOfPool(AddressOfPool, &M);
llvm::IntegerType *PtrSizedInteger = getPointerSizedInteger(Ctxt, *Model);
for (auto &[TheUseToGEPify, GEPArgs] : GEPReplacementMap) {
// Skip ModelGEPs that have no arguments
if (GEPArgs.IndexVector.empty())
continue;
revng_log(ModelGEPLog,
"GEPify use of: " << dumpToString(TheUseToGEPify->get()));
revng_log(ModelGEPLog,
" `-> use in: " << dumpToString(TheUseToGEPify->getUser()));
llvm::Type *UseType = TheUseToGEPify->get()->getType();
llvm::Type *BaseAddrType = GEPArgs.BaseAddress.Address->getType();
llvm::IntegerType *ModelGEPReturnedType;
// Calculate the size of the GEPPed field
if (GEPArgs.RestOff.isStrictlyPositive()) {
// If there is a remaining offset, we are returning something more
// similar to a pointer than the actual value
ModelGEPReturnedType = PtrSizedInteger;
} else {
std::optional<uint64_t> PointeeSize = GEPArgs.PointeeType.size(VH);
revng_assert(PointeeSize.has_value());
ModelGEPReturnedType = llvm::IntegerType::get(Ctxt,
PointeeSize.value() * 8);
}
auto *ModelGEPFunction = getModelGEP(M, ModelGEPReturnedType, 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
// qualified type of the base type of the modelGEP
model::QualifiedType &BaseType = GEPArgs.BaseAddress.Type;
auto *BaseTypeConstantStrPtr = TypeArgCache.getQualifiedTypeArg(BaseType,
M);
Args.push_back(BaseTypeConstantStrPtr);
// The second argument is the base address
Args.push_back(GEPArgs.BaseAddress.Address);
auto *UserInstr = cast<Instruction>(TheUseToGEPify->getUser());
if (auto *PHIUser = dyn_cast<PHINode>(UserInstr)) {
auto *IncomingB = PHIUser->getIncomingBlock(*TheUseToGEPify);
Builder.SetInsertPoint(IncomingB->getTerminator());
} else {
Builder.SetInsertPoint(UserInstr);
}
// The other arguments are the indices in IndexVector
for (auto [ConstantIndex, InductionVariable, AggregateTy] :
GEPArgs.IndexVector) {
if (InductionVariable) {
revng_assert(AggregateTy == AggregateKind::Array);
if (ConstantIndex) {
auto *FixedId = llvm::ConstantInt::get(InductionVariable->getType(),
ConstantIndex);
Args.push_back(Builder.CreateAdd(InductionVariable, FixedId));
} else {
Args.push_back(InductionVariable);
}
} else {
auto *Int64Type = llvm::IntegerType::get(Ctxt, 64 /*NumBits*/);
auto *FixedId = llvm::ConstantInt::get(Int64Type, ConstantIndex);
Args.push_back(FixedId);
}
}
Value *ModelGEPRef = Builder.CreateCall(ModelGEPFunction, Args);
auto AddrOfReturnedType = UseType;
if (GEPArgs.RestOff.isStrictlyPositive()) {
// If there is a remaining offset, we are returning something more
// similar to a pointer than the actual value
AddrOfReturnedType = PtrSizedInteger;
}
// Inject a call to AddressOf
auto *AddressOfFunctionType = getAddressOfType(AddrOfReturnedType,
ModelGEPReturnedType);
auto *AddressOfFunction = AddressOfPool.get({ AddrOfReturnedType,
ModelGEPReturnedType },
AddressOfFunctionType,
"AddressOf");
auto *PointeeConstantStrPtr = TypeArgCache
.getQualifiedTypeArg(GEPArgs.PointeeType,
M);
Value *ModelGEPPtr = Builder.CreateCall(AddressOfFunction,
{ PointeeConstantStrPtr,
ModelGEPRef });
if (GEPArgs.RestOff.isStrictlyPositive()) {
// 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 = GEPArgs.RestOff.zextOrTrunc(GEPResultBitWidth);
ModelGEPPtr = Builder.CreateAdd(ModelGEPPtr,
ConstantInt::get(Ctxt, OffsetToAdd));
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));
Changed = true;
}
if (VerifyLog.isEnabled())
revng_assert(not llvm::verifyModule(*F.getParent(), &llvm::dbgs()));
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);