mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
2457 lines
90 KiB
C++
2457 lines
90 KiB
C++
//
|
|
// Copyright rev.ng Srls. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <algorithm>
|
|
#include <compare>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
|
|
#include "llvm/ADT/APInt.h"
|
|
#include "llvm/ADT/Optional.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/ADT/RecursiveCoroutine.h"
|
|
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
|
|
#include "revng/Model/Architecture.h"
|
|
#include "revng/Model/Binary.h"
|
|
#include "revng/Model/LoadModelPass.h"
|
|
#include "revng/Model/Type.h"
|
|
#include "revng/Model/VerifyHelper.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/FunctionTags.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
|
|
#include "revng-c/Support/FunctionTags.h"
|
|
#include "revng-c/Support/IRHelpers.h"
|
|
#include "revng-c/Support/ModelHelpers.h"
|
|
#include "revng-c/TargetFunctionOption/TargetFunctionOption.h"
|
|
|
|
using llvm::AnalysisUsage;
|
|
using llvm::APInt;
|
|
using llvm::Argument;
|
|
using llvm::BinaryOperator;
|
|
using llvm::CallInst;
|
|
using llvm::cast;
|
|
using llvm::ConstantExpr;
|
|
using llvm::ConstantInt;
|
|
using llvm::dyn_cast;
|
|
using llvm::dyn_cast_or_null;
|
|
using llvm::ExtractValueInst;
|
|
using llvm::FunctionCallee;
|
|
using llvm::FunctionPass;
|
|
using llvm::GlobalVariable;
|
|
using llvm::Instruction;
|
|
using llvm::IRBuilder;
|
|
using llvm::isa;
|
|
using llvm::LLVMContext;
|
|
using llvm::LoadInst;
|
|
using llvm::MDTuple;
|
|
using llvm::None;
|
|
using llvm::Optional;
|
|
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 llvm::ValueAsMetadata;
|
|
|
|
static Logger<> ModelGEPLog{ "make-model-gep" };
|
|
|
|
static constexpr const char *const FunctionMDName = "revng.function.entry";
|
|
static constexpr const char *const CallMDName = "revng.callerblock.start";
|
|
|
|
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>();
|
|
}
|
|
};
|
|
|
|
template<HasMetadata ValueT>
|
|
static MetaAddress
|
|
getMetaAddressFromNamedMD(const ValueT *V, const std::string &Name) {
|
|
if (V)
|
|
if (const auto *Metadata = V->getMetadata(Name))
|
|
if (const auto *MDT = dyn_cast_or_null<MDTuple>(Metadata))
|
|
if (const auto *VAM = dyn_cast<ValueAsMetadata>(MDT->getOperand(0)))
|
|
return MetaAddress::fromConstant(VAM->getValue());
|
|
return MetaAddress::invalid();
|
|
}
|
|
|
|
static const model::Function &
|
|
getModelFunction(const model::Binary &Model, const llvm::Function &F) {
|
|
auto FunctionMetaAddress = getMetaAddressFromNamedMD(&F, FunctionMDName);
|
|
return Model.Functions.at(FunctionMetaAddress);
|
|
}
|
|
|
|
static RecursiveCoroutine<bool> isArray(const model::QualifiedType &QT) {
|
|
|
|
const auto IsNotConstQual = [](const model::Qualifier &Q) {
|
|
return not Q.isConstQualifier();
|
|
};
|
|
|
|
for (const auto &Q : llvm::make_filter_range(QT.Qualifiers, IsNotConstQual)) {
|
|
|
|
if (Q.isArrayQualifier())
|
|
rc_return true;
|
|
|
|
revng_assert(Q.isPointerQualifier());
|
|
rc_return false;
|
|
}
|
|
|
|
if (auto *TD = dyn_cast<model::TypedefType>(QT.UnqualifiedType.get()))
|
|
rc_return rc_recur isArray(TD->UnderlyingType);
|
|
|
|
rc_return false;
|
|
}
|
|
|
|
static RecursiveCoroutine<bool> isPointer(const model::QualifiedType &QT) {
|
|
|
|
const auto IsNotConstQual = [](const model::Qualifier &Q) {
|
|
return not Q.isConstQualifier();
|
|
};
|
|
|
|
for (const auto &Q : llvm::make_filter_range(QT.Qualifiers, IsNotConstQual)) {
|
|
|
|
if (Q.isPointerQualifier())
|
|
rc_return true;
|
|
|
|
revng_assert(Q.isArrayQualifier());
|
|
rc_return false;
|
|
}
|
|
|
|
if (auto *TD = dyn_cast<model::TypedefType>(QT.UnqualifiedType.get()))
|
|
rc_return rc_recur isPointer(TD->UnderlyingType);
|
|
|
|
rc_return false;
|
|
}
|
|
|
|
static RecursiveCoroutine<model::QualifiedType>
|
|
dropPointer(const model::QualifiedType &QT) {
|
|
|
|
revng_assert(isPointer(QT));
|
|
|
|
auto QIt = QT.Qualifiers.begin();
|
|
auto QEnd = QT.Qualifiers.end();
|
|
for (; QIt != QEnd; ++QIt) {
|
|
|
|
if (QIt->isConstQualifier())
|
|
continue;
|
|
|
|
if (QIt->isPointerQualifier())
|
|
rc_return model::QualifiedType(QT.UnqualifiedType,
|
|
{ std::next(QIt), QEnd });
|
|
|
|
revng_assert(QIt->isArrayQualifier());
|
|
rc_return QT;
|
|
}
|
|
|
|
if (auto *TD = dyn_cast<model::TypedefType>(QT.UnqualifiedType.get()))
|
|
rc_return rc_recur dropPointer(TD->UnderlyingType);
|
|
|
|
rc_return QT;
|
|
}
|
|
|
|
static RecursiveCoroutine<bool> isVoid(const model::QualifiedType &QT) {
|
|
|
|
const auto IsNotConstQual = [](const model::Qualifier &Q) {
|
|
return not Q.isConstQualifier();
|
|
};
|
|
|
|
// If it has a qualifier that is non const, then it's an array or a pointer,
|
|
// so it cannot be void.
|
|
if (llvm::any_of(QT.Qualifiers, IsNotConstQual))
|
|
rc_return false;
|
|
|
|
const model::Type *U = QT.UnqualifiedType.get();
|
|
if (auto *TD = dyn_cast<model::TypedefType>(U))
|
|
rc_return rc_recur isVoid(TD->UnderlyingType);
|
|
|
|
auto *Primitive = dyn_cast<model::PrimitiveType>(U);
|
|
if (not Primitive)
|
|
rc_return false;
|
|
|
|
rc_return Primitive->PrimitiveKind == model::PrimitiveTypeKind::Void;
|
|
}
|
|
|
|
static const model::Type *getCalleePrototype(const model::BasicBlock &ModelBB,
|
|
const model::Binary &Model) {
|
|
|
|
const model::CallEdge *CallSucc = nullptr;
|
|
for (const auto &Succ : ModelBB.Successors) {
|
|
|
|
const auto *ThisSucc = dyn_cast<model::CallEdge>(Succ.get());
|
|
if (not ThisSucc)
|
|
continue;
|
|
|
|
// ModelBB should only have a single CallEdge successor
|
|
revng_assert(not CallSucc);
|
|
CallSucc = ThisSucc;
|
|
}
|
|
revng_assert(CallSucc);
|
|
|
|
const model::Type *FType = getPrototype(Model, *CallSucc).get();
|
|
revng_assert(FType);
|
|
return FType;
|
|
}
|
|
|
|
using ValueModelTypesMap = std::map<Value *, model::QualifiedType>;
|
|
|
|
ValueModelTypesMap initializeModelTypes(llvm::Function &F,
|
|
const model::Function &ModelF,
|
|
const model::Binary &Model) {
|
|
ValueModelTypesMap Result;
|
|
|
|
auto Indent = LoggerIndent(ModelGEPLog);
|
|
|
|
const model::Type *FType = ModelF.Prototype.get();
|
|
revng_assert(FType);
|
|
|
|
// TODO we should create types for ConstantInts that are valid addresses that
|
|
// point into segments
|
|
|
|
// First, initialize the types of F's arguments
|
|
revng_log(ModelGEPLog, "Initialize argument types");
|
|
if (const auto *RFT = dyn_cast<model::RawFunctionType>(FType)) {
|
|
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "model::RawFunctionType");
|
|
unsigned ActualArgSize = RFT->Arguments.size()
|
|
+ (RFT->StackArgumentsType.isValid() ? 1 : 0);
|
|
revng_assert(F.arg_size() == ActualArgSize);
|
|
|
|
auto MoreMoreIndent = LoggerIndent(ModelGEPLog);
|
|
for (const auto &[ModelArg, LLVMArg] :
|
|
llvm::zip_first(RFT->Arguments, F.args())) {
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "llvm::Argument: " << dumpToString(LLVMArg));
|
|
revng_log(ModelGEPLog, "model::QualifiedType: " << ModelArg.Type);
|
|
if (isPointer(ModelArg.Type)) {
|
|
revng_log(ModelGEPLog, "INITIALIZED");
|
|
Result[&LLVMArg] = ModelArg.Type;
|
|
}
|
|
}
|
|
} else if (const auto *CFT = dyn_cast<model::CABIFunctionType>(FType)) {
|
|
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
revng_assert(CFT->Arguments.size() == F.arg_size());
|
|
revng_log(ModelGEPLog, "model::CABIFunctionType");
|
|
|
|
auto MoreMoreIndent = LoggerIndent(ModelGEPLog);
|
|
for (const auto &[ModelArg, LLVMArg] :
|
|
llvm::zip_first(CFT->Arguments, F.args())) {
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "llvm::Argument: " << dumpToString(LLVMArg));
|
|
revng_log(ModelGEPLog, "model::QualifiedType: " << ModelArg.Type);
|
|
if (isPointer(ModelArg.Type)) {
|
|
revng_log(ModelGEPLog, "INITIALIZED");
|
|
Result[&LLVMArg] = ModelArg.Type;
|
|
}
|
|
}
|
|
} else {
|
|
revng_abort("Function should have RawFunctionType or CABIFunctionType");
|
|
}
|
|
|
|
for (auto &I : llvm::instructions(F)) {
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "Instruction " << dumpToString(&I));
|
|
auto MoreMoreIndent = LoggerIndent(ModelGEPLog);
|
|
|
|
// For calls we have some cases we want to catch:
|
|
// - return values for which we have types on the model
|
|
// - special functions that initialize stack-allocated stuff (stack
|
|
// variables, stack arguments passed to call sites)
|
|
if (auto *Call = dyn_cast<CallInst>(&I)) {
|
|
revng_log(ModelGEPLog, "Call");
|
|
|
|
// Special case for calls to special functions that initialize
|
|
// stack-allocated stuff
|
|
auto *Callee = Call->getCalledFunction();
|
|
if (Callee) {
|
|
if (Callee->getName() == "revng_stack_frame") {
|
|
if (ModelF.StackFrameType.isValid()) {
|
|
|
|
using model::Architecture::getPointerSize;
|
|
size_t PointerBytes = getPointerSize(Model.Architecture);
|
|
auto PointerQual = model::Qualifier::createPointer(PointerBytes);
|
|
model::QualifiedType FStackType(ModelF.StackFrameType,
|
|
{ PointerQual });
|
|
revng_log(ModelGEPLog, "Call: " << dumpToString(Call));
|
|
revng_log(ModelGEPLog, "model::QualifiedType: " << FStackType);
|
|
revng_log(ModelGEPLog, "INITIALIZED");
|
|
Result[Call] = std::move(FStackType);
|
|
}
|
|
|
|
continue;
|
|
|
|
} else if (Callee->getName() == "revng_call_stack_arguments") {
|
|
|
|
for (Use &StackArgsUse : Call->uses()) {
|
|
auto *CallUsingArgs = dyn_cast<CallInst>(StackArgsUse.getUser());
|
|
if (CallUsingArgs) {
|
|
// The stack argument should be the last
|
|
unsigned NArgOperands = CallUsingArgs->getNumArgOperands();
|
|
revng_assert(StackArgsUse.getOperandNo() == NArgOperands - 1);
|
|
|
|
auto CallMA = getMetaAddressFromNamedMD(Call, CallMDName);
|
|
revng_assert(not CallMA.isInvalid());
|
|
const model::BasicBlock &ModelBB = ModelF.CFG.at(CallMA);
|
|
const auto *FType = getCalleePrototype(ModelBB, Model);
|
|
const auto *CalleeRFT = cast<model::RawFunctionType>(FType);
|
|
revng_assert(CalleeRFT->StackArgumentsType.isValid());
|
|
|
|
model::QualifiedType
|
|
CalleeStackType(CalleeRFT->StackArgumentsType,
|
|
{ model::Qualifier::createPointer(8) });
|
|
Result[Call] = std::move(CalleeStackType);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If we reach this point, the call is not calling a special-cased
|
|
// stack-allocation function, so we need to check if it calls an
|
|
// isolated function.
|
|
|
|
// If this call does not have revng metadata we cannot figure out the
|
|
// prototype of the callee, so we have no types to inizialize.
|
|
// Just go on with the next instruction.
|
|
auto CallMetaAddress = getMetaAddressFromNamedMD(Call, CallMDName);
|
|
if (CallMetaAddress.isInvalid()) {
|
|
revng_log(ModelGEPLog, "Does not have model metadata. Skip ...");
|
|
continue;
|
|
}
|
|
|
|
const model::BasicBlock &ModelBB = ModelF.CFG.at(CallMetaAddress);
|
|
const auto *FType = getCalleePrototype(ModelBB, Model);
|
|
revng_assert(FType);
|
|
|
|
if (const auto *RFT = dyn_cast<model::RawFunctionType>(FType)) {
|
|
revng_log(ModelGEPLog, "Call has RawFunctionType prototype.");
|
|
|
|
// If the callee function does not return anything, skip to the next
|
|
// instruction.
|
|
if (RFT->ReturnValues.empty()) {
|
|
revng_log(ModelGEPLog, "Does not return values on model. Skip ...");
|
|
revng_assert(Call->getType()->isVoidTy());
|
|
continue;
|
|
}
|
|
|
|
if (RFT->ReturnValues.size() == 1) {
|
|
revng_log(ModelGEPLog, "Has single return type.");
|
|
|
|
revng_assert(Call->getType()->isVoidTy()
|
|
or Call->getType()->isIntOrPtrTy());
|
|
|
|
const model::QualifiedType &ModT = RFT->ReturnValues.begin()->Type;
|
|
if (isPointer(ModT)) {
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "llvm::CallInst: " << dumpToString(Call));
|
|
revng_log(ModelGEPLog, "model::QualifiedType: " << ModT);
|
|
Result[Call] = ModT;
|
|
}
|
|
|
|
} else {
|
|
auto *StrucT = cast<llvm::StructType>(Call->getType());
|
|
revng_log(ModelGEPLog, "Has many return types.");
|
|
revng_assert(StrucT->getNumElements() == RFT->ReturnValues.size());
|
|
|
|
if (not Call->getNumUses()) {
|
|
revng_log(ModelGEPLog, "Has no uses. Skip ...");
|
|
continue;
|
|
}
|
|
|
|
const auto Extracted = getExtractedValuesFromInstruction(Call);
|
|
revng_assert(Extracted.size() == StrucT->getNumElements());
|
|
for (const auto &[ReturnValue, ExtractedSet] :
|
|
llvm::zip_first(RFT->ReturnValues, Extracted)) {
|
|
// Inside here we're working on a signle field of the struct.
|
|
// ExtractedSet contains all the ExtractValueInst that extract the
|
|
// same field of the struct.
|
|
const model::QualifiedType &ModT = ReturnValue.Type;
|
|
if (isPointer(ModT)) {
|
|
for (auto *V : ExtractedSet) {
|
|
revng_assert(isa<ExtractValueInst>(V));
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog,
|
|
"llvm::ExtractValueInst: " << dumpToString(V));
|
|
revng_log(ModelGEPLog, "model::QualifiedType: " << ModT);
|
|
Result[V] = ModT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (const auto *CFT = dyn_cast<model::CABIFunctionType>(FType)) {
|
|
revng_log(ModelGEPLog, "Call has CABIFunctionType prototype.");
|
|
|
|
// If the callee function does not return anything, skip to the next
|
|
// instruction.
|
|
if (isVoid(CFT->ReturnType)) {
|
|
revng_log(ModelGEPLog, "Returns void. Skip ...");
|
|
revng_assert(Call->getType()->isVoidTy());
|
|
continue;
|
|
}
|
|
|
|
// TODO: we haven't handled how to emit return values of CABIFunctions
|
|
revng_abort();
|
|
|
|
} else {
|
|
revng_abort("Function should have RawFunctionType or "
|
|
"CABIFunctionType");
|
|
}
|
|
}
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "Done initializing argument types");
|
|
|
|
return Result;
|
|
}
|
|
|
|
// 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 << "Address: ";
|
|
if (Address)
|
|
Address->print(OS);
|
|
else
|
|
OS << "nullptr";
|
|
OS << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
// clang-format off
|
|
template<typename T>
|
|
concept LLVMRawOStreamDumpable = not ValueLikePrintable<T>
|
|
and not ModFunLikePrintable<T>
|
|
and requires(T TheT) {
|
|
TheT.dump(std::declval<llvm::raw_ostream &>());
|
|
};
|
|
// clang-format on
|
|
|
|
template<bool B, LLVMRawOStreamDumpable Dumpable>
|
|
static void writeToLog(Logger<B> &L, const Dumpable &P, int /* Ignore */) {
|
|
if (L.isEnabled()) {
|
|
llvm::SmallString<32> Buffer;
|
|
llvm::raw_svector_ostream Stream(Buffer);
|
|
P.dump(Stream);
|
|
L << Stream.str().str();
|
|
}
|
|
}
|
|
|
|
/// \brief Logging routine for model::QualifiedType
|
|
// TODO: drop when QualifiedType itself will provide its own
|
|
template<bool B>
|
|
static void
|
|
writeToLog(Logger<B> &L, const model::QualifiedType &QT, int /* Ignore */) {
|
|
if (L.isEnabled()) {
|
|
llvm::SmallString<32> Buffer;
|
|
llvm::raw_svector_ostream Stream(Buffer);
|
|
serialize(Stream, QT);
|
|
L << Stream.str().str();
|
|
}
|
|
}
|
|
|
|
// 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 = {};
|
|
Optional<model::QualifiedType> PointeeType = None;
|
|
|
|
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.hasValue())
|
|
serialize(OS, PointeeType.getValue());
|
|
else
|
|
OS << "std::nullopt";
|
|
OS << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
static IRAccessPattern computeAccessPattern(const Use &U,
|
|
const ModelGEPSummation &GEPSum,
|
|
const model::Binary &Model) {
|
|
revng_assert(GEPSum.isAddress());
|
|
|
|
// First, prepare the BaseOffset and the Indices for the IRAccessPattern.
|
|
GEPSummationVector IRPatternIndices;
|
|
size_t PointerBytes = model::Architecture::getPointerSize(Model.Architecture);
|
|
APInt BaseOff = APInt(/*NumBits*/ 8 * PointerBytes, /*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.truncOrSelf(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,
|
|
.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 = None };
|
|
|
|
// 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::TypePath
|
|
Pointee = Model.getPrimitiveType(model::PrimitiveTypeKind::Generic,
|
|
PointeeSize);
|
|
model::QualifiedType QPointee = model::QualifiedType(Pointee, {});
|
|
revng_log(ModelGEPLog, "QPointee: " << 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 QPointee = model::QualifiedType(Pointee, {});
|
|
revng_log(ModelGEPLog, "QPointee: " << 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.
|
|
|
|
revng_assert(FunctionTags::Lifted.isTagOf(ReturningF));
|
|
const model::Function &MF = getModelFunction(Model, *ReturningF);
|
|
|
|
const model::Type *FType = MF.Prototype.get();
|
|
revng_assert(FType);
|
|
|
|
if (const auto *RFT = dyn_cast<model::RawFunctionType>(FType)) {
|
|
revng_log(ModelGEPLog, "Has RawFunctionType prototype.");
|
|
|
|
// If the callee function does not return anything, skip to the next
|
|
// instruction.
|
|
if (RFT->ReturnValues.empty()) {
|
|
revng_log(ModelGEPLog, "Does not return values on model. Skip ...");
|
|
revng_assert(not Ret->getReturnValue());
|
|
} else if (RFT->ReturnValues.size() == 1) {
|
|
revng_log(ModelGEPLog, "Has single return type.");
|
|
|
|
revng_assert(Ret->getReturnValue()->getType()->isVoidTy()
|
|
or Ret->getReturnValue()->getType()->isIntOrPtrTy());
|
|
|
|
const model::QualifiedType &ModT = RFT->ReturnValues.begin()->Type;
|
|
// 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 (isPointer(ModT)) {
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
revng_log(ModelGEPLog, "llvm::ReturnInst: " << dumpToString(Ret));
|
|
revng_log(ModelGEPLog, "Pointee: model::QualifiedType: " << ModT);
|
|
IRPattern.PointeeType = dropPointer(ModT);
|
|
}
|
|
|
|
} else {
|
|
auto *RetVal = Ret->getReturnValue();
|
|
auto *StructTy = cast<llvm::StructType>(RetVal->getType());
|
|
revng_log(ModelGEPLog, "Has many return types.");
|
|
revng_assert(StructTy->getNumElements() == RFT->ReturnValues.size());
|
|
|
|
// 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 (const auto *CFT = dyn_cast<model::CABIFunctionType>(FType)) {
|
|
revng_log(ModelGEPLog, "Has CABIFunctionType prototype.");
|
|
|
|
// If the callee function does not return anything, skip to the next
|
|
// instruction.
|
|
if (isVoid(CFT->ReturnType)) {
|
|
revng_log(ModelGEPLog, "Returns void. Skip ...");
|
|
revng_assert(not Ret->getReturnValue());
|
|
} else {
|
|
|
|
// TODO: we haven't handled return values of CABIFunctions yet
|
|
revng_abort();
|
|
}
|
|
|
|
} else {
|
|
revng_abort("Function should have RawFunctionType or "
|
|
"CABIFunctionType");
|
|
}
|
|
|
|
} 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");
|
|
|
|
// If this call does not have revng metadata we cannot figure out the
|
|
// prototype of the callee, so we have no types to inizialize.
|
|
// Just go on with the next instruction.
|
|
auto CallMetaAddress = getMetaAddressFromNamedMD(Call, CallMDName);
|
|
|
|
const llvm::Function *F = Call->getFunction();
|
|
if (CallMetaAddress.isInvalid()) {
|
|
|
|
revng_log(ModelGEPLog, "Does not have model metadata. Skip ...");
|
|
|
|
} else if (FunctionTags::StructInitializer.isTagOf(F)) {
|
|
|
|
// special case for struct initializers. Eventually we should use
|
|
// FunctionTags for this, not the function name.
|
|
unsigned ArgNum = Call->getArgOperandNo(&U);
|
|
|
|
const model::Function &MF = getModelFunction(Model, *F);
|
|
const auto *FType = MF.Prototype.get();
|
|
if (const auto *RFT = dyn_cast<model::RawFunctionType>(FType)) {
|
|
revng_log(ModelGEPLog, "Has RawFunctionType prototype.");
|
|
revng_assert(RFT->ReturnValues.size() > 1);
|
|
|
|
auto *StructTy = cast<llvm::StructType>(F->getReturnType());
|
|
revng_log(ModelGEPLog, "Has many return types.");
|
|
revng_assert(StructTy->getNumElements() == RFT->ReturnValues.size());
|
|
|
|
model::QualifiedType
|
|
RetTy = std::next(RFT->ReturnValues.begin(), ArgNum)->Type;
|
|
if (isPointer(RetTy)) {
|
|
model::QualifiedType Pointee = dropPointer(RetTy);
|
|
revng_log(ModelGEPLog, "Pointee: " << Pointee);
|
|
IRPattern.PointeeType = Pointee;
|
|
}
|
|
} else if (const auto *CFT = dyn_cast<model::CABIFunctionType>(FType)) {
|
|
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 {
|
|
|
|
const model::Function &MF = getModelFunction(Model, *F);
|
|
const model::BasicBlock &ModelBB = MF.CFG.at(CallMetaAddress);
|
|
const auto *FType = getCalleePrototype(ModelBB, Model);
|
|
revng_assert(FType);
|
|
|
|
if (const auto *RFT = dyn_cast<model::RawFunctionType>(FType)) {
|
|
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
revng_assert(RFT->Arguments.size() == Call->arg_size());
|
|
revng_log(ModelGEPLog, "model::RawFunctionType");
|
|
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
unsigned ArgOpNum = Call->getArgOperandNo(&U);
|
|
revng_log(ModelGEPLog, "ArgOpNum: " << ArgOpNum);
|
|
revng_log(ModelGEPLog, "ArgOperand: " << U.get());
|
|
auto ArgIt = RFT->Arguments.begin();
|
|
model::QualifiedType ArgTy = std::next(ArgIt, ArgOpNum)->Type;
|
|
revng_log(ModelGEPLog, "model::QualifiedType: " << ArgTy);
|
|
if (isPointer(ArgTy)) {
|
|
model::QualifiedType Pointee = dropPointer(ArgTy);
|
|
revng_log(ModelGEPLog, "Pointee: " << Pointee);
|
|
IRPattern.PointeeType = Pointee;
|
|
}
|
|
|
|
} else if (const auto *CFT = dyn_cast<model::CABIFunctionType>(FType)) {
|
|
|
|
auto MoreIndent = LoggerIndent(ModelGEPLog);
|
|
revng_assert(CFT->Arguments.size() == Call->arg_size());
|
|
revng_log(ModelGEPLog, "model::CABIFunctionType");
|
|
|
|
auto _ = LoggerIndent(ModelGEPLog);
|
|
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: " << ArgTy);
|
|
if (isPointer(ArgTy)) {
|
|
model::QualifiedType Pointee = dropPointer(ArgTy);
|
|
revng_log(ModelGEPLog, "Pointee: " << Pointee);
|
|
IRPattern.PointeeType = Pointee;
|
|
}
|
|
|
|
} else {
|
|
revng_abort("Function should have RawFunctionType or "
|
|
"CABIFunctionType");
|
|
}
|
|
}
|
|
|
|
} else if (auto *PHI = dyn_cast<PHINode>(UserInstr)) {
|
|
}
|
|
}
|
|
|
|
return IRPattern;
|
|
}
|
|
|
|
static bool compareQualifiedTypes(const model::QualifiedType &LHS,
|
|
const model::QualifiedType &RHS) {
|
|
if (LHS.Qualifiers < RHS.Qualifiers)
|
|
return true;
|
|
|
|
if (LHS.UnqualifiedType.get() < RHS.UnqualifiedType.get())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
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 compareQualifiedTypes(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 { Struct, Union, Array };
|
|
|
|
std::string toString(AggregateKind K) {
|
|
switch (K) {
|
|
case Struct:
|
|
return "Struct";
|
|
case Union:
|
|
return "Union";
|
|
case Array:
|
|
return "Array";
|
|
}
|
|
return "Invalid";
|
|
}
|
|
|
|
struct ChildInfo {
|
|
Value *Index;
|
|
AggregateKind Type;
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "ModelGEPSummationElement{\nIndex:\n";
|
|
if (Index)
|
|
Index->print(OS);
|
|
else
|
|
OS << "nullptr";
|
|
OS << "\nType: " << toString(Type) << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
using ChildIndexVector = SmallVector<ChildInfo, 4>;
|
|
|
|
using TAPToChildIdsMap = std::map<TypedAccessPattern, ChildIndexVector>;
|
|
|
|
using QTLess = std::integral_constant<decltype(&compareQualifiedTypes),
|
|
compareQualifiedTypes>;
|
|
|
|
// clang-format off
|
|
using QualifiedTypeToTAPChildIdsMap = std::map<const model::QualifiedType,
|
|
TAPToChildIdsMap,
|
|
QTLess>;
|
|
// clang-format on
|
|
|
|
using TAPToChildIdsMapConstRef = std::reference_wrapper<const TAPToChildIdsMap>;
|
|
|
|
struct DifferenceScore {
|
|
// Higher Difference are for stuff that is farther apart from a perfect match.
|
|
// 0 or lower scores are for accesses that insist exactly on the beginning of
|
|
// the type. Negative scores are for accesses that insist exactly on the
|
|
// beginning of the type, but allowing for various levels of customization
|
|
// (such as e.g. accesses that go deeper inside the type system and perfect
|
|
// match have a score that is "more negative").
|
|
ssize_t Difference = std::numeric_limits<ssize_t>::max();
|
|
|
|
// 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();
|
|
|
|
// Boolean to mark out-of-range accesses
|
|
bool InRange = false;
|
|
|
|
std::strong_ordering operator<=>(const DifferenceScore &Other) const {
|
|
if (InRange != Other.InRange)
|
|
return InRange ? std::strong_ordering::less :
|
|
std::strong_ordering::greater;
|
|
auto Cmp = Difference <=> Other.Difference;
|
|
if (Cmp != 0)
|
|
return Cmp;
|
|
|
|
// 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;
|
|
}
|
|
|
|
void dump(llvm::raw_ostream &OS) const debug_function {
|
|
OS << "DifferenceScore { .Difference = " << Difference
|
|
<< ", .Depth = " << Depth
|
|
<< ", .InRange = " << (InRange ? "true" : "false") << "}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
struct ScoredIndices {
|
|
// The score is optional, nullopt means that the difference score is infinity
|
|
std::optional<DifferenceScore> Score = std::nullopt;
|
|
ChildIndexVector Indices = {};
|
|
|
|
static ScoredIndices invalid() { return ScoredIndices{}; }
|
|
|
|
static ScoredIndices outOfBound(ssize_t DiffScore) {
|
|
return ScoredIndices{ .Score = DifferenceScore{ .Difference = DiffScore,
|
|
.Depth = 0,
|
|
.InRange = false },
|
|
.Indices{} };
|
|
}
|
|
static ScoredIndices nestedOutOfBound(ssize_t DiffScore,
|
|
size_t Depth,
|
|
ChildIndexVector &&Indices) {
|
|
revng_assert(Depth <= Indices.size());
|
|
if (Depth < Indices.size())
|
|
Indices.resize(Depth);
|
|
return ScoredIndices{ .Score = DifferenceScore{ .Difference = DiffScore,
|
|
.Depth = Depth,
|
|
.InRange = false },
|
|
.Indices = std::move(Indices) };
|
|
}
|
|
};
|
|
|
|
static ScoredIndices
|
|
differenceScore(const model::QualifiedType &BaseType,
|
|
const TAPToChildIdsMap::value_type &TAPWithIndices,
|
|
const IRAccessPattern &IRAP,
|
|
model::VerifyHelper &VH) {
|
|
|
|
auto Result = ScoredIndices::outOfBound(IRAP.BaseOffset.getSExtValue());
|
|
|
|
const auto &[TAP, ChildIndices] = TAPWithIndices;
|
|
ChildIndexVector ResultIndices = ChildIndices;
|
|
|
|
size_t BaseSize = *BaseType.size();
|
|
if (IRAP.BaseOffset.uge(BaseSize))
|
|
return Result;
|
|
|
|
revng_assert(TAP.BaseOffset.ult(BaseSize));
|
|
revng_assert((TAP.BaseOffset + *TAP.AccessedType.size()).ule(BaseSize));
|
|
|
|
APInt RestOff = IRAP.BaseOffset;
|
|
|
|
auto ArrayInfoIt = TAP.Arrays.begin();
|
|
auto ArrayInfoEnd = TAP.Arrays.end();
|
|
|
|
auto IRAPIndicesIt = IRAP.Indices.begin();
|
|
auto IRAPIndicesEnd = IRAP.Indices.end();
|
|
|
|
model::QualifiedType NestedType = BaseType;
|
|
|
|
for (auto &ChildID : ResultIndices) {
|
|
model::QualifiedType Normalized = peelConstAndTypedefs(NestedType, VH);
|
|
|
|
// 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 isPointer(Normalized));
|
|
|
|
switch (ChildID.Type) {
|
|
|
|
case Struct: {
|
|
revng_assert(not isArray(Normalized));
|
|
|
|
auto *S = cast<model::StructType>(Normalized.UnqualifiedType.get());
|
|
size_t FieldOffset = cast<ConstantInt>(ChildID.Index)->getZExtValue();
|
|
|
|
// 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.
|
|
// So we just bail out.
|
|
if (RestOff.ult(FieldOffset))
|
|
return ScoredIndices::invalid();
|
|
|
|
RestOff -= FieldOffset;
|
|
|
|
NestedType = S->Fields.at(FieldOffset).Type;
|
|
++Result.Score->Depth;
|
|
} break;
|
|
|
|
case Union: {
|
|
revng_assert(not isArray(Normalized));
|
|
auto *U = cast<model::UnionType>(Normalized.UnqualifiedType.get());
|
|
size_t FieldID = cast<ConstantInt>(ChildID.Index)->getZExtValue();
|
|
NestedType = U->Fields.at(FieldID).Type;
|
|
++Result.Score->Depth;
|
|
} break;
|
|
|
|
case Array: {
|
|
revng_assert(isArray(Normalized));
|
|
revng_assert(ArrayInfoIt != ArrayInfoEnd);
|
|
|
|
const auto ArrayQualEnd = Normalized.Qualifiers.end();
|
|
const auto ArrayQualIt = llvm::find_if(Normalized.Qualifiers,
|
|
[](const model::Qualifier &Q) {
|
|
return Q.isArrayQualifier();
|
|
});
|
|
revng_assert(ArrayQualIt != ArrayQualEnd);
|
|
|
|
revng_assert(not ChildID.Index);
|
|
if (IRAPIndicesIt == IRAPIndicesEnd) {
|
|
// This means that the IRAP does not have strided accesses anymore.
|
|
// Hence for performing this array access it's using a constant offset
|
|
// that needs to be translated into an index into the array.
|
|
|
|
revng_assert(ArrayQualIt->Size == ArrayInfoIt->NumElems);
|
|
|
|
APInt ElemIndex;
|
|
APInt OffInElem;
|
|
APInt::udivrem(RestOff, ArrayInfoIt->Stride, 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.getSExtValue(),
|
|
Result.Score->Depth,
|
|
std::move(ResultIndices));
|
|
}
|
|
|
|
RestOff = OffInElem;
|
|
|
|
} else {
|
|
revng_assert(not isa<ConstantInt>(IRAPIndicesIt->Index));
|
|
|
|
// If the IRAccessPattern has a coefficient that is different from the
|
|
// Stride of ArrayInfo, it means that IRAPIndicesIt->Index is not the
|
|
// index of an element in the array, so we bail out.
|
|
if (IRAPIndicesIt->Coefficient->getZExtValue() != ArrayInfoIt->Stride) {
|
|
// TODO: in principle we could score this guy for similarity anyway.
|
|
// But we need to return an llvm::Value or something that represents
|
|
// what's left to add to the modelGEP if we select this.
|
|
return ScoredIndices::invalid();
|
|
}
|
|
|
|
++IRAPIndicesIt;
|
|
++Result.Score->Depth;
|
|
}
|
|
|
|
NestedType = model::QualifiedType(Normalized.UnqualifiedType,
|
|
{ std::next(ArrayQualIt),
|
|
ArrayQualEnd });
|
|
|
|
++ArrayInfoIt;
|
|
} break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
Result = ScoredIndices{
|
|
.Score = DifferenceScore{ .Difference = RestOff.getSExtValue(),
|
|
.Depth = ResultIndices.size(),
|
|
.InRange = RestOff.isNonNegative() },
|
|
.Indices = std::move(ResultIndices),
|
|
};
|
|
|
|
return Result;
|
|
}
|
|
|
|
static std::pair<TypedAccessPattern, ChildIndexVector>
|
|
pickBestTAP(const model::QualifiedType &BaseType,
|
|
const IRAccessPattern &IRPattern,
|
|
const TAPToChildIdsMap &TAPIndices,
|
|
model::VerifyHelper &VH) {
|
|
revng_log(ModelGEPLog, "Picking Best TAP for IRAP: " << IRPattern);
|
|
auto Indent = LoggerIndent{ ModelGEPLog };
|
|
revng_log(ModelGEPLog, "TAPIndices.size() = " << TAPIndices.size());
|
|
|
|
std::map<TypedAccessPattern, ChildIndexVector> BestTAPsWithIndices;
|
|
|
|
DifferenceScore BestDifferenceScore;
|
|
revng_log(ModelGEPLog, "BestDifferenceScore = " << BestDifferenceScore);
|
|
auto MoreIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
for (const auto &TAPWithIndices : TAPIndices) {
|
|
|
|
if (ModelGEPLog.isEnabled()) {
|
|
revng_log(ModelGEPLog, "TAP = " << TAPWithIndices.first);
|
|
revng_log(ModelGEPLog, "Indices = {");
|
|
for (const auto &I : TAPWithIndices.second) {
|
|
auto InternalIndent = LoggerIndent{ ModelGEPLog };
|
|
revng_log(ModelGEPLog, I);
|
|
}
|
|
revng_log(ModelGEPLog, "}");
|
|
}
|
|
auto EvenMoreIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
ScoredIndices ScoredIdx = differenceScore(BaseType,
|
|
TAPWithIndices,
|
|
IRPattern,
|
|
VH);
|
|
if (not ScoredIdx.Score.has_value()) {
|
|
revng_log(ModelGEPLog, "differenceScore = std::nullopt");
|
|
continue;
|
|
}
|
|
|
|
DifferenceScore Difference = ScoredIdx.Score.value();
|
|
|
|
revng_log(ModelGEPLog, "differenceScore = " << Difference);
|
|
if (Difference > BestDifferenceScore) {
|
|
revng_log(ModelGEPLog, "Worse than BestDifferenceScore");
|
|
continue;
|
|
}
|
|
|
|
if (Difference < BestDifferenceScore) {
|
|
BestTAPsWithIndices.clear();
|
|
BestDifferenceScore = Difference;
|
|
revng_log(ModelGEPLog,
|
|
"Update BestDifferenceScore = " << BestDifferenceScore);
|
|
}
|
|
|
|
revng_log(ModelGEPLog, "NEW Best Indices");
|
|
|
|
BestTAPsWithIndices[TAPWithIndices.first] = std::move(ScoredIdx.Indices);
|
|
}
|
|
|
|
// TODO: here we always pick the first among those with the best similarity
|
|
// score. In the future we can try to figure out if there is a better policy.
|
|
// But in principle we should be able to integrate the policy into the
|
|
// similarity score, more than adding another layer of decision making here.
|
|
revng_assert(not BestTAPsWithIndices.empty());
|
|
return *BestTAPsWithIndices.begin();
|
|
}
|
|
|
|
struct ModelGEPArgs {
|
|
TypedBaseAddress BaseAddress = {};
|
|
ChildIndexVector IndexVector = {};
|
|
APInt RestOff;
|
|
|
|
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 << "\n}";
|
|
}
|
|
|
|
void dump() const debug_function { dump(llvm::dbgs()); }
|
|
};
|
|
|
|
static std::optional<model::QualifiedType>
|
|
getType(ModelGEPArgs &GEPArgs, model::VerifyHelper &VH) {
|
|
std::optional<model::QualifiedType> CurrType = std::nullopt;
|
|
|
|
if (GEPArgs.RestOff.ugt(0))
|
|
return CurrType;
|
|
|
|
CurrType = GEPArgs.BaseAddress.Type;
|
|
for (const auto &[Index, AggregateType] : GEPArgs.IndexVector) {
|
|
|
|
switch (AggregateType) {
|
|
|
|
case AggregateKind::Struct: {
|
|
|
|
CurrType = peelConstAndTypedefs(CurrType.value(), VH);
|
|
auto *S = cast<model::StructType>(CurrType->UnqualifiedType.get());
|
|
size_t FieldOffset = cast<ConstantInt>(Index)->getZExtValue();
|
|
CurrType = S->Fields.at(FieldOffset).Type;
|
|
|
|
} break;
|
|
|
|
case AggregateKind::Union: {
|
|
|
|
CurrType = peelConstAndTypedefs(CurrType.value(), VH);
|
|
auto *U = cast<model::UnionType>(CurrType->UnqualifiedType.get());
|
|
size_t FieldID = cast<ConstantInt>(Index)->getZExtValue();
|
|
CurrType = U->Fields.at(FieldID).Type;
|
|
|
|
} break;
|
|
|
|
case AggregateKind::Array: {
|
|
|
|
auto It = CurrType->Qualifiers.begin();
|
|
|
|
do {
|
|
CurrType = peelConstAndTypedefs(CurrType.value(), VH);
|
|
|
|
It = llvm::find_if(CurrType->Qualifiers, [](const model::Qualifier &Q) {
|
|
return Q.isArrayQualifier();
|
|
});
|
|
|
|
// 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(),
|
|
It,
|
|
[](const model::Qualifier &Q) {
|
|
return Q.isPointerQualifier();
|
|
}));
|
|
|
|
} while (It == CurrType->Qualifiers.end());
|
|
|
|
// 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(It),
|
|
CurrType->Qualifiers.end() });
|
|
|
|
} break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
return CurrType;
|
|
}
|
|
|
|
static std::optional<ModelGEPArgs>
|
|
makeBestGEPArgs(const TypedBaseAddress &TBA,
|
|
const IRAccessPattern &IRPattern,
|
|
const TAPToChildIdsMap &TAPIndices,
|
|
const model::Binary &Model,
|
|
model::VerifyHelper &VH) {
|
|
std::optional<ModelGEPArgs> Result = std::nullopt;
|
|
LLVMContext &Ctxt = TBA.Address->getContext();
|
|
|
|
revng_log(ModelGEPLog, "===============================");
|
|
revng_log(ModelGEPLog, "makeBestGEPArgs for TBA: " << TBA);
|
|
auto MakeBestGEPArgsIndent = LoggerIndent(ModelGEPLog);
|
|
|
|
const auto &[BestTAP,
|
|
BestIndices] = pickBestTAP(TBA.Type, IRPattern, TAPIndices, VH);
|
|
|
|
// 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;
|
|
|
|
const GEPSummationVector &IRPatternIndices = IRPattern.Indices;
|
|
auto IRIndicesIt = IRPatternIndices.begin();
|
|
auto IRIndicesEnd = IRPatternIndices.end();
|
|
|
|
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: {
|
|
// For arrays we have to fill up info about the index of the array
|
|
// access. It can be a constant or an llvm::Value, but it should never
|
|
// be already initialized.
|
|
revng_assert(not Id.Index);
|
|
revng_assert(Back.Type == AggregateKind::Array);
|
|
revng_assert(isArray(CurrentType));
|
|
|
|
model::QualifiedType Array = peelConstAndTypedefs(CurrentType, VH);
|
|
auto ArrayQualIt = Array.Qualifiers.begin();
|
|
auto QEnd = Array.Qualifiers.end();
|
|
|
|
revng_assert(ArrayQualIt != QEnd);
|
|
revng_assert(ArrayQualIt->isArrayQualifier());
|
|
|
|
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()));
|
|
|
|
// 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();
|
|
revng_assert(TAPArrayIt->Stride == ElementSize);
|
|
|
|
// The array in BestTAP that we're unwrapping has the same number of
|
|
// elements.
|
|
revng_assert(TAPArrayIt->NumElems == ArrayQualIt->Size);
|
|
|
|
if (RestOff.uge(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
|
|
APInt ElementIndex;
|
|
APInt::udivrem(RestOff,
|
|
APInt(/*bitwidth*/ 64, /*value*/ ElementSize),
|
|
ElementIndex,
|
|
RestOff);
|
|
Back.Index = ConstantInt::get(llvm::IntegerType::get(Ctxt,
|
|
64 /*NumBits*/),
|
|
ElementIndex);
|
|
|
|
} else {
|
|
// Here the remaining offset is smaller than an element size.
|
|
// So we have to look for a non-constant index.
|
|
|
|
revng_assert(IRIndicesIt != IRIndicesEnd);
|
|
const auto &[Coefficient, Index] = *IRIndicesIt;
|
|
|
|
// This should never happen because of how IRAccessPattern is built
|
|
revng_assert(not isa<ConstantInt>(Index));
|
|
|
|
// Coefficient should always have the same value of the element, so that
|
|
// the Index is actually the index in the array.
|
|
revng_assert(Coefficient->getValue() == ElementSize);
|
|
|
|
Back.Index = Index;
|
|
|
|
// The current IR indices have been handled, increase the iterator.
|
|
++IRIndicesIt;
|
|
}
|
|
|
|
// 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()));
|
|
|
|
// We also omve the TAPArrayIt to point to the next array info available
|
|
// in BestTAP
|
|
++TAPArrayIt;
|
|
continue;
|
|
|
|
} break;
|
|
|
|
case AggregateKind::Struct: {
|
|
const model::StructType *Struct = nullptr;
|
|
|
|
while (not Struct) {
|
|
// Skip over all the qualifiers. We only expect const qualifiers here.
|
|
// And we can basically ignore them.
|
|
for (const auto &Q : CurrentType.Qualifiers)
|
|
revng_assert(not Q.isPointerQualifier() and not Q.isArrayQualifier());
|
|
|
|
auto *Unqualified = CurrentType.UnqualifiedType.getConst();
|
|
Struct = dyn_cast<model::StructType>(Unqualified);
|
|
// If this is Unqualified was not a struct, the only valid thing for
|
|
// it is to be a Typedef, in which case we unwrap it and keep looking
|
|
// for a struct
|
|
if (not Struct) {
|
|
auto *TD = cast<model::TypedefType>(Unqualified);
|
|
CurrentType = TD->UnderlyingType;
|
|
}
|
|
}
|
|
|
|
// Index represents the offset of a field in the struct
|
|
uint64_t FieldOff = cast<ConstantInt>(Back.Index)->getZExtValue();
|
|
|
|
// 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, and we have to bail out.
|
|
if (RestOff.ult(FieldOff))
|
|
return Result;
|
|
|
|
APInt OffsetInField = RestOff - FieldOff;
|
|
auto &FieldType = Struct->Fields.at(FieldOff).Type;
|
|
if (OffsetInField.uge(*FieldType.size())) {
|
|
Result = ModelGEPArgs{ .BaseAddress = TBA,
|
|
.IndexVector = std::move(Indices),
|
|
.RestOff = RestOff };
|
|
return Result;
|
|
}
|
|
|
|
// Then we subtract the field offset from the remaining offset
|
|
RestOff = OffsetInField;
|
|
CurrentType = FieldType;
|
|
} break;
|
|
|
|
case AggregateKind::Union: {
|
|
const model::UnionType *Union = nullptr;
|
|
|
|
while (not Union) {
|
|
// Skip over all the qualifiers. We only expect const qualifiers here.
|
|
// And we can basically ignore them.
|
|
for (const auto &Q : CurrentType.Qualifiers)
|
|
revng_assert(not Q.isPointerQualifier() and not Q.isArrayQualifier());
|
|
|
|
auto *Unqualified = CurrentType.UnqualifiedType.get();
|
|
Union = dyn_cast<model::UnionType>(Unqualified);
|
|
// If this is Unqualified was not a union, the only valid thing for
|
|
// it is to be a Typedef, in which case we unwrap it and keep looking
|
|
// for a union
|
|
if (not Union) {
|
|
auto *TD = cast<model::TypedefType>(Unqualified);
|
|
CurrentType = TD->UnderlyingType;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
uint64_t FieldId = cast<ConstantInt>(Back.Index)->getZExtValue();
|
|
auto &FieldType = Union->Fields.at(FieldId).Type;
|
|
if (RestOff.uge(*FieldType.size())) {
|
|
Result = ModelGEPArgs{ .BaseAddress = TBA,
|
|
.IndexVector = std::move(Indices),
|
|
.RestOff = RestOff };
|
|
return Result;
|
|
}
|
|
|
|
CurrentType = FieldType;
|
|
|
|
} break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
revng_assert(RestOff.isNonNegative());
|
|
Result = ModelGEPArgs{ .BaseAddress = TBA,
|
|
.IndexVector = std::move(Indices),
|
|
.RestOff = RestOff };
|
|
|
|
return Result;
|
|
}
|
|
|
|
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 ValueModelTypesMap &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()) {
|
|
|
|
revng_log(ModelGEPLog, "Use is typed!");
|
|
|
|
auto &[AddressVal, Type] = *TypeIt;
|
|
|
|
revng_assert(isPointer(Type));
|
|
|
|
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);
|
|
|
|
if ((nullptr != Op0Const) xor (nullptr != Op1Const)) {
|
|
auto *ConstOp = Op1Const ? Op1Const : Op0Const;
|
|
auto *OtherOp = Op1Const ? Op0Const : Op1Const;
|
|
|
|
// The constant operand is the coefficient, while the other is the
|
|
// index.
|
|
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 = 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());
|
|
|
|
// 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::Trunc:
|
|
case Instruction::Load:
|
|
case Instruction::Call:
|
|
case Instruction::PHI:
|
|
case Instruction::Select:
|
|
case Instruction::ExtractValue:
|
|
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)) {
|
|
|
|
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 ValueModelTypesMap &PointerTypes) {
|
|
return getGEPSumImpl(AddressUse, PointerTypes);
|
|
}
|
|
};
|
|
|
|
class TypedAccessCache {
|
|
QualifiedTypeToTAPChildIdsMap TAPCache;
|
|
|
|
// Builds a map of all the possible TypedAccessPattern starting from
|
|
// BaseType, mapping them to the vector of child indices that need to be
|
|
// traversed on the type system to access types represented by that TAP.
|
|
RecursiveCoroutine<TAPToChildIdsMapConstRef>
|
|
getTAPImpl(const model::QualifiedType &BaseType, LLVMContext &Ctxt) {
|
|
|
|
revng_log(ModelGEPLog, "getTAPImpl for BaseType: " << BaseType);
|
|
auto Indent = LoggerIndent{ ModelGEPLog };
|
|
|
|
auto It = TAPCache.lower_bound(BaseType);
|
|
// If we cannot find it we have to build it
|
|
if (It == TAPCache.end() or TAPCache.key_comp()(BaseType, It->first)) {
|
|
revng_log(ModelGEPLog, "Not found. Build it!");
|
|
auto MoreIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
// Initialize a new map, that we need to fill with the results for
|
|
// BaseType.
|
|
TAPToChildIdsMap Result;
|
|
|
|
// First, we need to build a new TAP representing the access pattern to
|
|
// BaseType itself
|
|
TypedAccessPattern NewTAP = {
|
|
// 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 = std::move(BaseType),
|
|
};
|
|
|
|
// The new TAP has no associated child ids, since its not accessing any
|
|
// child of the BaseType, but the type itself
|
|
Result[NewTAP] = {};
|
|
|
|
if (BaseType.Qualifiers.empty()) {
|
|
revng_log(ModelGEPLog, "No qualifiers!");
|
|
auto EvenMoreIndent = LoggerIndent{ ModelGEPLog };
|
|
const model::Type *BaseT = BaseType.UnqualifiedType.get();
|
|
|
|
switch (BaseT->Kind) {
|
|
|
|
// If we've reached a primitive type or an enum type we're done. The
|
|
// NewTAP added above to Results is enough and we don't need to
|
|
// traverse anything.
|
|
case model::TypeKind::Primitive: {
|
|
revng_log(ModelGEPLog, "Primitive. Done!");
|
|
} break;
|
|
case model::TypeKind::Enum: {
|
|
revng_log(ModelGEPLog, "Enum. Done!");
|
|
} break;
|
|
|
|
case model::TypeKind::Struct: {
|
|
revng_log(ModelGEPLog, "Struct, look at fields");
|
|
const auto *S = cast<model::StructType>(BaseT);
|
|
auto StructIndent = LoggerIndent{ ModelGEPLog };
|
|
for (const model::StructField &Field : S->Fields) {
|
|
revng_log(ModelGEPLog, "Field at offset: " << Field.Offset);
|
|
auto FieldIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
// First, traverse each child's type to get the TAPs from it
|
|
TAPToChildIdsMap FieldResult = rc_recur getTAPImpl(Field.Type,
|
|
Ctxt);
|
|
|
|
revng_log(ModelGEPLog,
|
|
"Number of types inside field: " << FieldResult.size());
|
|
// Then, create a ChildInfo representing the traversal of the
|
|
// children. In particular, this has a known index, that
|
|
// represents the offset of the field in the struct.
|
|
ChildInfo CI{
|
|
.Index = ConstantInt::get(llvm::IntegerType::get(Ctxt,
|
|
64 /*NumBits*/),
|
|
Field.Offset /*Value*/),
|
|
.Type = AggregateKind::Struct
|
|
};
|
|
|
|
// Now iterate on data in InnerResult, and massage them to add the
|
|
// field offset to the TAP, as well as the child info to the child
|
|
// ids, before actually merging them into Result.
|
|
auto InnerTAPIt = FieldResult.begin();
|
|
auto InnerTAPEnd = FieldResult.end();
|
|
while (InnerTAPIt != InnerTAPEnd) {
|
|
// Save the next valid value of the iterator, because we're
|
|
// going to extract the pointee of InnerTAPIt and mess with it
|
|
// before inserting into Result, and that would make it
|
|
// impossible to properly continue the iteration on InnerResult
|
|
// otherwise.
|
|
auto InnerTAPNext = std::next(InnerTAPIt);
|
|
|
|
auto TAPWithIdsHandle = FieldResult.extract(InnerTAPIt);
|
|
|
|
// Add the Field.Offset to the Base offset
|
|
auto &BaseOffset = TAPWithIdsHandle.key().BaseOffset;
|
|
BaseOffset += Field.Offset;
|
|
|
|
// Prepend the info on this struct to the child ids in the inner
|
|
// result.
|
|
auto &ChildIds = TAPWithIdsHandle.mapped();
|
|
ChildIds.insert(ChildIds.begin(), CI);
|
|
|
|
Result.insert(std::move(TAPWithIdsHandle));
|
|
|
|
// Increment the iterator.
|
|
InnerTAPIt = InnerTAPNext;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case model::TypeKind::Union: {
|
|
revng_log(ModelGEPLog, "Union, look at fields");
|
|
const auto *U = cast<model::UnionType>(BaseT);
|
|
auto UnionIndent = LoggerIndent{ ModelGEPLog };
|
|
for (const model::UnionField &Field : U->Fields) {
|
|
revng_log(ModelGEPLog, "Field ID: " << Field.Index);
|
|
auto FieldIndent = LoggerIndent{ ModelGEPLog };
|
|
|
|
// First, traverse each child's type to get the TAPs from it
|
|
TAPToChildIdsMap FieldResult = rc_recur getTAPImpl(Field.Type,
|
|
Ctxt);
|
|
|
|
revng_log(ModelGEPLog,
|
|
"Number of types inside field: " << FieldResult.size());
|
|
// Then, create a ChildInfo representing the traversal of the
|
|
// children. In particular, this has a known index, that
|
|
// represents the number of the field in the struct (not its
|
|
// offset in this case)
|
|
ChildInfo CI{
|
|
.Index = ConstantInt::get(llvm::IntegerType::get(Ctxt,
|
|
64 /*NumBits*/),
|
|
Field.Index /*Value*/),
|
|
.Type = AggregateKind::Union
|
|
};
|
|
|
|
// Now iterate on data in InnerResult, and massage them to add the
|
|
// field offset to the TAP, as well as the child info to the child
|
|
// ids, before actually merging them into Result.
|
|
auto InnerTAPIt = FieldResult.begin();
|
|
auto InnerTAPEnd = FieldResult.end();
|
|
while (InnerTAPIt != InnerTAPEnd) {
|
|
// Save the next valid value of the iterator, because we're
|
|
// going to extract the pointee of InnerTAPIt and mess with it
|
|
// before inserting into Result, and that would make it
|
|
// impossible to properly continue the iteration on InnerResult
|
|
// otherwise.
|
|
auto InnerTAPNext = std::next(InnerTAPIt);
|
|
|
|
auto TAPWithIdsHandle = FieldResult.extract(InnerTAPIt);
|
|
|
|
// Prepend the info on this struct to the child ids in the inner
|
|
// result.
|
|
auto &ChildIds = TAPWithIdsHandle.mapped();
|
|
ChildIds.insert(ChildIds.begin(), CI);
|
|
|
|
Result.insert(std::move(TAPWithIdsHandle));
|
|
|
|
// Increment the iterator.
|
|
InnerTAPIt = InnerTAPNext;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case model::TypeKind::Typedef: {
|
|
revng_log(ModelGEPLog, "Typedef, unwrap");
|
|
// For typedefs, we need to unwrap the underlying type and try to
|
|
// traverse it.
|
|
const auto *TD = cast<model::TypedefType>(BaseT);
|
|
TAPToChildIdsMap InnerResult = rc_recur getTAPImpl(TD->UnderlyingType,
|
|
Ctxt);
|
|
// The InnerResult can just be merged into the Result, because
|
|
// typedefs are shallow names that don't really add ids to the
|
|
// traversal of the typesystem.
|
|
Result.merge(std::move(InnerResult));
|
|
} break;
|
|
|
|
case model::TypeKind::RawFunctionType:
|
|
case model::TypeKind::CABIFunctionType: {
|
|
revng_abort();
|
|
} break;
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
} else {
|
|
revng_log(ModelGEPLog,
|
|
"Has qualifiers: " << BaseType.Qualifiers.size());
|
|
auto EvenMoreIndent = LoggerIndent{ ModelGEPLog };
|
|
const model::Qualifier &FirstQualifier = *BaseType.Qualifiers.begin();
|
|
|
|
// If the first qualifier is a pointer qualifier, we're done
|
|
// descending, because the pointee does not reside into the BaseType,
|
|
// it's only referenced by it. 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.
|
|
if (not FirstQualifier.isPointerQualifier()) {
|
|
revng_log(ModelGEPLog, "FirstQualifier is not ConstQualifier");
|
|
|
|
auto QIt = std::next(BaseType.Qualifiers.begin());
|
|
auto QEnd = BaseType.Qualifiers.end();
|
|
auto InnerType = model::QualifiedType(BaseType.UnqualifiedType,
|
|
{ QIt, QEnd });
|
|
|
|
// First, compute the InnerResult, which represents all the TAPs
|
|
// from the InnerType going downward. At this point we do make a
|
|
// copy of it, because we'll need to change it with information on
|
|
// BaseType
|
|
TAPToChildIdsMap InnerResult = rc_recur getTAPImpl(InnerType, Ctxt);
|
|
|
|
if (not FirstQualifier.isConstQualifier()) {
|
|
// If the first qualifier is const, we can just use the
|
|
// InnerResult for BaseType as well.
|
|
// Otherwise, the first qualifier is an array, and we need to
|
|
// handle that.
|
|
revng_assert(FirstQualifier.isArrayQualifier());
|
|
revng_log(ModelGEPLog, "FirstQualifier is not ConstQualifier");
|
|
|
|
// First, build the array info associated to the array we're
|
|
// handling.
|
|
uint64_t NElems = FirstQualifier.Size;
|
|
revng_assert(InnerType.size());
|
|
uint64_t Stride = *InnerType.size();
|
|
ArrayInfo AI{ .Stride = APInt(/*NumBits*/ 64, /*Value*/ Stride),
|
|
.NumElems = APInt(/*NumBits*/ 64,
|
|
/*Value*/ NElems) };
|
|
|
|
// Second, build the child info associated to the array we're
|
|
// handling. In this case we initialize the Index to nullptr,
|
|
// because at this point we don't really know the index used for
|
|
// accessing the array. This will be fixed up later, whenever we
|
|
// have elected the best TypedAccessPattern or the given
|
|
// IRAccessPattern. At that point the Index will be expanded with
|
|
// an actual llvm::Value.
|
|
ChildInfo CI{ .Index = nullptr, .Type = AggregateKind::Array };
|
|
|
|
// Now iterate on data in InnerResult, and massage them to add
|
|
// array info before actually merging them into Result.
|
|
auto InnerTAPIt = InnerResult.begin();
|
|
auto InnerTAPEnd = InnerResult.end();
|
|
while (InnerTAPIt != InnerTAPEnd) {
|
|
// Save the next valid value of the iterator, because we're
|
|
// going to extract the pointee of InnerTAPIt and mess with it
|
|
// before inserting into Result, and that would make it
|
|
// impossible to properly continue the iteration on InnerResult
|
|
// otherwise.
|
|
auto InnerTAPNext = std::next(InnerTAPIt);
|
|
|
|
auto TAPWithIdsHandle = InnerResult.extract(InnerTAPIt);
|
|
|
|
// Prepend the info on this array to the Arrays info in the
|
|
// inner result.
|
|
auto &Arrays = TAPWithIdsHandle.key().Arrays;
|
|
Arrays.insert(Arrays.begin(), AI);
|
|
|
|
// Prepend the info on this array to the child ids in the inner
|
|
// result.
|
|
auto &ChildIds = TAPWithIdsHandle.mapped();
|
|
ChildIds.insert(ChildIds.begin(), CI);
|
|
|
|
Result.insert(std::move(TAPWithIdsHandle));
|
|
|
|
// Increment the iterator.
|
|
InnerTAPIt = InnerTAPNext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
revng_log(ModelGEPLog, "Result.size() = " << Result.size());
|
|
It = TAPCache.insert(It, { BaseType, std::move(Result) });
|
|
} else {
|
|
revng_log(ModelGEPLog, "Found!");
|
|
}
|
|
|
|
rc_return It->second;
|
|
}
|
|
|
|
public:
|
|
const TAPToChildIdsMap &
|
|
getTAP(const model::QualifiedType &BaseType, LLVMContext &Ctxt) {
|
|
return static_cast<TAPToChildIdsMapConstRef>(getTAPImpl(BaseType, Ctxt))
|
|
.get();
|
|
}
|
|
|
|
void clear() { TAPCache.clear(); }
|
|
};
|
|
|
|
using UseGEPInfoMap = std::map<Use *, ModelGEPArgs>;
|
|
|
|
static UseGEPInfoMap
|
|
makeGEPReplacements(llvm::Function &F, const model::Binary &Model) {
|
|
|
|
UseGEPInfoMap Result;
|
|
|
|
const model::Function &ModelF = getModelFunction(Model, F);
|
|
|
|
// 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.
|
|
ValueModelTypesMap PointerTypes = initializeModelTypes(F, ModelF, Model);
|
|
if (PointerTypes.empty()) {
|
|
revng_log(ModelGEPLog, "Model Types not found for " << F.getName());
|
|
return Result;
|
|
}
|
|
|
|
GEPSummationCache GEPSumCache{ Model };
|
|
TypedAccessCache TAPCache;
|
|
model::VerifyHelper VH;
|
|
|
|
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)) {
|
|
auto *Callee = CallI->getCalledFunction();
|
|
if (not Callee or not FunctionTags::Lifted.isTagOf(Callee)) {
|
|
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 (auto *CallUser = dyn_cast<CallInst>(U.getUser())) {
|
|
if (&U == &CallUser->getCalledOperandUse()) {
|
|
revng_log(ModelGEPLog, "Skipping callee operand in CallInst");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Skip all but the pointer operands of load and store instructions
|
|
if (auto *Load = dyn_cast<LoadInst>(U.getUser())) {
|
|
if (U.getOperandNo() != Load->getPointerOperandIndex()) {
|
|
revng_log(ModelGEPLog, "Skipping non-pointer operand in LoadInst");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (auto *Store = dyn_cast<StoreInst>(U.getUser())) {
|
|
if (U.getOperandNo() != Store->getPointerOperandIndex()) {
|
|
revng_log(ModelGEPLog, "Skipping non-pointer operand in LoadInst");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Skip booleans, since they cannot be addresses
|
|
if (auto *IntTy = dyn_cast<llvm::IntegerType>(U.get()->getType())) {
|
|
if (IntTy->getIntegerBitWidth() == 1) {
|
|
revng_log(ModelGEPLog, "Skipping i1 value");
|
|
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;
|
|
const auto &TAPToChildIds = TAPCache.getTAP(BaseTy, Ctxt);
|
|
|
|
// If the set of typed access patterns from BaseTy is empty we can skip
|
|
// to the next instruction
|
|
if (TAPToChildIds.empty())
|
|
continue;
|
|
|
|
// Now we extract an IRAccessPattern from the ModelGEPSummation
|
|
IRAccessPattern IRPattern = computeAccessPattern(U, GEPSum, Model);
|
|
|
|
// Select among the computed TAPIndices the one which best fits the
|
|
// IRPattern
|
|
auto BestGEPArgsOrNone = makeBestGEPArgs(GEPSum.BaseAddress,
|
|
IRPattern,
|
|
TAPToChildIds,
|
|
Model,
|
|
VH);
|
|
|
|
// If the selection failed, we bail out.
|
|
if (not BestGEPArgsOrNone.has_value())
|
|
continue;
|
|
|
|
ModelGEPArgs &GEPArgs = BestGEPArgsOrNone.value();
|
|
|
|
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,
|
|
VH);
|
|
if (GEPTypeOrNone.has_value()) {
|
|
model::QualifiedType &GEPType = GEPTypeOrNone.value();
|
|
if (isPointer(GEPType))
|
|
PointerTypes[Load] = GEPType;
|
|
}
|
|
}
|
|
|
|
Result[&U] = GEPArgs;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
class ModelGEPArgCache {
|
|
|
|
std::map<model::QualifiedType, GlobalVariable *, QTLess>
|
|
GlobalModelGEPTypeArgs;
|
|
|
|
public:
|
|
Value *
|
|
getModelGEPQualifiedTypeArg(model::QualifiedType &QT, llvm::Module &M) {
|
|
auto It = GlobalModelGEPTypeArgs.find(QT);
|
|
if (It != GlobalModelGEPTypeArgs.end())
|
|
return It->second;
|
|
|
|
std::string SerializedQT;
|
|
{
|
|
llvm::raw_string_ostream StringStream(SerializedQT);
|
|
llvm::yaml::Output YAMLOutput(StringStream);
|
|
YAMLOutput << QT;
|
|
}
|
|
It = GlobalModelGEPTypeArgs
|
|
.insert({ QT, buildString(&M, SerializedQT, "") })
|
|
.first;
|
|
return It->second;
|
|
}
|
|
};
|
|
|
|
bool MakeModelGEPPass::runOnFunction(llvm::Function &F) {
|
|
bool Changed = false;
|
|
|
|
// Skip non-isolated functions
|
|
if (not FunctionTags::Lifted.isTagOf(&F))
|
|
return Changed;
|
|
|
|
// If the `-single-decompilation` option was passed from command line, skip
|
|
// decompilation for all the functions that are not the selected one.
|
|
if (not TargetFunction.empty())
|
|
if (not F.hasName() or not F.getName().equals(TargetFunction.c_str()))
|
|
return Changed;
|
|
|
|
revng_log(ModelGEPLog, "Make ModelGEP for " << F.getName());
|
|
auto Indent = LoggerIndent(ModelGEPLog);
|
|
|
|
auto &Model = getAnalysis<LoadModelWrapperPass>().get().getReadOnlyModel();
|
|
|
|
UseGEPInfoMap GEPReplacementMap = makeGEPReplacements(F, Model);
|
|
|
|
llvm::Module &M = *F.getParent();
|
|
LLVMContext &Ctxt = M.getContext();
|
|
IRBuilder<> Builder(Ctxt);
|
|
ModelGEPArgCache TypeArgCache;
|
|
for (auto &[TheUseToGEPify, GEPArgs] : GEPReplacementMap) {
|
|
|
|
revng_log(ModelGEPLog,
|
|
"GEPify use of: " << dumpToString(TheUseToGEPify->get()));
|
|
revng_log(ModelGEPLog,
|
|
" `-> use in: " << dumpToString(TheUseToGEPify->getUser()));
|
|
|
|
llvm::Type *IType = TheUseToGEPify->get()->getType();
|
|
auto *ModelGEPFunction = getModelGEP(M, IType);
|
|
|
|
// 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;
|
|
Args.push_back(TypeArgCache.getModelGEPQualifiedTypeArg(BaseType, M));
|
|
|
|
// The second argument is the base address
|
|
Args.push_back(GEPArgs.BaseAddress.Address);
|
|
|
|
// The other arguments are the indices in IndexVector
|
|
for (auto [ChildId, AggregateTy] : GEPArgs.IndexVector) {
|
|
revng_assert(isa<ConstantInt>(ChildId)
|
|
or AggregateTy == AggregateKind::Array);
|
|
Args.push_back(ChildId);
|
|
}
|
|
|
|
// Insert a call to revng_model_gep right before the use, special casing the
|
|
// uses that are incoming for PHI nodes.
|
|
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);
|
|
}
|
|
|
|
Value *ModelGEP = Builder.CreateCall(ModelGEPFunction, Args);
|
|
|
|
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
|
|
revng_assert(IType->isIntOrPtrTy());
|
|
|
|
// First, cast it to int if necessary.
|
|
if (IType->isPointerTy()) {
|
|
auto *IntType = llvm::IntegerType::get(Ctxt,
|
|
GEPArgs.RestOff.getBitWidth());
|
|
ModelGEP = Builder.CreatePtrToInt(ModelGEP, IntType);
|
|
}
|
|
|
|
// Then, inject the actuall add
|
|
auto GEPResultBitWidth = ModelGEP->getType()->getIntegerBitWidth();
|
|
APInt OffsetToAdd = GEPArgs.RestOff.zextOrSelf(GEPResultBitWidth);
|
|
ModelGEP = Builder.CreateAdd(ModelGEP,
|
|
ConstantInt::get(Ctxt, OffsetToAdd));
|
|
|
|
// Finally, convert it back to pointer.
|
|
if (IType->isPointerTy())
|
|
ModelGEP = Builder.CreateIntToPtr(ModelGEP, IType);
|
|
}
|
|
|
|
// Finally, replace the use to gepify with the call to modelGEP, plus the
|
|
// potential arithmetic we've just build.
|
|
TheUseToGEPify->set(ModelGEP);
|
|
revng_log(ModelGEPLog, " `-> replaced with: " << dumpToString(ModelGEP));
|
|
|
|
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 revng_model_gep ",
|
|
false,
|
|
false);
|