// // Copyright rev.ng Labs Srl. See LICENSE.md for details. // #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/ADT/RecursiveCoroutine.h" #include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h" #include "revng/EarlyFunctionAnalysis/IRHelpers.h" #include "revng/Model/Architecture.h" #include "revng/Model/Binary.h" #include "revng/Model/LoadModelPass.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::None; 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; 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(); } }; // 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; 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; struct IRAccessPattern { APInt BaseOffset = APInt(/*NumBits*/ 64, /*Value*/ 0); GEPSummationVector Indices = {}; std::optional 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; static IRAccessPattern computeAccessPattern(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; 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(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, .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(U.getUser())) { if (auto *Load = dyn_cast(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(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(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 model::Type *FType = MF->Prototype.get(); revng_assert(FType); if (const auto *RFT = dyn_cast(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 (ModT.isPointer()) { auto _ = LoggerIndent(ModelGEPLog); revng_log(ModelGEPLog, "llvm::ReturnInst: " << dumpToString(Ret)); revng_log(ModelGEPLog, "Pointee: model::QualifiedType: " << serializeToString(ModT)); IRPattern.PointeeType = dropPointer(ModT); } } else { auto *RetVal = Ret->getReturnValue(); auto *StructTy = cast(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(RetVal)->getCalledFunction(); revng_assert(FunctionTags::StructInitializer.isTagOf(Returned)); } } else if (const auto *CFT = dyn_cast(FType)) { revng_log(ModelGEPLog, "Has CABIFunctionType prototype."); // If the callee function does not return anything, skip to the next // instruction. if (CFT->ReturnType.isVoid()) { 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(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(CalledPrototype)) { revng_log(ModelGEPLog, "Has RawFunctionType prototype."); revng_assert(RFT->ReturnValues.size() > 1); auto *StructTy = cast(CalledF->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 (RetTy.isPointer()) { model::QualifiedType Pointee = dropPointer(RetTy); revng_log(ModelGEPLog, "Pointee: " << serializeToString(Pointee)); IRPattern.PointeeType = Pointee; } } else if (auto *CFT = dyn_cast(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 = getCallSitePrototype(Model, Call); revng_assert(Proto.isValid()); if (const auto *RFT = dyn_cast(Proto.get())) { auto MoreIndent = LoggerIndent(ModelGEPLog); auto ModelArgSize = RFT->Arguments.size(); revng_assert(RFT->StackArgumentsType.Qualifiers.empty()); revng_assert((ModelArgSize == Call->arg_size() - 1 and RFT->StackArgumentsType.UnqualifiedType.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(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(UserInstr)) { } } return IRPattern; } static bool compareQualifiedTypes(const model::QualifiedType &LHS, const model::QualifiedType &RHS) { if (LHS.Qualifiers < RHS.Qualifiers) return true; if (LHS.Qualifiers > RHS.Qualifiers) return false; return LHS.UnqualifiedType.get() < RHS.UnqualifiedType.get(); } 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; 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 }; static 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; using TAPToChildIdsMap = std::map; using QTLess = std::integral_constant; // clang-format off using QualifiedTypeToTAPChildIdsMap = std::map; // clang-format on using TAPToChildIdsMapConstRef = std::reference_wrapper; struct DifferenceScore { // Among two DifferenceScore, the one with PerfectTypeMatch set to true is // always the lowest. bool PerfectTypeMatch = 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::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::min(); // Boolean to mark out-of-range accesses bool InRange = false; 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; } 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()); } }; struct ScoredIndices { // The score is optional, nullopt means that the difference score is infinity std::optional Score = std::nullopt; ChildIndexVector Indices = {}; static ScoredIndices invalid() { return ScoredIndices{}; } static ScoredIndices outOfBound(size_t DiffScore) { return ScoredIndices{ .Score = DifferenceScore{ .PerfectTypeMatch = false, .Difference = DiffScore, .ExactSize = false, .Depth = 0, .InRange = false }, .Indices{} }; } static ScoredIndices nestedOutOfBound(size_t DiffScore, size_t Depth, ChildIndexVector &&Indices) { revng_assert(Depth <= Indices.size()); if (Depth < Indices.size()) Indices.resize(Depth); auto Score = DifferenceScore{ .PerfectTypeMatch = false, .Difference = DiffScore, .ExactSize = false, .Depth = Depth, .InRange = false }; return ScoredIndices{ .Score = std::move(Score), .Indices = std::move(Indices) }; } }; static ScoredIndices differenceScore(const model::QualifiedType &BaseType, const TAPToChildIdsMap::value_type &TAPWithIndices, const IRAccessPattern &IRAP, model::VerifyHelper &VH) { const auto &[TAP, ChildIndices] = TAPWithIndices; ChildIndexVector ResultIndices = ChildIndices; size_t BaseSize = *BaseType.size(); if (IRAP.BaseOffset.uge(BaseSize)) return ScoredIndices::outOfBound(IRAP.BaseOffset.getZExtValue()); 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; size_t Depth = 0; 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 Normalized.isPointer()); switch (ChildID.Type) { case Struct: { revng_assert(not Normalized.isArray()); auto *S = cast(Normalized.UnqualifiedType.getConst()); size_t FieldOffset = cast(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; } break; case Union: { revng_assert(not Normalized.isArray()); auto *U = cast(Normalized.UnqualifiedType.get()); size_t FieldID = cast(ChildID.Index)->getZExtValue(); 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); // TODO: in principle we have noguarantee here that // // ArrayQualIt->Size == ArrayInfoIt->NumElems // // In fact: // - ArrayQualIt->Size is the size of the array type that we reach // traversing the IR // - ArrayInfoIt->NumElems is the size of the array type corresponding to // this TAP (hence coming from the pure type system, without looking at // the IR) // // This two are not necessarily the same. // When ArrayQualIt->Size - ArrayInfoIt->NumElems is: // - 0 the IR and the type system agree on the number of elements of // the array. // - > 0 the IR thinks there are less elements than the type system so the // TAP might not be perfect but we know for sure that if we pick this as // best we're not emitting C code that accesses elements out of bound // (because C code is emitted according to the IR). // - < 0 the IR thinks there are more elements than the type system, so // the TAP is worse then the >0 case, because if we emit the C code // according to the IR we could access an element that is out of bound // for the TAP (even if only with an dynamic index, the static index is // taken into account already and scored very poorly). // // At the moment this information is not used for scoring the difference, // so whichever is evalutated first wins. // We also don't have evidence of situations where this skews the // selection of the best TAP to a suboptimal choice, so we haven't // dedicated effort to iron this out. // // In the future we might need to take this into account. 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. 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.getZExtValue(), Depth, std::move(ResultIndices)); } RestOff = OffInElem; } else { revng_assert(not isa(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; } 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()) SameSize = *TAP.AccessedType.size() == *IRAP.PointeeType.value().size(); return ScoredIndices{ .Score = DifferenceScore{ .PerfectTypeMatch = PerfectMatch, .Difference = RestOff.getZExtValue(), .ExactSize = SameSize, .Depth = Depth, .InRange = true }, .Indices = std::move(ResultIndices), }; } static std::pair 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 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; 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 getType(ModelGEPArgs &GEPArgs, model::VerifyHelper &VH) { std::optional 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(CurrType->UnqualifiedType.getConst()); size_t FieldOffset = cast(Index)->getZExtValue(); CurrType = S->Fields.at(FieldOffset).Type; } break; case AggregateKind::Union: { CurrType = peelConstAndTypedefs(CurrType.value(), VH); auto *U = cast(CurrType->UnqualifiedType.getConst()); size_t FieldID = cast(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, 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(), It, model::Qualifier::isPointer)); } 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 makeBestGEPArgs(const TypedBaseAddress &TBA, const IRAccessPattern &IRPattern, const TAPToChildIdsMap &TAPIndices, const model::Binary &Model, model::VerifyHelper &VH) { std::optional 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(CurrentType.isArray()); model::QualifiedType Array = peelConstAndTypedefs(CurrentType, VH); 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())); // 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); 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. if (IRIndicesIt != IRIndicesEnd) { const auto &[Coefficient, Index] = *IRIndicesIt; // This should never happen because of how IRAccessPattern is built revng_assert(not isa(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; } else { // If we ran out of indices in IRAP there's no offset left, so // consider this to be zero revng_assert(RestOff.isNullValue()); Back.Index = ConstantInt::get(llvm::IntegerType::get(Ctxt, 64 /*NumBits*/), RestOff); } } // 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 model::Qualifier::isPointer(Q) and not model::Qualifier::isArray(Q)); auto *Unqualified = CurrentType.UnqualifiedType.getConst(); Struct = dyn_cast(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(Unqualified); CurrentType = TD->UnderlyingType; } } // Index represents the offset of a field in the struct uint64_t FieldOff = cast(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, .PointeeType = FieldType }; 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 model::Qualifier::isPointer(Q) and not model::Qualifier::isArray(Q)); auto *Unqualified = CurrentType.UnqualifiedType.get(); Union = dyn_cast(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(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(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, .PointeeType = FieldType }; return Result; } CurrentType = FieldType; } break; default: revng_abort(); } } revng_assert(RestOff.isNonNegative()); Result = ModelGEPArgs{ .BaseAddress = TBA, .IndexVector = std::move(Indices), .RestOff = RestOff, .PointeeType = CurrentType }; 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 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(AddressArith)) { revng_log(ModelGEPLog, "Traverse cast!"); Result = makeOffsetGEPSummation(AddressArith); } else if (isa(AddressArith)) { auto *AddrArithmeticInst = dyn_cast(AddressArith); auto *ConstExprAddrArith = dyn_cast(AddressArith); if (ConstExprAddrArith) AddrArithmeticInst = ConstExprAddrArith->getAsInstruction(); switch (AddrArithmeticInst->getOpcode()) { case Instruction::Add: { auto *Add = cast(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(Op0); auto *Op1 = AddrArithmeticInst->getOperand(1); auto *Op1Const = dyn_cast(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(ShiftedBits)) { if (ConstShift->getValue().isNonNegative()) { // Build the stride auto *AddrType = AddrArithmeticInst->getType(); auto *ArithTy = cast(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(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(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(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(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); } }; 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 getTAPImpl(const model::QualifiedType &BaseType, LLVMContext &Ctxt) { revng_log(ModelGEPLog, "getTAPImpl for BaseType: " << serializeToString(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::PrimitiveType: { revng_log(ModelGEPLog, "Primitive. Done!"); } break; case model::TypeKind::EnumType: { revng_log(ModelGEPLog, "Enum. Done!"); } break; case model::TypeKind::StructType: { revng_log(ModelGEPLog, "Struct, look at fields"); const auto *S = cast(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::UnionType: { revng_log(ModelGEPLog, "Union, look at fields"); const auto *U = cast(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::TypedefType: { revng_log(ModelGEPLog, "Typedef, unwrap"); // For typedefs, we need to unwrap the underlying type and try to // traverse it. const auto *TD = cast(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 model::Qualifier::isPointer(FirstQualifier)) { 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 model::Qualifier::isConst(FirstQualifier)) { // 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(model::Qualifier::isArray(FirstQualifier)); 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(getTAPImpl(BaseType, Ctxt)) .get(); } void clear() { TAPCache.clear(); } }; using UseGEPInfoMap = std::map; static UseGEPInfoMap makeGEPReplacements(llvm::Function &F, const model::Binary &Model) { 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(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; model::VerifyHelper VH; 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(&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(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(U.getUser())) { if (not isa(CallUser->getCalledOperand())) { if (&U == &CallUser->getCalledOperandUse()) { revng_log(ModelGEPLog, "Skipping callee operand in CallInst"); continue; } } } // Skip all but the pointer operands of load instructions if (auto *Load = dyn_cast(U.getUser())) { if (U.getOperandNo() != Load->getPointerOperandIndex()) { revng_log(ModelGEPLog, "Skipping non-pointer operand in LoadInst"); continue; } } // Skip booleans, since they cannot be addresses if (auto *IntTy = dyn_cast(U.get()->getType())) { if (IntTy->getIntegerBitWidth() == 1) { 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(U.get()) or isa(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; 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, PointerTypes, GEPifiedUsedTypes); // 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(); { model::QualifiedType UseType = GEPArgs.PointeeType; addPointerQualifier(UseType, Model); GEPifiedUsedTypes.insert({ &U, UseType }); } 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(&I)) { std::optional GEPTypeOrNone = getType(GEPArgs, 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 GlobalModelGEPTypeArgs; public: Value *getQualifiedTypeArg(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, buildStringPtr(&M, SerializedQT, "") }) .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().get().getReadOnlyModel(); UseGEPInfoMap GEPReplacementMap = makeGEPReplacements(F, *Model); llvm::Module &M = *F.getParent(); LLVMContext &Ctxt = M.getContext(); IRBuilder<> Builder(Ctxt); ModelGEPArgCache TypeArgCache; // Create a function pool for AddressOf calls OpaqueFunctionsPool 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 { const auto &PointeeSize = GEPArgs.PointeeType.size(); 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 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); // The other arguments are the indices in IndexVector for (auto [ChildId, AggregateTy] : GEPArgs.IndexVector) { revng_assert(isa(ChildId) or AggregateTy == AggregateKind::Array); Args.push_back(ChildId); } // Insert a call to ModelGEP right before the use, special casing the // uses that are incoming for PHI nodes. auto *UserInstr = cast(TheUseToGEPify->getUser()); if (auto *PHIUser = dyn_cast(UserInstr)) { auto *IncomingB = PHIUser->getIncomingBlock(*TheUseToGEPify); Builder.SetInsertPoint(IncomingB->getTerminator()); } else { Builder.SetInsertPoint(UserInstr); } 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 X("make-model-gep", "Pass that transforms address arithmetic into " "calls to ModelGEP ", false, false);