/// \file FunctionSummaryOracle.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include "llvm/IR/GlobalVariable.h" #include "revng/ABI/FunctionType/Layout.h" #include "revng/ADT/STLExtras.h" #include "revng/EarlyFunctionAnalysis/FunctionSummaryOracle.h" static Logger<> Log("efa-import-model"); namespace efa { FunctionSummary PrototypeImporter::prototype(const AttributesSet &Attributes, const model::TypeDefinition *Prototype) { FunctionSummary Summary(Attributes, ABICSVs, {}, {}, {}); if (Prototype == nullptr) return Summary; // Drop known to be preserved registers from `Summary.ClobberedRegisters`. auto TransformToCSV = std::views::transform([this](Register Register) { return M.getGlobalVariable(model::Register::getCSVName(Register), true); }); auto IgnoreNullptr = std::views::filter([](const auto *Pointer) -> bool { return Pointer != nullptr; }); for (auto *CSV : abi::FunctionType::calleeSavedRegisters(*Prototype) | TransformToCSV | IgnoreNullptr) { Summary.ClobberedRegisters.erase(CSV); } // Stop importing prototype here, if `ClobberedRegisters` is the only // information callee needs. if (Level == PrototypeImportLevel::None) return Summary; Summary.ElectedFSO = abi::FunctionType::finalStackOffset(*Prototype); // Stop importing prototype here, if callee also needs final stack offset. if (Level == PrototypeImportLevel::Partial) return Summary; for (llvm::GlobalVariable *CSV : ABICSVs) { Summary.ABIResults.ArgumentsRegisters.erase(CSV); Summary.ABIResults.ReturnValuesRegisters.erase(CSV); } auto &&[ArgumentRegisters, ReturnValueRegisters] = abi::FunctionType::usedRegisters(*Prototype); for (Register ArgumentRegister : ArgumentRegisters) { llvm::StringRef Name = model::Register::getCSVName(ArgumentRegister); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) Summary.ABIResults.ArgumentsRegisters.insert(CSV); } for (Register ReturnValueRegister : ReturnValueRegisters) { llvm::StringRef Name = model::Register::getCSVName(ReturnValueRegister); if (llvm::GlobalVariable *CSV = M.getGlobalVariable(Name, true)) Summary.ABIResults.ReturnValuesRegisters.insert(CSV); } // This point is only ever reached on the "full" import (like the one // DetectABI does) - the proper function summary in extracted. return Summary; } std::pair FunctionSummaryOracle::getCallSite(MetaAddress Function, BasicBlockID CallerBlockAddress, MetaAddress CalledLocalFunction, llvm::StringRef CalledSymbol) { auto &&[Summary, IsTailCall] = getExactCallSite(Function, CallerBlockAddress); if (Summary != nullptr) { return { Summary, IsTailCall }; } else if (not CalledSymbol.empty()) { return { &getDynamicFunction(CalledSymbol), false }; } else if (CalledLocalFunction.isValid() and Binary.Functions().contains(CalledLocalFunction)) { return { &getLocalFunction(CalledLocalFunction), false }; } else { return { &getDefault(), false }; } } bool FunctionSummaryOracle::registerCallSite(MetaAddress Function, BasicBlockID CallSite, FunctionSummary &&New, bool IsTailCall) { revng_assert(Function.isValid()); revng_assert(CallSite.isValid()); std::pair Key = { Function, CallSite }; auto It = CallSites.find(Key); if (It != CallSites.end()) { auto &Recorded = It->second.first; bool Changed = not New.containedOrEqual(Recorded); New.combine(Recorded); Recorded = std::move(New); return Changed; } else { CallSiteDescriptor CSD = { std::move(New), IsTailCall }; CallSites.emplace(Key, std::move(CSD)); return true; } } bool FunctionSummaryOracle::registerLocalFunction(MetaAddress PC, FunctionSummary &&New) { revng_assert(PC.isValid()); if (Log.isEnabled()) { Log << "registerLocalFunction " << PC.toString() << " with summary:\n"; New.dump(Log); Log << DoLog; } auto It = LocalFunctions.find(PC); bool Changed = It == LocalFunctions.end(); if (not Changed) { auto &Recorded = It->second; Changed = not New.containedOrEqual(Recorded); New.combine(Recorded); Recorded = std::move(New); } else { LocalFunctions.emplace(PC, std::move(New)); } return Changed; } bool FunctionSummaryOracle::registerDynamicFunction(llvm::StringRef Name, FunctionSummary &&New) { auto It = DynamicFunctions.find(Name.str()); if (It != DynamicFunctions.end()) { auto &Recorded = It->second; bool Changed = not New.containedOrEqual(Recorded); New.combine(Recorded); Recorded = std::move(New); return Changed; } else { DynamicFunctions.emplace(Name, std::move(New)); return true; } } const FunctionSummary &FunctionSummaryOracle::getDefault() { if (not Default.has_value()) setDefault(Importer.prototype({}, Binary.defaultPrototype())); return Default.value(); } FunctionSummary &FunctionSummaryOracle::getLocalFunction(MetaAddress PC) { if (not LocalFunctions.contains(PC)) { const model::Function &Function = Binary.Functions().at(PC); AttributesSet Attributes; for (auto &ToCopy : Function.Attributes()) Attributes.insert(ToCopy); const auto *Prototype = Binary.prototypeOrDefault(Function.prototype()); auto Summary = Importer.prototype(Attributes, Prototype); registerLocalFunction(Function.Entry(), std::move(Summary)); } return LocalFunctions.at(PC); } const FunctionSummary & FunctionSummaryOracle::getDynamicFunction(llvm::StringRef Name) { if (not DynamicFunctions.contains(Name.str())) { const auto &DynamicFunction = Binary.ImportedDynamicFunctions() .at(Name.str()); auto *Prototype = Binary.prototypeOrDefault(DynamicFunction.prototype()); AttributesSet Attributes; for (auto &ToCopy : DynamicFunction.Attributes()) Attributes.insert(ToCopy); registerDynamicFunction(DynamicFunction.OriginalName(), Importer.prototype(Attributes, Prototype)); } return DynamicFunctions.at(Name.str()); } std::pair FunctionSummaryOracle::getExactCallSite(MetaAddress Entry, BasicBlockID CallSiteAddress) { auto It = CallSites.find({ Entry, CallSiteAddress }); if (It == CallSites.end()) { // Note: in case of absence we're doing the lookup every time, not super // efficient. const model::Function &Function = Binary.Functions().at(Entry); // TODO: should CallSitePrototypes be index by BasicBlockID? if (auto *CallSite = Function.CallSitePrototypes() .tryGet(CallSiteAddress.start())) { AttributesSet Attributes; for (auto &ToCopy : CallSite->Attributes()) Attributes.insert(ToCopy); registerCallSite(Function.Entry(), BasicBlockID(CallSite->CallerBlockAddress()), Importer.prototype(Attributes, CallSite->prototype()), CallSite->IsTailCall()); } } It = CallSites.find({ Entry, CallSiteAddress }); if (It == CallSites.end()) { return { nullptr, false }; } else { return { &It->second.first, It->second.second }; } } template FunctionSummaryOracle importImpl(llvm::Module &M, GeneratedCodeBasicInfo &GCBI, const model::Binary &Binary) { using GV = llvm::GlobalVariable; auto RegisterFilter = std::views::filter([SP = GCBI.spReg()](GV *CSV) { return CSV != nullptr && CSV != SP; }); PrototypeImporter Importer{ .Level = Level, .M = M, .ABICSVs = GCBI.abiRegisters() | RegisterFilter | revng::to>() }; return FunctionSummaryOracle(Binary, std::move(Importer)); } FunctionSummaryOracle FunctionSummaryOracle::importFullPrototypes(llvm::Module &M, GeneratedCodeBasicInfo &GCBI, const model::Binary &Binary) { revng_log(Log, "Importing from the model, while taking prototypes into the " "account"); return importImpl(M, GCBI, Binary); } FunctionSummaryOracle FunctionSummaryOracle::importBasicPrototypeData(llvm::Module &M, GeneratedCodeBasicInfo &GCBI, const model::Binary &Binary) { revng_log(Log, "Importing from the model, but ignoring some of the prototype " "data"); return importImpl(M, GCBI, Binary); } FunctionSummaryOracle FunctionSummaryOracle::importWithoutPrototypes(llvm::Module &M, GeneratedCodeBasicInfo &GCBI, const model::Binary &Binary) { revng_log(Log, "Importing from the model, but ignoring prototypes"); return importImpl(M, GCBI, Binary); } } // namespace efa