diff --git a/include/phasar/ControlFlow/ICFGBase.h b/include/phasar/ControlFlow/ICFGBase.h index 2f03bba60..bbdd5b75f 100644 --- a/include/phasar/ControlFlow/ICFGBase.h +++ b/include/phasar/ControlFlow/ICFGBase.h @@ -119,6 +119,10 @@ template class ICFGBase { return self().getAsJsonImpl(); } + [[nodiscard]] size_t getNumCallSites() const noexcept { + return self().getNumCallSitesImpl(); + } + private: const Derived &self() const noexcept { return static_cast(*this); diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h index ee7ee4b92..4a74d9e3f 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h @@ -12,6 +12,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctionSingletonCache.h" #include "phasar/Utils/ByRef.h" +#include "phasar/Utils/EmptyBaseOptimizationUtils.h" #include "phasar/Utils/TypeTraits.h" #include "llvm/ADT/DenseMapInfo.h" diff --git a/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h b/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h new file mode 100644 index 000000000..d124e8a01 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h @@ -0,0 +1,108 @@ +#ifndef PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_GENERICFLOWFUNCTION_H +#define PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_GENERICFLOWFUNCTION_H + +#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" + +namespace psr { +/// Encapsulates an unmanaged pointer to a FlowFunction +template > +class GenericFlowFunctionView { +public: + using FlowFunctionType = FlowFunction; + using FlowFunctionPtrType = std::unique_ptr; + + using container_type = Container; + using value_type = typename container_type::value_type; + + GenericFlowFunctionView() noexcept = default; + GenericFlowFunctionView(FlowFunctionType *FF) noexcept : FF(FF) {} + + GenericFlowFunctionView(const GenericFlowFunctionView &) noexcept = default; + GenericFlowFunctionView & + operator=(const GenericFlowFunctionView &) noexcept = default; + + ~GenericFlowFunctionView() = default; + + [[nodiscard]] container_type computeTargets(D Source) const { + assert(FF != nullptr); + return FF->computeTargets(std::move(Source)); + } + + explicit operator bool() const noexcept { return FF; } + + [[nodiscard]] bool operator==(GenericFlowFunctionView Other) const noexcept { + return FF == Other.FF; + } + [[nodiscard]] bool operator==(std::nullptr_t) const noexcept { + return FF == nullptr; + } + [[nodiscard]] bool operator!=(GenericFlowFunctionView Other) const noexcept { + return !(*this == Other); + } + [[nodiscard]] bool operator!=(std::nullptr_t) const noexcept { return FF; } + +private: + FlowFunctionType *FF = nullptr; +}; + +/// Encapsulates a managed pointer to a FlowFunction +template > +class GenericFlowFunction { +public: + using FlowFunctionType = FlowFunction; + using FlowFunctionPtrType = typename FlowFunctionType::FlowFunctionPtrType; + + using container_type = Container; + using value_type = typename container_type::value_type; + + GenericFlowFunction() noexcept = default; + GenericFlowFunction(FlowFunctionPtrType FF) noexcept : FF(std::move(FF)) {} + template >>> + GenericFlowFunction(T &&FF) + : FF(std::make_unique>(std::forward(FF))) {} + + template + explicit GenericFlowFunction(std::in_place_type_t /*unused*/, + ArgTys &&...Args) + : FF(std::make_unique(std::forward(Args)...)) {} + + GenericFlowFunction(GenericFlowFunction &&) noexcept = default; + GenericFlowFunction &operator=(GenericFlowFunction &&) noexcept = default; + + GenericFlowFunction(const GenericFlowFunction &) = delete; + GenericFlowFunction &operator=(const GenericFlowFunction &) = delete; + + ~GenericFlowFunction() = default; + + [[nodiscard]] container_type computeTargets(D Source) const { + assert(FF != nullptr); + return FF->computeTargets(std::move(Source)); + } + + explicit operator bool() const noexcept { return FF; } + + operator GenericFlowFunctionView() const noexcept { + return FF.get(); + } + + [[nodiscard]] bool + operator==(GenericFlowFunctionView Other) const noexcept { + return FF == Other.FF; + } + [[nodiscard]] bool operator==(std::nullptr_t) const noexcept { + return FF == nullptr; + } + [[nodiscard]] bool + operator!=(GenericFlowFunctionView Other) const noexcept { + return !(*this == Other); + } + [[nodiscard]] bool operator!=(std::nullptr_t) const noexcept { return FF; } + +private: + FlowFunctionPtrType FF; +}; + +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_GENERICFLOWFUNCTION_H diff --git a/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h b/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h index 5d3dc71f6..c9a02a1df 100644 --- a/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h +++ b/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h @@ -18,6 +18,7 @@ #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h" #include "phasar/DataFlow/IfdsIde/SolverResults.h" #include "phasar/Utils/JoinLattice.h" #include "phasar/Utils/NullAnalysisPrinter.h" @@ -134,7 +135,7 @@ class IDETabulationProblem : public FlowFunctions, /// Generates a text report of the results that is written to the specified /// output stream. virtual void - emitTextReport([[maybe_unused]] const SolverResults &Results, + emitTextReport([[maybe_unused]] GenericSolverResults Results, llvm::raw_ostream &OS = llvm::outs()) { OS << "No text report available!\n"; } @@ -142,7 +143,7 @@ class IDETabulationProblem : public FlowFunctions, /// Generates a graphical report, e.g. in html or other markup languages, of /// the results that is written to the specified output stream. virtual void emitGraphicalReport( - [[maybe_unused]] const SolverResults &Results, + [[maybe_unused]] GenericSolverResults Results, llvm::raw_ostream &OS = llvm::outs()) { OS << "No graphical report available!\n"; } @@ -151,6 +152,8 @@ class IDETabulationProblem : public FlowFunctions, /// the level of soundness is ignored. Otherwise, true. virtual bool setSoundness(Soundness /*S*/) { return false; } + const ProjectIRDBBase *getProjectIRDB() const noexcept { return IRDB; } + protected: typename FlowFunctions::FlowFunctionPtrType generateFromZero(d_t FactToGenerate) { diff --git a/include/phasar/DataFlow/IfdsIde/Solver/Compressor.h b/include/phasar/DataFlow/IfdsIde/Solver/Compressor.h new file mode 100644 index 000000000..7d357a0a0 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/Compressor.h @@ -0,0 +1,191 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_COMPRESSOR_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_COMPRESSOR_H + +#include "phasar/DB/ProjectIRDBBase.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/TypeTraits.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/SmallVector.h" + +#include +#include +#include +#include +#include + +namespace psr { +template class Compressor; + +template +class Compressor>> { +public: + void reserve(size_t Capacity) { + assert(Capacity <= UINT32_MAX); + ToInt.reserve(Capacity); + FromInt.reserve(Capacity); + } + + uint32_t getOrInsert(T Elem) { + auto [It, Inserted] = ToInt.try_emplace(Elem, ToInt.size()); + if (Inserted) { + FromInt.push_back(Elem); + } + return It->second; + } + + std::optional getOrNull(T Elem) const { + if (auto It = ToInt.find(Elem); It != ToInt.end()) { + return It->second; + } + return std::nullopt; + } + + T operator[](size_t Idx) const noexcept { + assert(Idx < FromInt.size()); + return FromInt[Idx]; + } + + [[nodiscard]] size_t size() const noexcept { return FromInt.size(); } + [[nodiscard]] size_t capacity() const noexcept { + return FromInt.capacity() + + ToInt.getMemorySize() / sizeof(typename decltype(ToInt)::value_type); + } + + auto begin() const noexcept { return FromInt.begin(); } + auto end() const noexcept { return FromInt.end(); } + +private: + llvm::DenseMap ToInt; + llvm::SmallVector FromInt; +}; + +template +class Compressor>> { +public: + void reserve(size_t Capacity) { + assert(Capacity <= UINT32_MAX); + ToInt.reserve(Capacity); + } + + uint32_t getOrInsert(const T &Elem) { + if (auto It = ToInt.find(&Elem); It != ToInt.end()) { + return It->second; + } + auto Ret = FromInt.size(); + auto *Ins = &FromInt.emplace_back(Elem); + ToInt[Ins] = Ret; + return Ret; + } + + uint32_t getOrInsert(T &&Elem) { + if (auto It = ToInt.find(&Elem); It != ToInt.end()) { + return It->second; + } + auto Ret = FromInt.size(); + auto *Ins = &FromInt.emplace_back(std::move(Elem)); + ToInt[Ins] = Ret; + return Ret; + } + + std::optional getOrNull(const T &Elem) const { + if (auto It = ToInt.find(&Elem); It != ToInt.end()) { + return It->second; + } + return std::nullopt; + } + + const T &operator[](size_t Idx) const noexcept { + assert(Idx < FromInt.size()); + return FromInt[Idx]; + } + + [[nodiscard]] size_t size() const noexcept { return FromInt.size(); } + [[nodiscard]] size_t capacity() const noexcept { + return FromInt.size() + + ToInt.getMemorySize() / sizeof(typename decltype(ToInt)::value_type); + } + + auto begin() const noexcept { return FromInt.begin(); } + auto end() const noexcept { return FromInt.end(); } + +private: + struct DSI : llvm::DenseMapInfo { + static auto getHashValue(const T *Elem) noexcept { + assert(Elem != nullptr); + if constexpr (has_llvm_dense_map_info) { + return llvm::DenseMapInfo::getHashValue(*Elem); + } else { + return std::hash{}(*Elem); + } + } + static auto isEqual(const T *LHS, const T *RHS) noexcept { + if (LHS == RHS) { + return true; + } + if (LHS == DSI::getEmptyKey() || LHS == DSI::getTombstoneKey() || + RHS == DSI::getEmptyKey() || RHS == DSI::getTombstoneKey()) { + return false; + } + if constexpr (has_llvm_dense_map_info) { + return llvm::DenseMapInfo::isEqual(*LHS, *RHS); + } else { + return *LHS == *RHS; + } + } + }; + + std::deque FromInt; + llvm::DenseMap ToInt; +}; + +struct NoneCompressor final { + constexpr NoneCompressor() noexcept = default; + + template >> + constexpr NoneCompressor(const T & /*unused*/) noexcept {} + + template + [[nodiscard]] decltype(auto) getOrInsert(T &&Val) const noexcept { + return std::forward(Val); + } + template + [[nodiscard]] decltype(auto) operator[](T &&Val) const noexcept { + return std::forward(Val); + } + void reserve(size_t /*unused*/) const noexcept {} + + [[nodiscard]] size_t size() const noexcept { return 0; } + [[nodiscard]] size_t capacity() const noexcept { return 0; } +}; + +class LLVMProjectIRDB; + +/// Once we have fast instruction IDs (as we already have in IntelliSecPhasar), +/// we might want to create a specialization for T/const llvm::Value * that uses +/// the IDs from the IRDB +template struct NodeCompressorTraits { + using type = Compressor; + + static type create(const ProjectIRDBBase + * /*IRDB*/) noexcept(noexcept(type())) { + return type(); + } +}; + +template struct ValCompressorTraits { + using type = Compressor; + using id_type = uint32_t; +}; + +template +struct ValCompressorTraits>> { + using type = NoneCompressor; + using id_type = T; +}; + +} // namespace psr + +#endif // PHASAR_DATAFLOW_IFDSIDE_SOLVER_COMPRESSOR_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h b/include/phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h index 94b98ed89..9beaa0d63 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h @@ -1,3 +1,6 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ESGEDGEKIND_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ESGEDGEKIND_H + /****************************************************************************** * Copyright (c) 2022 Philipp Schubert. * All rights reserved. This program and the accompanying materials are made @@ -7,9 +10,6 @@ * Fabian Schiebel and others *****************************************************************************/ -#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ESGEDGEKIND_H -#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ESGEDGEKIND_H - namespace psr { enum class ESGEdgeKind { Normal, Call, CallToRet, SkipUnknownFn, Ret, Summary }; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h new file mode 100644 index 000000000..dbcd46561 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h @@ -0,0 +1,163 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_EDGEFUNCTIONCACHE_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_EDGEFUNCTIONCACHE_H + +#include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" +#include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/EquivalenceClassMap.h" +#include "phasar/Utils/StableVector.h" + +#include "llvm/ADT/DenseSet.h" +#include "llvm/Support/raw_ostream.h" + +#include + +namespace psr { + +template +class EdgeFunctionCache : private EdgeFunctionCacheStats { + using domain_t = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename domain_t::d_t; + using n_t = typename domain_t::n_t; + using f_t = typename domain_t::f_t; + using l_t = typename domain_t::l_t; + +public: + EdgeFunctionCache() noexcept = default; + + using EdgeFunctionPtrType = EdgeFunction; + + EdgeFunctionPtrType + getNormalEdgeFunction(ProblemTy &Problem, ByConstRef Curr, + ByConstRef CurrNode, ByConstRef Succ, + ByConstRef SuccNode, uint64_t CurrSuccId, + uint64_t CurrNodeSuccNodeId) { + auto &EqMap = NormalAndCtrEFCache[CurrSuccId]; + if (!EqMap) { + EqMap = &NormalMapOwner.emplace_back(); + } + return EqMap->getOrInsertLazy(CurrNodeSuccNodeId, [&] { + ++NormalAndCtrEFCacheCumulSize; + return Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); + }); + } + + EdgeFunctionPtrType + getCallToRetEdgeFunction(ProblemTy &Problem, ByConstRef CallSite, + ByConstRef CallNode, ByConstRef RetSite, + ByConstRef RetSiteNode, + llvm::ArrayRef Callees, uint64_t CallRetId, + uint64_t CallNodeRetNodeId) { + auto &EqMap = NormalAndCtrEFCache[CallRetId]; + if (!EqMap) { + EqMap = &NormalMapOwner.emplace_back(); + } + return EqMap->getOrInsertLazy(CallNodeRetNodeId, [&] { + ++NormalAndCtrEFCacheCumulSize; + return Problem.getCallToRetEdgeFunction(CallSite, CallNode, RetSite, + RetSiteNode, Callees); + }); + } + + EdgeFunctionPtrType + getCallEdgeFunction(ProblemTy &Problem, ByConstRef CallSite, + ByConstRef SrcNode, ByConstRef Callee, + ByConstRef DestNode, uint64_t CSCalleeId, + uint64_t SrcDestNodeId) { + auto &EqMap = CallEFCache[CSCalleeId]; + if (!EqMap) { + EqMap = &NormalMapOwner.emplace_back(); + } + return EqMap->getOrInsertLazy(SrcDestNodeId, [&] { + ++CallEFCacheCumulSize; + return Problem.getCallEdgeFunction(CallSite, SrcNode, Callee, DestNode); + }); + } + + EdgeFunctionPtrType + getReturnEdgeFunction(ProblemTy &Problem, ByConstRef CallSite, + ByConstRef Callee, ByConstRef ExitInst, + ByConstRef ExitNode, ByConstRef RetSite, + ByConstRef RetSiteNode, uint32_t ExitId, + uint64_t CSRSId, uint64_t ExitRSNodeId) { + auto &EqMap = RetEFCache[ExitId]; + if (!EqMap) { + EqMap = &RetMapOwner.emplace_back(); + } + return EqMap->getOrInsertLazy(std::make_pair(CSRSId, ExitRSNodeId), [&] { + ++RetEFCacheCumulSize; + return Problem.getReturnEdgeFunction(CallSite, Callee, ExitInst, ExitNode, + RetSite, RetSiteNode); + }); + } + + EdgeFunctionPtrType + getSummaryEdgeFunction(ProblemTy &Problem, ByConstRef CallSite, + ByConstRef CallNode, ByConstRef RetSite, + ByConstRef RetSiteNode, uint64_t CSRSId, + uint64_t CSNodeRSNodeId) { + auto &EqMap = SummaryEFCache[CSRSId]; + if (!EqMap) { + EqMap = &NormalMapOwner.emplace_back(); + } + return EqMap->getOrInsertLazy(CSNodeRSNodeId, [&] { + ++SummaryEFCacheCumulSize; + return Problem.getSummaryEdgeFunction(CallSite, CallNode, RetSite, + RetSiteNode); + }); + } + + void clear() { + NormalAndCtrEFCache.clear(); + CallEFCache.clear(); + RetEFCache.clear(); + SummaryEFCache.clear(); + NormalMapOwner.clear(); + RetMapOwner.clear(); + } + + void dumpStats(llvm::raw_ostream &OS) const { OS << getStats(); } + + [[nodiscard]] EdgeFunctionCacheStats getStats() const noexcept { + size_t Sz = 0; + size_t MaxInnerSz = 0; + llvm::SmallVector Sizes; + for (const auto &[Edge, NormalAndCtrEF] : NormalAndCtrEFCache) { + Sz += NormalAndCtrEF->size(); + MaxInnerSz = std::max(MaxInnerSz, NormalAndCtrEF->size()); + Sizes.push_back(NormalAndCtrEF->size()); + } + + std::sort(Sizes.begin(), Sizes.end()); + + EdgeFunctionCacheStats Ret = *this; + Ret.NormalAndCtrEFCacheCumulSizeReduced = Sz; + Ret.AvgEFPerEdge = double(Sz) / NormalAndCtrEFCache.size(); + Ret.MaxEFPerEdge = MaxInnerSz; + Ret.MedianEFPerEdge = Sizes.empty() ? 0 : Sizes[Sizes.size() / 2]; + return Ret; + } + +private: + StableVector> + NormalMapOwner{}; + StableVector< + EquivalenceClassMapNG, EdgeFunctionPtrType>> + RetMapOwner{}; + /// NOTE: For CTR, the Callees set is implied by the CallSite/RetSite + llvm::DenseMap *> + NormalAndCtrEFCache{}; + llvm::DenseMap *> + CallEFCache{}; + llvm::DenseMap, + EdgeFunctionPtrType> *> + RetEFCache{}; + llvm::DenseMap *> + SummaryEFCache{}; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_EDGEFUNCTIONCACHE_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.h b/include/phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.h new file mode 100644 index 000000000..a91950460 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.h @@ -0,0 +1,27 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_EDGEFUNCTIONCACHESTATS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_EDGEFUNCTIONCACHESTATS_H + +#include + +namespace llvm { +class raw_ostream; +} // namespace llvm + +namespace psr { + +struct EdgeFunctionCacheStats { + size_t NormalAndCtrEFCacheCumulSize = 0; + size_t NormalAndCtrEFCacheCumulSizeReduced = 0; + double AvgEFPerEdge = 0; + size_t MedianEFPerEdge = 0; + size_t MaxEFPerEdge = 0; + size_t CallEFCacheCumulSize = 0; + size_t RetEFCacheCumulSize = 0; + size_t SummaryEFCacheCumulSize = 0; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const EdgeFunctionCacheStats &S); +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_EDGEFUNCTIONCACHESTATS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h new file mode 100644 index 000000000..1c5b77c21 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h @@ -0,0 +1,45 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHENG_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHENG_H + +#include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h" + +#include "llvm/Support/raw_ostream.h" + +namespace psr { + +template +class FlowEdgeFunctionCacheNG + : public FlowFunctionCache, + public EdgeFunctionCache { +public: + explicit FlowEdgeFunctionCacheNG(ProblemTy & /*Problem*/) + : FlowFunctionCache(), + EdgeFunctionCache() {} + + void clearFlowFunctions() { + this->FlowFunctionCache::clear(); + } + + void clear() { + this->FlowFunctionCache::clear(); + this->EdgeFunctionCache::clear(); + } + + void reserve(size_t NumInsts, size_t NumCalls, size_t NumFuns) { + this->FlowFunctionCache::reserve(NumInsts, NumCalls, + NumFuns); + /// XXX: reserve edge functions as well, once possible! + } + + [[nodiscard]] FlowEdgeFunctionCacheStats getStats() const noexcept { + return {this->FlowFunctionCache::getStats(), + this->EdgeFunctionCache::getStats()}; + } + + void dumpStats(llvm::raw_ostream &OS) const { OS << getStats(); } +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHENG_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.h new file mode 100644 index 000000000..b88aa4679 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.h @@ -0,0 +1,15 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHESTATS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHESTATS_H + +#include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h" + +namespace psr { +struct FlowEdgeFunctionCacheStats : FlowFunctionCacheStats, + EdgeFunctionCacheStats { + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const FlowEdgeFunctionCacheStats &Stats); +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHESTATS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h new file mode 100644 index 000000000..a0f42d821 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h @@ -0,0 +1,338 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H + +#include "phasar/DataFlow/IfdsIde/GenericFlowFunction.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/PointerUtils.h" +#include "phasar/Utils/TableWrappers.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include + +namespace psr { +namespace detail { +template +constexpr inline bool IsFlowFunction = false; + +template +constexpr inline bool + IsFlowFunction().computeTargets( + std::declval()))>> = true; + +template +constexpr inline bool IsFlowFunctionPtr = false; + +template +constexpr inline bool + IsFlowFunctionPtr()->computeTargets( + std::declval()))>> = true; + +template struct AutoAddZeroFF { + FFTy FF; + D ZeroValue; + + auto computeTargets(ByConstRef Source) { + auto Ret = FF.computeTargets(Source); + if (Source == ZeroValue) { + Ret.insert(ZeroValue); + } + return Ret; + } + + [[nodiscard]] bool operator==(std::nullptr_t) const noexcept { + if constexpr (std::is_same_v> || + std::is_same_v>) { + return FF == nullptr; + } else { + return std::is_same_v, std::nullptr_t>; + } + } + [[nodiscard]] bool operator!=(std::nullptr_t) const noexcept { + return !(*this == nullptr); + } +}; + +template +AutoAddZeroFF(FFTy, D) -> AutoAddZeroFF, std::decay_t>; + +template +struct IntraFlowFunctionsMixin { + DenseTable1d NormalFFCache{}; + /// NOTE: The Callees set is completely implied by CallSite/RetSite and does + /// not need to be stored! Note2: Assume here that the callees set is + /// invariant during the IFDS/IDE analysis + DenseTable1d CtrFFCache{}; + + void reserveIntraFFs(size_t NumInsts, size_t NumCalls) { + NormalFFCache.reserve(NumInsts); + CtrFFCache.reserve(NumCalls); + } +}; + +template +struct IntraFlowFunctionsMixin { + /// Whenever possible, use only one map for normal-flows and ctr-flows. The + /// key-spaces are strictly disjoint and paying 75% MaxLoadFactor once less + /// should be good for memory usage + + DenseTable1d NormalFFCache{}; + DenseTable1d &CtrFFCache = NormalFFCache; + + void reserveIntraFFs(size_t NumInsts, size_t /*NumCalls*/) { + NormalFFCache.reserve(NumInsts); + } +}; + +template struct FlowFunctionCacheBase { + using domain_t = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename domain_t::d_t; + using n_t = typename domain_t::n_t; + using f_t = typename domain_t::f_t; + using normal_ff_t = + std::decay_t().getNormalFlowFunction( + std::declval(), std::declval()))>; + using call_ff_t = + std::decay_t().getCallFlowFunction( + std::declval(), std::declval()))>; + using ret_ff_t = + std::decay_t().getRetFlowFunction( + std::declval(), std::declval(), std::declval(), + std::declval()))>; + using ctr_ff_t = + std::decay_t().getCallToRetFlowFunction( + std::declval(), std::declval(), + std::declval>()))>; + using summary_ff_t = + std::decay_t().getSummaryFlowFunction( + std::declval(), std::declval()))>; + + static_assert(detail::IsFlowFunction || + detail::IsFlowFunctionPtr); + static_assert(detail::IsFlowFunction || + detail::IsFlowFunctionPtr); + static_assert(detail::IsFlowFunction || + detail::IsFlowFunctionPtr); + static_assert(detail::IsFlowFunction || + detail::IsFlowFunctionPtr); + static_assert(detail::IsFlowFunction || + detail::IsFlowFunctionPtr || + std::is_same_v); + + template + static constexpr bool needs_cache_v = + std::is_same_v>, T> || + std::is_same_v, T> || + (detail::IsFlowFunctionPtr && !std::is_pointer_v); +}; + +} // namespace detail + +template +class FlowFunctionCache + : detail::FlowFunctionCacheBase, + detail::IntraFlowFunctionsMixin< + typename detail::FlowFunctionCacheBase::normal_ff_t, + typename detail::FlowFunctionCacheBase::ctr_ff_t> { + using base_t = detail::FlowFunctionCacheBase; + + using typename base_t::call_ff_t; + using typename base_t::ctr_ff_t; + using typename base_t::d_t; + using typename base_t::domain_t; + using typename base_t::f_t; + using typename base_t::n_t; + using typename base_t::normal_ff_t; + using typename base_t::ret_ff_t; + using typename base_t::summary_ff_t; + + using detail::IntraFlowFunctionsMixin::NormalFFCache; + using detail::IntraFlowFunctionsMixin::CtrFFCache; + + template + static constexpr bool needs_cache_v = base_t::template needs_cache_v; + + template + static auto wrapFlowFunction(ProblemTy &Problem, FFTy &&FF) { + using ff_t = std::decay_t; + + if constexpr (AutoAddZero) { + if constexpr (detail::IsFlowFunctionPtr) { + if constexpr (needs_cache_v) { + return detail::AutoAddZeroFF{ + GenericFlowFunctionView(getPointerFrom(FF)), + Problem.getZeroValue()}; + } else { + return detail::AutoAddZeroFF{ + GenericFlowFunction(std::forward(FF)), + Problem.getZeroValue()}; + } + } else { + return detail::AutoAddZeroFF{std::forward(FF), + Problem.getZeroValue()}; + } + } else { + if constexpr (detail::IsFlowFunctionPtr) { + if constexpr (needs_cache_v) { + return GenericFlowFunctionView(getPointerFrom(FF)); + } else { + return GenericFlowFunction(std::forward(FF)); + } + } else { + return std::forward(FF); + } + } + } + + template + decltype(auto) getFlowFunction(CacheTy &Cache, uint64_t Id, + ProblemTy &Problem, FFFactory Fact) { + if constexpr (needs_cache_v) { + auto &Ret = Cache.getOrCreate(Id); + if (!Ret) { + Ret = std::invoke(Fact, Problem); + } + + return wrapFlowFunction(Problem, Ret); + } else { + return wrapFlowFunction(Problem, std::invoke(Fact, Problem)); + } + } + +public: + FlowFunctionCache() noexcept = default; + + decltype(auto) getNormalFlowFunction(ProblemTy &Problem, ByConstRef Curr, + ByConstRef Succ, + uint64_t CurrSuccId) { + return getFlowFunction( + NormalFFCache, CurrSuccId, Problem, [Curr, Succ](ProblemTy &Problem) { + return Problem.getNormalFlowFunction(Curr, Succ); + }); + } + + decltype(auto) getCallToRetFlowFunction(ProblemTy &Problem, + ByConstRef CallSite, + ByConstRef RetSite, + llvm::ArrayRef Callees, + uint64_t CallRetId) { + return getFlowFunction( + CtrFFCache, CallRetId, Problem, + [CallSite, RetSite, Callees](ProblemTy &Problem) { + return Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees); + }); + } + + decltype(auto) getCallFlowFunction(ProblemTy &Problem, + ByConstRef CallSite, + ByConstRef Callee, + uint64_t CSCalleeId) { + return getFlowFunction(CallFFCache, CSCalleeId, Problem, + [CallSite, Callee](ProblemTy &Problem) { + return Problem.getCallFlowFunction( + CallSite, Callee); + }); + } + + /// CAUTION: Keep the ID definitions in mind! + decltype(auto) getRetFlowFunction(ProblemTy &Problem, + ByConstRef CallSite, + ByConstRef Callee, + ByConstRef ExitInst, + ByConstRef RetSite, uint64_t CSExitId, + uint64_t CalleeRSId) { + if constexpr (needs_cache_v) { + if constexpr (std::is_base_of_v< + llvm::Instruction, + std::remove_cv_t>>) { + if (llvm::isa(CallSite)) { + auto &Ret = SimpleRetFFCache.getOrCreate(CSExitId); + if (!Ret) { + Ret = + Problem.getRetFlowFunction(CallSite, Callee, ExitInst, RetSite); + } + return wrapFlowFunction(Problem, Ret); + } + } + + auto &Ret = RetFFCache.getOrCreate(std::make_pair(CSExitId, CalleeRSId)); + if (!Ret) { + Ret = Problem.getRetFlowFunction(CallSite, Callee, ExitInst, RetSite); + } + return wrapFlowFunction(Problem, Ret); + } else { + return wrapFlowFunction( + Problem, + Problem.getRetFlowFunction(CallSite, Callee, ExitInst, RetSite)); + } + } + + decltype(auto) getSummaryFlowFunction(ProblemTy &Problem, + ByConstRef CallSite, + ByConstRef Callee, + uint64_t /*CSCalleeId*/) { + if constexpr (needs_cache_v) { + return GenericFlowFunction( + Problem.getSummaryFlowFunction(CallSite, Callee)); + } else { + return Problem.getSummaryFlowFunction(CallSite, Callee); + } + + /// XXX: The old FECache doesn't cache summary FFs, so refrain from doing + /// this here as well. Enable it, once the user-problems reliably return + /// std::nullptr_t such that caching can be disabled automatically + } + + void clear() noexcept { + NormalFFCache.clear(); + CallFFCache.clear(); + RetFFCache.clear(); + SimpleRetFFCache.clear(); + CtrFFCache.clear(); + SummaryFFCache.clear(); + } + + void reserve(size_t NumInsts, size_t NumCalls, size_t NumFuns) { + detail::IntraFlowFunctionsMixin::reserveIntraFFs( + NumInsts, NumCalls); + CallFFCache.reserve(NumCalls); + RetFFCache.reserve(NumFuns); + SimpleRetFFCache.reserve(NumFuns); + } + + void dumpStats(llvm::raw_ostream &OS) const { OS << getStats(); } + + [[nodiscard]] FlowFunctionCacheStats getStats() const noexcept { + /// TODO (#734): With C++20 use designated aggregate initializers here! + return FlowFunctionCacheStats{ + NormalFFCache.size(), CallFFCache.size(), RetFFCache.size(), + SimpleRetFFCache.size(), CtrFFCache.size(), SummaryFFCache.size(), + }; + } + +private: + DenseTable1d CallFFCache; + /// NOTE: Unfortunately, RetSite cannot imply CallSite (c.f. invoke + /// instructions). Although ExitInst implies CalleeFun, it does not help to + /// store a pair --> padding + /// + /// Keys are (CS, ExitInst, Callee, RetSite) + DenseTable1d, ctr_ff_t> RetFFCache; + /// Here, we restrict ourselves to simple calls: + /// Keys are (CS, ExitInst) + DenseTable1d SimpleRetFFCache; + + DenseTable1d SummaryFFCache; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h new file mode 100644 index 000000000..049af06a0 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h @@ -0,0 +1,24 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHESTATS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHESTATS_H + +#include + +namespace llvm { +class raw_ostream; +} // namespace llvm + +namespace psr { +struct FlowFunctionCacheStats { + size_t NormalFFCacheSize{}; + size_t CallFFCacheSize{}; + size_t ReturnFFCacheSize{}; + size_t SimpleRetFFCacheSize{}; + size_t CallToRetFFCacheSize{}; + size_t SummaryFFCacheSize{}; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const FlowFunctionCacheStats &S); +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_FLOWFUNCTIONCACHESTATS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h new file mode 100644 index 000000000..744c8e7e3 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h @@ -0,0 +1,249 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_GENERICSOLVERRESULTS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_GENERICSOLVERRESULTS_H + +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/ByRef.h" + +#include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include +#include + +namespace psr { + +/// XXX (#734): When upgrading to C++20, create a concept checking valid +/// SolverResults types + +/// A type-erased version of the main functionality of SolverResults. +/// Can be accepted by consumers that don't need deep access to the internals +/// (so, the usual ones). As we have now two kinds of solver-results +/// (SolverResults and IdBasedSolverResults), we need a common way of accessing +/// them. +template class GenericSolverResults final { +public: + using n_t = N; + using d_t = D; + using l_t = L; + + /// NOTE: Don't provide an operator= that constructs a GenericSolverResults + /// from a concrete SolverResults object to prevent issues with object + /// lifetime. Probably that can be mitigated with use of std::launder, but for + /// now, we don't need this complexity + + template >>> + GenericSolverResults(const T &SR) noexcept : VT(&VtableFor>) { + using type = std::decay_t; + + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_trivially_copyable_v, + "SolverResults should only be a *view* into the results of " + "an IFDS/IDE solver."); + + /// NOTE: We reinterpret the bytes of the `Buffer` as `type`, so it better + /// be properly aligned + static_assert( + alignof(type) <= alignof(void *), + "The solver-results type is improperly aligned for the inline buffer"); + static_assert( + sizeof(type) <= sizeof(Buffer), + "The inline buffer is to small to hold this type of solver-result"); + + static_assert(std::is_trivially_copyable_v); + + new (Buffer.data()) type(SR); + } + + [[nodiscard]] l_t resultAt(ByConstRef Stmt, ByConstRef Node) const { + assert(VT != nullptr); + return VT->ResultAt(Buffer.data(), Stmt, Node); + } + + [[nodiscard]] std::unordered_map + resultsAt(ByConstRef Stmt, bool StripZero = false) const { + assert(VT != nullptr); + return VT->ResultsAt(Buffer.data(), Stmt, StripZero); + } + + template >> + [[nodiscard]] std::set ifdsResultsAt(ByConstRef Stmt) const { + assert(VT != nullptr); + assert(VT->IfdsResultsAt != nullptr); + return VT->IfdsResultsAt(Buffer.data(), Stmt); + } + + [[nodiscard]] size_t size() const noexcept { + assert(VT != nullptr); + return VT->Size(Buffer.data()); + } + + [[nodiscard]] bool containsNode(ByConstRef Stmt) const { + return VT->ContainsNode(Buffer.data(), Stmt); + } + + template >> + [[nodiscard]] l_t resultAtInLLVMSSA(const llvm::Instruction *Stmt, + ByConstRef Node) const { + if (const auto *Next = Stmt->getNextNode()) { + return resultAt(Next, Node); + } + return resultAt(Stmt, Node); + } + + template >> + [[nodiscard]] std::unordered_map + resultsAtInLLVMSSA(const llvm::Instruction *Stmt, + bool StripZero = false) const { + if (const auto *Next = Stmt->getNextNode()) { + return resultsAt(Next, StripZero); + } + return resultsAt(Stmt, StripZero); + } + + void foreachResultEntry( + llvm::function_ref)> Handler) const { + assert(VT != nullptr); + VT->ForeachResultEntry(Buffer.data(), Handler); + } + +private: + struct VTableTy { + l_t (*ResultAt)(const void *, ByConstRef, ByConstRef); + std::unordered_map (*ResultsAt)(const void *, ByConstRef, + bool); + std::set (*IfdsResultsAt)(const void *, ByConstRef); + size_t (*Size)(const void *) noexcept; + bool (*ContainsNode)(const void *, ByConstRef); + void (*ForeachResultEntry)( + const void *, llvm::function_ref)>); + }; + + template + static constexpr VTableTy VtableFor{ + [](const void *SR, ByConstRef Stmt, ByConstRef Node) -> l_t { + return static_cast(SR)->resultAt(Stmt, Node); + }, + [](const void *SR, ByConstRef Stmt, + bool StripZero) -> std::unordered_map { + return static_cast(SR)->resultsAt(Stmt, StripZero); + }, + [] { + std::set (*IfdsResultsAt)(const void *, ByConstRef) = nullptr; + if constexpr (std::is_same_v) { + IfdsResultsAt = [](const void *SR, ByConstRef Stmt) { + return static_cast(SR)->ifdsResultsAt(Stmt); + }; + } + return IfdsResultsAt; + }(), + [](const void *SR) noexcept -> size_t { + return static_cast(SR)->size(); + }, + [](const void *SR, ByConstRef Stmt) -> bool { + return static_cast(SR)->containsNode(Stmt); + }, + [](const void *SR, + llvm::function_ref)> Handler) { + static_cast(SR)->foreachResultEntry(Handler); + }}; + + const VTableTy *VT{}; + alignas(alignof(void *)) std::array Buffer{}; +}; + +template +bool ifdsEqual(const SR1 &LHS, const SR2 &RHS) { + if (LHS.size() != RHS.size()) { + return false; + } + + for (const auto &[Row, ColVal] : LHS.rowMapView()) { + if (!RHS.containsNode(Row)) { + return false; + } + + const auto &OtherColVal = LHS.row(Row); + if (ColVal.size() != OtherColVal.size()) { + return false; + } + + for (const auto &[Col, Val] : ColVal) { + if (!OtherColVal.count(Col)) { + return false; + } + } + } + return true; +} + +template +bool checkSREquality(const SR1 &LHS, const SR2 &RHS) { + bool HasError = false; + if (LHS.size() != RHS.size()) { + HasError = true; + llvm::errs() << "The results sizes do not match: " << LHS.size() << " vs " + << RHS.size() << '\n'; + } + + auto ToString = [](const auto &Fact) { + if constexpr (std::is_pointer_v> && + std::is_base_of_v>>>) { + return llvmIRToString(Fact); + } else { + std::string S; + llvm::raw_string_ostream ROS(S); + ROS << Fact; + ROS.flush(); + return S; + } + }; + + for (const auto &[Row, ColVal] : LHS.rowMapView()) { + if (!RHS.containsNode(Row)) { + HasError = true; + llvm::errs() << "RHS does not contain row for Inst: " << ToString(Row) + << '\n'; + } + + auto RHSColVal = RHS.row(Row); + + if (ColVal.size() != RHSColVal.size()) { + HasError = true; + llvm::errs() << "The number of facts at " << ToString(Row) + << " differ: " << ColVal.size() << " vs " << RHSColVal.size() + << '\n'; + } + + for (const auto &[Col, Val] : ColVal) { + auto It = RHSColVal.find(Col); + + if (It == RHSColVal.end()) { + HasError = true; + llvm::errs() << "RHS does not contain fact " << ToString(Col) + << " at Inst " << ToString(Row) << '\n'; + } else if (Val != It->second) { + HasError = true; + llvm::errs() << "The edge values at inst " << ToString(Row) + << " and fact " << ToString(Col) + << " differ: " << ToString(Val) << " vs " + << ToString(It->second) << '\n'; + } + } + } + return !HasError; +} +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_GENERICSOLVERRESULTS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 9f10b10e2..b0636d148 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -91,6 +91,10 @@ class IDESolver assert(ICF != nullptr); } + IDESolver(IDETabulationProblem *Problem, + const i_t *ICF) + : IDESolver(assertNotNull(Problem), ICF) {} + IDESolver(const IDESolver &) = delete; IDESolver &operator=(const IDESolver &) = delete; IDESolver(IDESolver &&) = delete; @@ -1857,6 +1861,11 @@ IDESolver(Problem &, ICF *) -> IDESolver; +template +IDESolver(Problem *, ICF *) + -> IDESolver; + template using IDESolver_P = IDESolver; @@ -1867,7 +1876,7 @@ OwningSolverResults solveIDEProblem(IDETabulationProblem &Problem, const typename AnalysisDomainTy::i_t &ICF) { - IDESolver Solver(Problem, &ICF); + IDESolver Solver(&Problem, &ICF); Solver.solve(); return Solver.consumeSolverResults(); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h index 3a5f20d8f..fadaf7625 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h @@ -43,6 +43,12 @@ class IFDSSolver std::is_base_of_v>> IFDSSolver(IFDSTabulationProblem &IFDSProblem, const i_t *ICF) + : IDESolver>(&IFDSProblem, ICF) {} + template >> + IFDSSolver(IFDSTabulationProblem *IFDSProblem, + const i_t *ICF) : IDESolver>(IFDSProblem, ICF) {} ~IFDSSolver() override = default; @@ -101,6 +107,10 @@ template IFDSSolver(Problem &, ICF *) -> IFDSSolver; +template +IFDSSolver(Problem *, ICF *) + -> IFDSSolver; template using IFDSSolver_P = IFDSSolver +#include +#include +#include +#include + +namespace psr { + +template class IdBasedSolverResults { + using row_map_t = + typename detail::IterativeIDESolverResults::ValTab_value_type; + +public: + using n_t = N; + using d_t = D; + using l_t = L; + + class RowView { + struct Transformator { + const detail::IterativeIDESolverResults *Results{}; + mutable std::optional> Cache{}; + + const std::pair & + operator()(ByConstRef Entry) const { + Cache = std::make_pair(Results->FactCompressor[Entry.first], + Results->ValCompressor[Entry.second]); + return *Cache; + } + }; + + public: + using iterator = llvm::mapped_iterator; + using const_iterator = iterator; + using difference_type = ptrdiff_t; + + explicit RowView( + const detail::IterativeIDESolverResults *Results, + const row_map_t *Row) noexcept + : Results(Results), Row(Row) { + assert(Results != nullptr); + assert(Row != nullptr); + } + + [[nodiscard]] const_iterator begin() const noexcept { + return llvm::map_iterator(Row->cells().begin(), Transformator{Results}); + } + + [[nodiscard]] const_iterator end() const noexcept { + return llvm::map_iterator(Row->cells().end(), Transformator{Results}); + } + + [[nodiscard]] const_iterator find(ByConstRef Fact) const { + auto FactId = Results->FactCompressor.getOrNull(Fact); + if (!FactId) { + return end(); + } + return llvm::map_iterator(Row->find(*FactId), Transformator{Results}); + } + + [[nodiscard]] bool count(ByConstRef Fact) const { + auto FactId = Results->FactCompressor.getOrNull(Fact); + if (!FactId) { + return false; + } + return Row->contains(*FactId); + } + + [[nodiscard]] size_t size() const noexcept { return Row->size(); } + + private: + const detail::IterativeIDESolverResults *Results{}; + const row_map_t *Row{}; + }; + + explicit IdBasedSolverResults( + const detail::IterativeIDESolverResults *Results) noexcept + : Results(Results) { + assert(Results != nullptr); + } + + [[nodiscard]] l_t resultAt(ByConstRef Stmt, ByConstRef Node) const { + auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + auto FactId = Results->FactCompressor.getOrNull(Node); + + if (!NodeId || !FactId) { + return l_t{}; + } + + const auto &Entry = Results->ValTab[size_t(*NodeId)]; + auto RetIt = Entry.find(*FactId); + if (RetIt == Entry.cells().end()) { + return l_t{}; + } + + return Results->ValCompressor[RetIt->second]; + } + + [[nodiscard]] std::unordered_map + resultsAt(ByConstRef Stmt, bool StripZero = false) const { + auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + if (!NodeId) { + return {}; + } + + std::unordered_map Result; + Result.reserve(Results->ValTab[size_t(*NodeId)].size()); + for (auto [Fact, Value] : Results->ValTab[size_t(*NodeId)].cells()) { + /// In the IterativeIDESolver, we have made sure that the zero flow-fact + /// always has the Id 0 + if (StripZero && Fact == 0) { + continue; + } + Result.try_emplace(Results->FactCompressor[Fact], + Results->ValCompressor[Value]); + } + + return Result; + } + + [[nodiscard]] std::set ifdsResultsAt(ByConstRef Stmt) const { + auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + if (!NodeId) { + return {}; + } + + std::set Result; + for (auto [Fact, Unused] : Results->ValTab[size_t(*NodeId)]) { + Result.insert(Results->FactCompressor[Fact]); + } + return Result; + } + + [[nodiscard]] size_t size() const noexcept { + assert(Results->ValTab.size() >= Results->NodeCompressor.size()); + return Results->NodeCompressor.size(); + } + + [[nodiscard]] auto getAllResultEntries() const noexcept { + auto Txn = + [Results{this->Results}](const auto &Entry) -> std::pair { + const auto &[First, Second] = Entry; + return std::make_pair(First, RowView(Results, &Second)); + }; + + return llvm::map_range(llvm::zip(Results->NodeCompressor, Results->ValTab), + Txn); + } + + [[nodiscard]] bool containsNode(ByConstRef Stmt) const { + return Results->NodeCompressor.getOrNull(Stmt) != std::nullopt; + } + + [[nodiscard]] RowView row(ByConstRef Stmt) const { + auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + assert(NodeId); + return RowView(Results, &Results->ValTab[*NodeId]); + } + + template + void foreachResultEntry(HandlerFn Handler) const { + for (const auto &[Row, RowMap] : getAllResultEntries()) { + for (const auto &[Col, Val] : RowMap) { + std::invoke(Handler, std::make_tuple(Row, Col, Val)); + } + } + } + +private: + const detail::IterativeIDESolverResults *Results{}; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_IDBASEDSOLVERRESULTS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h new file mode 100644 index 000000000..bab1509d0 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -0,0 +1,1330 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H + +#include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" +#include "phasar/DataFlow/IfdsIde/Solver/Compressor.h" +#include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h" +#include "phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h" +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h" +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h" +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.h" +#include "phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h" +#include "phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h" +#include "phasar/DataFlow/IfdsIde/SolverResults.h" +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/StableVector.h" +#include "phasar/Utils/TableWrappers.h" +#include "phasar/Utils/TypeTraits.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace psr { + +/// Solves the given IDETabulationProblem as described in the 1996 paper by +/// Sagiv, Horwitz and Reps. To solve the problem, call solve(). +/// +/// This solver implements the optimizations and the $JF_N$ layout from the +/// paper "Scaling Interprocedural Static Data-Flow Analysis to Large C/C++ +/// Applications: An Experience Report" +/// (https://doi.org/10.4230/LIPIcs.ECOOP.2024.36) by Schiebel, Sattler, +/// Schubert, Apel, and Bodden. +template > +class IterativeIDESolver + : private SolverStatsSelector, + private detail::IterativeIDESolverResults< + typename ProblemTy::ProblemAnalysisDomain::n_t, + typename ProblemTy::ProblemAnalysisDomain::d_t, + std::conditional_t>, + public IterativeIDESolverBase< + StaticSolverConfigTy, + typename StaticSolverConfigTy::template EdgeFunctionPtrType< + typename ProblemTy::ProblemAnalysisDomain::l_t>> { +public: + using domain_t = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename domain_t::d_t; + using n_t = typename domain_t::n_t; + using f_t = typename domain_t::f_t; + using t_t = typename domain_t::t_t; + using v_t = typename domain_t::v_t; + using l_t = std::conditional_t; + using i_t = typename domain_t::i_t; + + using config_t = StaticSolverConfigTy; + +private: + using base_t = IterativeIDESolverBase< + StaticSolverConfigTy, + typename StaticSolverConfigTy::template EdgeFunctionPtrType< + typename domain_t::l_t>>; + + using base_results_t = detail::IterativeIDESolverResults; + + using base_t::ComputeValues; + using base_t::EnableStatistics; + static constexpr bool UseEndSummaryTab = config_t::UseEndSummaryTab; + using typename base_t::EdgeFunctionPtrType; + using typename base_t::InterPropagationJob; + using typename base_t::InterPropagationJobRef; + using typename base_t::InterPropagationJobRefDSI; + using typename base_t::PropagationJob; + using typename base_t::SummaryEdge; + using typename base_t::SummaryEdges; + using typename base_t::SummaryEdges_JF1; + using typename base_t::ValuePropagationJob; + + using base_results_t::FactCompressor; + using base_results_t::NodeCompressor; + using base_results_t::ValCompressor; + + template + using map_t = typename base_t::template map_t; + + template using set_t = typename base_t::template set_t; + + template + using worklist_t = typename base_t::template worklist_t; + + using flow_edge_function_cache_t = + typename base_t::template flow_edge_function_cache_t< + ProblemTy, StaticSolverConfigTy::AutoAddZero>; + + using typename base_t::summaries_t; + + static inline constexpr JumpFunctionGCMode EnableJumpFunctionGC = + StaticSolverConfigTy::EnableJumpFunctionGC; + + static inline ProblemTy &assertNotNull(ProblemTy *Problem) noexcept { + /// Dereferencing a nullptr is UB, so after initializing this->Problem the + /// null-check might be optimized away to the literal 'true'. + /// However, we still want to pass a pointer to the ctor to make clear that + /// the _reference_ of the problem is captured. + assert(Problem && + "IterativeIDESolver: The IDETabulationProblem must not be null!"); + return *Problem; + } + + static inline const i_t &assertNotNull(const i_t *ICFG) noexcept { + /// Dereferencing a nullptr is UB, so after initializing this->ICFG the + /// null-check might be optimized away to the literal 'true'. + /// However, we still want to pass a pointer to the ctor to make clear that + /// the _reference_ of the problem is captured. + assert(ICFG && "IterativeIDESolver: The ICFG must not be null!"); + return *ICFG; + } + +public: + IterativeIDESolver(ProblemTy *Problem, const i_t *ICFG) noexcept + : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)) {} + + void solve() { + const auto NumInsts = Problem.getProjectIRDB()->getNumInstructions(); + const auto NumFuns = Problem.getProjectIRDB()->getNumFunctions(); + + NodeCompressor = + NodeCompressorTraits::create(Problem.getProjectIRDB()); + + JumpFunctions.reserve(NumInsts); + this->base_results_t::ValTab.reserve(NumInsts); + + /// Initial size of 64 is too much for jump functions per instruction; 16 + /// should be better: + for (size_t I = 0; I != NumInsts; ++I) { + JumpFunctions.emplace_back(); + this->base_results_t::ValTab.emplace_back().reserve(16); + } + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + RefCountPerFunction = llvm::OwningArrayRef(NumFuns); + std::uninitialized_fill_n(RefCountPerFunction.data(), + RefCountPerFunction.size(), 0); + CandidateFunctionsForGC.resize(NumFuns); + } + + NodeCompressor.reserve(NumInsts); + FactCompressor.reserve(NumInsts); + FunCompressor.reserve(NumFuns); + FECache.reserve(NumInsts, ICFG.getNumCallSites(), NumFuns); + /// Make sure, that the Zero-flowfact always has the ID 0 + FactCompressor.getOrInsert(Problem.getZeroValue()); + + performDataflowFactPropagation(); + + /// Finished Phase I, now go for Phase II if necessary + performValuePropagation(); + } + + [[nodiscard]] IdBasedSolverResults + getSolverResults() const noexcept { + return IdBasedSolverResults(this); + } + + void dumpResults(llvm::raw_ostream &OS = llvm::outs()) const { + OS << "\n***************************************************************\n" + << "* Raw IDESolver results *\n" + << "***************************************************************\n"; + auto Cells = this->base_results_t::ValTab_cellVec(); + if (Cells.empty()) { + OS << "No results computed!\n"; + return; + } + + std::sort(Cells.begin(), Cells.end(), [](const auto &Lhs, const auto &Rhs) { + if constexpr (std::is_same_v) { + return StringIDLess{}(getMetaDataID(Lhs.getRowKey()), + getMetaDataID(Rhs.getRowKey())); + } else { + // If non-LLVM IR is used + return Lhs.getRowKey() < Rhs.getRowKey(); + } + }); + + n_t Prev{}; + n_t Curr{}; + f_t PrevFn{}; + f_t CurrFn{}; + + for (const auto &Cell : Cells) { + Curr = Cell.getRowKey(); + CurrFn = ICFG.getFunctionOf(Curr); + if (PrevFn != CurrFn) { + PrevFn = CurrFn; + OS << "\n\n============ Results for function '" + + ICFG.getFunctionName(CurrFn) + "' ============\n"; + } + if (Prev != Curr) { + Prev = Curr; + std::string NString = NToString(Curr); + std::string Line(NString.size(), '-'); + + OS << "\n\nN: " << NString << "\n---" << Line << '\n'; + } + OS << "\tD: " << DToString(Cell.getColumnKey()); + if constexpr (ComputeValues) { + OS << " | V: " << LToString(Cell.getValue()); + } + + OS << '\n'; + } + + OS << '\n'; + } + + template > + [[nodiscard]] IterativeIDESolverStats getStats() const noexcept { + return *this; + } + + template > + void dumpStats(llvm::raw_ostream &OS = llvm::outs()) const { + OS << getStats(); + } + +private: + void performDataflowFactPropagation() { + submitInitialSeeds(); + + std::atomic_bool Finished = true; + do { + /// NOTE: Have a separate function on the worklist to process it, to + /// allow for easier integration with task-pools + WorkList.processEntriesUntilEmpty([this, &Finished](PropagationJob Job) { + /// propagate only handles intra-edges as of now - add separate + /// functionality to handle inter-edges as well + propagate(Job.AtInstruction, Job.SourceFact, Job.PropagatedFact, + std::move(Job.SourceEF)); + bool Dummy = true; + Finished.compare_exchange_strong( + Dummy, false, std::memory_order_release, std::memory_order_relaxed); + }); + +#ifndef NDEBUG + // Sanity checks + if (llvm::any_of(RefCountPerFunction, [](auto RC) { return RC != 0; })) { + llvm::report_fatal_error( + "Worklist.empty() does not imply Function ref-counts==0 ?"); + } + + if (!WorkList.empty()) { + llvm::report_fatal_error( + "Worklist should be empty after processing all items"); + } +#endif // NDEBUG + + assert(WorkList.empty() && + "Worklist should be empty after processing all items"); + + processInterJobs(); + + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + /// CAUTION: The functions from the CallWL also need to be considered + /// live! We therefore need to be careful when applying this GC in a + /// multithreaded environment + + runGC(); + } + + } while (Finished.exchange(true, std::memory_order_acq_rel) == false); + + if constexpr (EnableStatistics) { + this->FEStats = FECache.getStats(); + this->NumAllInterPropagations = AllInterPropagations.size(); + this->AllInterPropagationsBytes = + AllInterPropagations.getApproxSizeInBytes() + + AllInterPropagationsOwner.getApproxSizeInBytes(); + this->SourceFactAndCSToInterJobSize = SourceFactAndCSToInterJob.size(); + this->SourceFactAndCSToInterJobBytes = + SourceFactAndCSToInterJob.getApproxSizeInBytes(); + this->SourceFactAndFuncToInterJobSize = + SourceFactAndFuncToInterJob.size(); + this->SourceFactAndFuncToInterJobBytes = + SourceFactAndFuncToInterJob.getApproxSizeInBytes(); + this->NumFlowFacts = FactCompressor.size(); + this->InstCompressorCapacity = NodeCompressor.capacity(); + this->FactCompressorCapacity = FactCompressor.capacity(); + this->FunCompressorCapacity = FunCompressor.capacity(); + + this->JumpFunctionsMapBytes = JumpFunctions.capacity_in_bytes(); + this->CumulESGEdges = 0; + this->MaxESGEdgesPerInst = 0; + for (const auto &JF : JumpFunctions) { + this->JumpFunctionsMapBytes += JF.getApproxSizeInBytes(); + this->CumulESGEdges += JF.size(); + this->MaxESGEdgesPerInst = + std::max(this->MaxESGEdgesPerInst, JF.size()); + } + + this->ValTabBytes = this->base_results_t::ValTab.capacity_in_bytes(); + for (const auto &FactsVals : this->base_results_t::ValTab) { + this->ValTabBytes += FactsVals.getApproxSizeInBytes(); + } + + this->AvgESGEdgesPerInst = + double(this->CumulESGEdges) / JumpFunctions.size(); + + if constexpr (UseEndSummaryTab) { + this->EndSummaryTabSize = EndSummaryTab.getApproxSizeInBytes(); + this->NumEndSummaries = 0; + for (const auto &Summary : EndSummaryTab.cells()) { + this->NumEndSummaries += Summary.second.size(); + this->EndSummaryTabSize += Summary.second.getMemorySize(); + } + } + } + + FECache.clearFlowFunctions(); + SourceFactAndFuncToInterJob.clear(); + WorkList.clear(); + CallWL.clear(); + } + + void performValuePropagation() { + if constexpr (ComputeValues) { + /// NOTE: We can already clear the EFCache here, as we are not querying + /// any edge function in Phase II; The EFs that are in use are kept alive + /// by their shared_ptr + FECache.clear(); + + submitInitialValues(); + WLProp.processEntriesUntilEmpty([this](ValuePropagationJob Job) { + propagateValue(Job.Inst, Job.Fact, std::move(Job.Value)); + }); + + WLProp.clear(); + + for (uint32_t SP : WLComp) { + computeValues(SP); + } + + if constexpr (EnableStatistics) { + this->WLCompHighWatermark = WLComp.size(); + } + + WLComp.clear(); + } + + JumpFunctions.clear(); + SourceFactAndCSToInterJob.clear(); + AllInterPropagations.clear(); + AllInterPropagationsOwner.clear(); + } + + void submitInitialSeeds() { + auto Seeds = Problem.initialSeeds(); + EdgeFunctionPtrType IdFun = [] { + if constexpr (ComputeValues) { + return EdgeIdentity{}; + } else { + return EdgeFunctionPtrType{}; + } + }(); + + for (const auto &[Inst, SeedMap] : Seeds.getSeeds()) { + auto InstId = NodeCompressor.getOrInsert(Inst); + auto Fun = FunCompressor.getOrInsert(ICFG.getFunctionOf(Inst)); + for (const auto &[Fact, Val] : SeedMap) { + auto FactId = FactCompressor.getOrInsert(Fact); + auto &JumpFns = JumpFunctions[InstId]; + + storeResultsAndPropagate(JumpFns, InstId, FactId, FactId, Fun, IdFun); + } + } + } + + template + [[nodiscard]] bool + keepAnalysisInformationAt(ByConstRef Inst) const noexcept { + if (ICFG.isExitInst(Inst) || ICFG.isStartPoint(Inst)) { + /// Keep the procedure summaries + starting points, such that + /// already analyzed paths are not analyzed again + return true; + } + + if (llvm::any_of(ICFG.getPredsOf(Inst), [ICF{&ICFG}](ByConstRef Pred) { + return ICF->isCallSite(Pred); + })) { + /// Keep the return-sites + return true; + } + + if constexpr (has_isInteresting_v && + (Set || (!Set && ComputeValues))) { + if (Problem.isInteresting(Inst)) { + /// Keep analysis information at interesting instructions + return true; + } + } + return false; + } + + ByConstRef get(uint32_t Inst, uint32_t Fact) { + return ValCompressor[this->base_results_t::ValTab[size_t(Inst)].getOrCreate( + Fact)]; + } + + void set(uint32_t Inst, uint32_t Fact, l_t Val) { + if constexpr (EnableJumpFunctionGC == + JumpFunctionGCMode::EnabledAggressively) { + if (!keepAnalysisInformationAt(NodeCompressor[Inst])) { + return; + } + } + + auto &Dest = this->base_results_t::ValTab[size_t(Inst)].getOrCreate(Fact); + if constexpr (!std::is_const_v>) { + Dest = ValCompressor.getOrInsert(std::move(Val)); + } + } + + template + std::enable_if_t + storeResultsAndPropagate(SummaryEdges &JumpFns, uint32_t SuccId, + uint32_t SourceFact, uint32_t LocalFact, + uint32_t FunId, EdgeFunctionPtrType LocalEF) { + auto &EF = JumpFns.getOrCreate(combineIds(SourceFact, LocalFact)); + if (!EF) { + EF = std::move(LocalEF); + /// Register new propagation job as we haven't seen this + /// fact here yet + + WorkList.emplace(PropagationJob{EF, SuccId, SourceFact, LocalFact}); + + set(SuccId, LocalFact, Problem.topElement()); + + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + RefCountPerFunction[FunId]++; + CandidateFunctionsForGC.set(FunId); + } + + if constexpr (EnableStatistics) { + this->NumPathEdges++; + if (this->NumPathEdges > this->NumPathEdgesHighWatermark) { + this->NumPathEdgesHighWatermark = this->NumPathEdges; + } + if (WorkList.size() > this->WorkListHighWatermark) { + this->WorkListHighWatermark = WorkList.size(); + } + } + return true; + } + + auto NewEF = EF.joinWith(std::move(LocalEF)); + assert(NewEF != nullptr); + + if (NewEF != EF) { + /// Register new propagation job as we have refined the + /// edge-function + EF = NewEF; + WorkList.emplace( + PropagationJob{std::move(NewEF), SuccId, SourceFact, LocalFact}); + + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + RefCountPerFunction[FunId]++; + CandidateFunctionsForGC.set(FunId); + } + return true; + } + return false; + } + template + std::enable_if_t + storeResultsAndPropagate(SummaryEdges &JumpFns, uint32_t SuccId, + uint32_t SourceFact, uint32_t LocalFact, + uint32_t FunId, EdgeFunctionPtrType /*LocalEF*/) { + if (JumpFns.insert(combineIds(SourceFact, LocalFact)).second) { + WorkList.emplace(PropagationJob{{}, SuccId, SourceFact, LocalFact}); + + set(SuccId, LocalFact, BinaryDomain::TOP); + + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + RefCountPerFunction[FunId]++; + + CandidateFunctionsForGC.set(FunId); + } + if constexpr (EnableStatistics) { + this->NumPathEdges++; + if (this->NumPathEdges > this->NumPathEdgesHighWatermark) { + this->NumPathEdgesHighWatermark = this->NumPathEdges; + } + if (WorkList.size() > this->WorkListHighWatermark) { + this->WorkListHighWatermark = WorkList.size(); + } + } + return true; + } + return false; + } + + template + std::enable_if_t storeSummary(SummaryEdges_JF1 &JumpFns, + uint32_t LocalFact, + EdgeFunctionPtrType LocalEF) { + auto &EF = JumpFns[LocalFact]; + if (!EF) { + EF = std::move(LocalEF); + /// Register new propagation job as we haven't seen this + /// fact here yet + + if constexpr (EnableStatistics) { + this->NumPathEdges++; + if (this->NumPathEdges > this->NumPathEdgesHighWatermark) { + this->NumPathEdgesHighWatermark = this->NumPathEdges; + } + } + return; + } + + auto NewEF = EF.joinWith(std::move(LocalEF)); + assert(NewEF != nullptr); + + if (NewEF != EF) { + /// Register new propagation job as we have refined the + /// edge-function + EF = NewEF; + } + } + + template + std::enable_if_t storeSummary(SummaryEdges_JF1 &JumpFns, + uint32_t LocalFact, + EdgeFunctionPtrType /*LocalEF*/) { + if (JumpFns.insert({LocalFact, {}}).second) { + if constexpr (EnableStatistics) { + this->NumPathEdges++; + if (this->NumPathEdges > this->NumPathEdgesHighWatermark) { + this->NumPathEdgesHighWatermark = this->NumPathEdges; + } + } + } + } + + void propagate(uint32_t AtInstructionId, uint32_t SourceFact, + uint32_t PropagatedFact, EdgeFunctionPtrType SourceEF) { + + auto AtInstruction = NodeCompressor[AtInstructionId]; + + auto FunId = [=] { + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + + auto Ret = FunCompressor.getOrInsert(ICFG.getFunctionOf(AtInstruction)); + + assert(RefCountPerFunction[Ret] > 0); + RefCountPerFunction[Ret]--; + return Ret; + } else { + (void)this; + (void)AtInstruction; + return 0; + } + }(); + + applyFlowFunction(AtInstruction, AtInstructionId, SourceFact, + PropagatedFact, FunId, std::move(SourceEF)); + } + + void applyFlowFunction(ByConstRef AtInstruction, + uint32_t AtInstructionId, uint32_t SourceFactId, + uint32_t PropagatedFactId, uint32_t FunId, + EdgeFunctionPtrType SourceEF) { + + if (!ICFG.isCallSite(AtInstruction)) { + applyNormalFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, std::move(SourceEF), FunId); + } else { + applyIntraCallFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, std::move(SourceEF), FunId); + } + } + + void applyNormalFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, + uint32_t SourceFactId, uint32_t PropagatedFactId, + EdgeFunctionPtrType SourceEF, uint32_t FunId) { + + auto CSFact = FactCompressor[PropagatedFactId]; + + for (ByConstRef Succ : ICFG.getSuccsOf(AtInstruction)) { + auto SuccId = NodeCompressor.getOrInsert(Succ); + auto Facts = + FECache + .getNormalFlowFunction(Problem, AtInstruction, Succ, + combineIds(AtInstructionId, SuccId)) + .computeTargets(CSFact); + + auto &JumpFns = JumpFunctions[SuccId]; + + for (ByConstRef Fact : Facts) { + auto FactId = FactCompressor.getOrInsert(Fact); + auto EF = [&] { + if constexpr (ComputeValues) { + return SourceEF.composeWith(FECache.getNormalEdgeFunction( + Problem, AtInstruction, CSFact, Succ, Fact, + combineIds(AtInstructionId, SuccId), + combineIds(PropagatedFactId, FactId))); + } else { + return EdgeFunctionPtrType{}; + } + }(); + + storeResultsAndPropagate(JumpFns, SuccId, SourceFactId, FactId, FunId, + std::move(EF)); + } + } + + /// NOTE: Is isExitInst, we did not enter the above loop. So, we can only + /// have reached here, if the PropagatedFact (or at least the SourceEF) was + /// new at AtInstruction. + if (ICFG.isExitInst(AtInstruction)) { + if constexpr (EnableJumpFunctionGC == JumpFunctionGCMode::Disabled) { + auto Fun = ICFG.getFunctionOf(AtInstruction); + FunId = FunCompressor.getOrInsert(std::move(Fun)); + } + + /// These call-sites might have already been processed, but since we have + /// now new summary information, we must reschedule them all for + /// processing in Step3 + CallWL.insert(combineIds(SourceFactId, FunId)); + + if constexpr (UseEndSummaryTab) { + storeSummary(EndSummaryTab.getOrCreate(combineIds(FunId, SourceFactId)), + PropagatedFactId, std::move(SourceEF)); + } + } + } + + void applyIntraCallFlow(ByConstRef AtInstruction, + uint32_t AtInstructionId, uint32_t SourceFactId, + uint32_t PropagatedFactId, + EdgeFunctionPtrType SourceEF, uint32_t FunId) { + const auto &Callees = ICFG.getCalleesOfCallAt(AtInstruction); + + applyCallToReturnFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, SourceEF, Callees, FunId); + + handleOrDeferCallFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, std::move(SourceEF), Callees, + FunId); + } + + template + void applyCallToReturnFlow(ByConstRef AtInstruction, + uint32_t AtInstructionId, uint32_t SourceFactId, + uint32_t PropagatedFactId, + EdgeFunctionPtrType SourceEF, + const CalleesTy &Callees, uint32_t FunId) { + auto CSFact = FactCompressor[PropagatedFactId]; + + for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { + auto RetSiteId = NodeCompressor.getOrInsert(RetSite); + auto Facts = FECache + .getCallToRetFlowFunction( + Problem, AtInstruction, RetSite, Callees /*Vec*/, + combineIds(AtInstructionId, RetSiteId)) + .computeTargets(CSFact); + + auto &JumpFns = JumpFunctions[RetSiteId]; + + for (ByConstRef Fact : Facts) { + auto FactId = FactCompressor.getOrInsert(Fact); + + auto EF = [&] { + if constexpr (ComputeValues) { + return SourceEF.composeWith(FECache.getCallToRetEdgeFunction( + Problem, AtInstruction, CSFact, RetSite, Fact, Callees /*Vec*/, + combineIds(AtInstructionId, RetSiteId), + combineIds(PropagatedFactId, FactId))); + } else { + return EdgeFunctionPtrType{}; + } + }(); + + storeResultsAndPropagate(JumpFns, RetSiteId, SourceFactId, FactId, + FunId, std::move(EF)); + } + } + + /// NOTE: A CallSite can never be an exit-inst + } + + template + void handleOrDeferCallFlow(ByConstRef AtInstruction, + uint32_t AtInstructionId, uint32_t SourceFactId, + uint32_t PropagatedFactId, + EdgeFunctionPtrType SourceEF, + const CalleesTy &Callees, uint32_t FunId) { + auto CSFact = FactCompressor[PropagatedFactId]; + for (ByConstRef Callee : Callees) { + auto CalleeId = FunCompressor.getOrInsert(Callee); + auto SummaryFF = + FECache.getSummaryFlowFunction(Problem, AtInstruction, Callee, + combineIds(AtInstructionId, CalleeId)); + + if (SummaryFF == nullptr) { + /// No summary. Start inTRA propagation for the callee and defer + /// return-propagation + deferCallFlow(AtInstruction, AtInstructionId, SourceFactId, CSFact, + PropagatedFactId, SourceEF, Callee, CalleeId, FunId); + } else { + /// Apply SummaryFF and ignore this CSCallee pair in the + /// inter-propagation + applySummaryFlow(SummaryFF.computeTargets(CSFact), AtInstruction, + AtInstructionId, SourceFactId, CSFact, + PropagatedFactId, SourceEF, FunId); + } + } + } + + void countSummaryLinearSearch(size_t SearchLen, size_t NumSummaries) { + if constexpr (UseEndSummaryTab) { + SearchLen = NumSummaries; + } + + if constexpr (EnableStatistics) { + this->TotalNumLinearSearchForSummary++; + this->CumulLinearSearchLenForSummary += SearchLen; + this->MaxLenLinearSearchForSummary = + std::max(this->MaxLenLinearSearchForSummary, SearchLen); + this->CumulDiffNumSummariesFound += (SearchLen - NumSummaries); + this->MaxDiffNumSummariesFound = + std::max(this->MaxDiffNumSummariesFound, (SearchLen - NumSummaries)); + + this->CumulRelDiffNumSummariesFound += + SearchLen ? double(NumSummaries) / double(SearchLen) : 1; + } + } + + void applyEarlySummariesAtCall(ByConstRef AtInstruction, + uint32_t AtInstructionId, + ByConstRef Callee, uint32_t CalleeId, + uint32_t FactId, uint32_t SourceFactId, + uint32_t FunId, EdgeFunctionPtrType CallEF) { + /* if (HasResults)*/ { + /// Lines 15.2-15.6 in Naeem's paper: + auto CallerId = [this, FunId, AtInstruction] { + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + (void)this; + (void)AtInstruction; + return FunId; + } else { + (void)FunId; + return FunCompressor.getOrInsert(ICFG.getFunctionOf(AtInstruction)); + } + }(); + + summaries_t Summaries; + if constexpr (UseEndSummaryTab) { + const auto &SummariesTab = + EndSummaryTab.getOrCreate(combineIds(CalleeId, FactId)); + Summaries = summaries_t(SummariesTab.begin(), SummariesTab.end()); + } + + for (ByConstRef ExitInst : ICFG.getExitPointsOf(Callee)) { + auto ExitId = NodeCompressor.getOrInsert(ExitInst); + + if constexpr (!UseEndSummaryTab) { + // Summaries = JumpFunctions[ExitId].cellVec([FactId](const auto &Kvp) + // { + // return splitId(Kvp.first).first == FactId; + // }); + Summaries = JumpFunctions[ExitId].allOf( + [](uint64_t Key) { return splitId(Key).first; }, FactId, + [](uint64_t Key) { return splitId(Key).second; }); + } + + countSummaryLinearSearch(JumpFunctions[ExitId].size(), + Summaries.size()); + + if (!Summaries.empty()) { + propagateProcedureSummaries(Summaries, AtInstruction, AtInstructionId, + Callee, CalleeId, ExitInst, ExitId, + SourceFactId, CallerId, CallEF); + } + } + } + } + + void deferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, + uint32_t SourceFactId, ByConstRef CSFact, + uint32_t CSFactId, EdgeFunctionPtrType SourceEF, + ByConstRef Callee, uint32_t CalleeId, + uint32_t FunId) { + auto CalleeFacts = + FECache + .getCallFlowFunction(Problem, AtInstruction, Callee, + combineIds(AtInstructionId, CalleeId)) + .computeTargets(CSFact); + + EdgeFunctionPtrType IdEF = [] { + if constexpr (ComputeValues) { + return EdgeIdentity{}; + } else { + return EdgeFunctionPtrType{}; + } + }(); + for (ByConstRef SP : ICFG.getStartPointsOf(Callee)) { + auto SPId = NodeCompressor.getOrInsert(SP); + auto &JumpFn = JumpFunctions[SPId]; + // bool HasResults = !JumpFn.empty(); + for (ByConstRef Fact : CalleeFacts) { + auto FactId = FactCompressor.getOrInsert(Fact); + + auto CallEF = [&] { + if constexpr (ComputeValues) { + return SourceEF.composeWith(FECache.getCallEdgeFunction( + Problem, AtInstruction, CSFact, Callee, Fact, + combineIds(AtInstructionId, CalleeId), + combineIds(CSFactId, FactId))); + } else { + return EdgeFunctionPtrType{}; + } + }(); + + storeResultsAndPropagate(JumpFn, SPId, FactId, FactId, CalleeId, IdEF); + + // CallWL.insert(combineIds(FactId, CalleeId)); + + auto It = &AllInterPropagationsOwner.emplace_back(InterPropagationJob{ + CallEF, SourceFactId, CalleeId, AtInstructionId, FactId}); + auto Inserted = + AllInterPropagations.insert(InterPropagationJobRef{It}).second; + if (Inserted) { + applyEarlySummariesAtCall(AtInstruction, AtInstructionId, Callee, + CalleeId, FactId, SourceFactId, FunId, + CallEF); + + { + /// Reverse lookup + auto *&InterJob = SourceFactAndFuncToInterJob.getOrCreate( + combineIds(FactId, CalleeId)); + It->NextWithSameSourceFactAndCallee = InterJob; + InterJob = &*It; + } + + if constexpr (ComputeValues) { + /// Forward lookup + auto *&InterJob = SourceFactAndCSToInterJob.getOrCreate( + combineIds(SourceFactId, AtInstructionId)); + It->NextWithSameSourceFactAndCS = InterJob; + InterJob = &*It; + } + } else { + /// The InterPropagationJob was already there, so we don't need to own + /// it + AllInterPropagationsOwner.pop_back(); + } + } + } + } + + template + void applySummaryFlow(const SummaryFactsTy &SummaryFacts, + ByConstRef AtInstruction, uint32_t AtInstructionId, + uint32_t SourceFactId, ByConstRef CSFact, + uint32_t CSFactId, EdgeFunctionPtrType SourceEF, + uint32_t FunId) { + for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { + auto RetSiteId = NodeCompressor.getOrInsert(RetSite); + auto &JumpFns = JumpFunctions[RetSiteId]; + + for (ByConstRef Fact : SummaryFacts) { + auto FactId = FactCompressor.getOrInsert(Fact); + + auto EF = [&] { + if constexpr (ComputeValues) { + auto EF = FECache.getSummaryEdgeFunction( + Problem, AtInstruction, CSFact, RetSite, Fact, + combineIds(AtInstructionId, RetSiteId), + combineIds(CSFactId, FactId)); + return EF ? SourceEF.composeWith(std::move(EF)) : SourceEF; + } else { + return EdgeFunctionPtrType{}; + } + }(); + + storeResultsAndPropagate(JumpFns, RetSiteId, SourceFactId, FactId, + FunId, std::move(EF)); + } + } + } + + void propagateProcedureSummaries(const summaries_t &Summaries, + ByConstRef CallSite, uint32_t CSId, + ByConstRef Callee, uint32_t CalleeId, + ByConstRef ExitInst, uint32_t ExitId, + uint32_t SourceFact, uint32_t CallerId, + EdgeFunctionPtrType CallEF) { + for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(CallSite)) { + auto RSId = NodeCompressor.getOrInsert(RetSite); + auto RetFF = FECache.getRetFlowFunction( + Problem, CallSite, Callee, ExitInst, RetSite, + combineIds(CSId, ExitId), combineIds(CalleeId, RSId)); + + auto &RSJumpFns = JumpFunctions[RSId]; + + for (const auto &Summary : Summaries) { + uint32_t SummaryFactId{Summary.first}; + auto SummaryFact = FactCompressor[SummaryFactId]; + auto RetFacts = RetFF.computeTargets(SummaryFact); + for (ByConstRef RetFact : RetFacts) { + auto RetFactId = FactCompressor.getOrInsert(RetFact); + + auto EF = [&]() mutable { + if constexpr (ComputeValues) { + auto RetEF = FECache.getReturnEdgeFunction( + Problem, CallSite, Callee, ExitInst, SummaryFact, RetSite, + RetFact, ExitId, combineIds(CSId, RSId), + combineIds(SummaryFactId, RetFactId)); + return CallEF.composeWith(Summary.second) + .composeWith(std::move(RetEF)); + } else { + return EdgeFunctionPtrType{}; + } + }(); + + storeResultsAndPropagate(RSJumpFns, RSId, SourceFact, RetFactId, + CallerId, std::move(EF)); + } + } + } + } + + void processInterJobs() { + + llvm::errs() << "processInterJobs: " << CallWL.size() + << " relevant calls\n"; + + /// Here, no other job is running concurrently, so we save and reset the + /// CallWL, such that we can start concurrent jobs in the loop below + std::vector RelevantCalls(CallWL.begin(), CallWL.end()); + + scope_exit FinishedInterCalls = [] { + llvm::errs() << "> end inter calls\n"; + }; + + if constexpr (EnableStatistics) { + if (CallWL.size() > this->CallWLHighWatermark) { + this->CallWLHighWatermark = CallWL.size(); + } + } + + CallWL.clear(); + + for (auto SourceFactAndFunc : RelevantCalls) { + auto [SPFactId, CalleeId] = splitId(SourceFactAndFunc); + auto Callee = FunCompressor[CalleeId]; + + if constexpr (EnableStatistics) { + this->TotalNumRelevantCalls++; + } + + summaries_t Summaries; + if constexpr (UseEndSummaryTab) { + const auto &SummariesTab = + EndSummaryTab.getOrCreate(combineIds(CalleeId, SPFactId)); + Summaries = summaries_t(SummariesTab.begin(), SummariesTab.end()); + } + + size_t NumInterJobs = 0; + for (ByConstRef ExitInst : ICFG.getExitPointsOf(Callee)) { + auto ExitId = NodeCompressor.getOrInsert(ExitInst); + + /// Copy the JumpFns, because in the below loop we are calling + /// getOrCreate on JumpFunctions again and in a tight recursion + /// ExitId and RSId might be the same and inserting into the same + /// map we are iterating over is bad + + if constexpr (!UseEndSummaryTab) { + // Summaries = JumpFunctions[ExitId].cellVec( + // [SPFactId{SPFactId}](const auto &Kvp) { + // return splitId(Kvp.first).first == SPFactId; + // }); + Summaries = JumpFunctions[ExitId].allOf( + [](uint64_t Key) { return splitId(Key).first; }, SPFactId, + [](uint64_t Key) { return splitId(Key).second; }); + } + + countSummaryLinearSearch(JumpFunctions[ExitId].size(), + Summaries.size()); + + for (const InterPropagationJob *InterJob = + SourceFactAndFuncToInterJob.getOr(SourceFactAndFunc, nullptr); + InterJob; InterJob = InterJob->NextWithSameSourceFactAndCallee) { + + auto CSId = InterJob->CallSite; + auto CallSite = NodeCompressor[CSId]; + auto CallerId = + FunCompressor.getOrInsert(ICFG.getFunctionOf(CallSite)); + + if constexpr (EnableStatistics) { + ++NumInterJobs; + } + propagateProcedureSummaries( + Summaries, CallSite, CSId, Callee, CalleeId, ExitInst, ExitId, + InterJob->SourceFact, CallerId, InterJob->SourceEF); + } + } + + if constexpr (EnableStatistics) { + this->CumulNumInterJobsPerRelevantCall += NumInterJobs; + this->MaxNumInterJobsPerRelevantCall = + std::max(this->MaxNumInterJobsPerRelevantCall, NumInterJobs); + } + } + } + + template > + void submitInitialValues() { + auto Seeds = Problem.initialSeeds(); + for (const auto &[Inst, SeedMap] : Seeds.getSeeds()) { + auto InstId = NodeCompressor.getOrInsert(Inst); + for (const auto &[Fact, Val] : SeedMap) { + auto FactId = FactCompressor.getOrInsert(Fact); + + WLProp.emplace(ValuePropagationJob{InstId, FactId, Val}); + } + } + } + + template > + void propagateValue(uint32_t SPId, uint32_t FactId, l_t Val) { + + /// TODO: Unbalanced return-sites + + auto SP = NodeCompressor[SPId]; + + PHASAR_LOG_LEVEL_CAT(DEBUG, "IterativeIDESolver", + "propagateValue for N: " + << NToString(SP) + << "; D: " << DToString(FactCompressor[FactId]) + << "; L: " << LToString(Val)); + + { + ByConstRef StoredVal = get(SPId, FactId); + auto NewVal = Problem.join(StoredVal, Val); + if (NewVal == StoredVal) { + /// Nothing new, so we have already seen this ValuePropagationJob + /// before. + /// NOTE: We propagate Bottom for the ZeroFact, such that at the + /// first iteration we always get a change here + /// NOTE: Need this early exit for termination in case of recursion + PHASAR_LOG_LEVEL_CAT(DEBUG, "IterativeIDESolver", + "> Value has already been seen!"); + + return; + } + set(SPId, FactId, std::move(NewVal)); + } + + PHASAR_LOG_LEVEL_CAT(DEBUG, "IterativeIDESolver", + "> Make ValuePropagationJob"); + + WLComp.insert(SPId); + + auto Fun = ICFG.getFunctionOf(SP); + + for (ByConstRef CS : ICFG.getCallsFromWithin(Fun)) { + PHASAR_LOG_LEVEL_CAT(DEBUG, "IterativeIDESolver", + "> CS: " << NToString(CS)); + + auto InstId = NodeCompressor.getOrInsert(CS); + + const InterPropagationJob *InterJobs = + SourceFactAndCSToInterJob.getOr(combineIds(FactId, InstId), nullptr); + + for (; InterJobs; InterJobs = InterJobs->NextWithSameSourceFactAndCS) { + auto Callee = FunCompressor[InterJobs->Callee]; + + PHASAR_LOG_LEVEL_CAT(DEBUG, "IterativeIDESolver", + ">> Callee: " << FToString(Callee)); + + for (ByConstRef CalleeSP : ICFG.getStartPointsOf(Callee)) { + auto CalleeSPId = NodeCompressor.getOrInsert(CalleeSP); + + PHASAR_LOG_LEVEL_CAT( + DEBUG, "IterativeIDESolver", + "> emplace { N: " + << NToString(CalleeSP) << "; D: " + << DToString(FactCompressor[InterJobs->FactInCallee]) + << "; L: " + << LToString(InterJobs->SourceEF.computeTarget(Val)) + << " } into WLProp"); + + WLProp.emplace( + ValuePropagationJob{CalleeSPId, InterJobs->FactInCallee, + InterJobs->SourceEF.computeTarget(Val)}); + + if constexpr (EnableStatistics) { + if (WLProp.size() > this->WLPropHighWatermark) { + this->WLPropHighWatermark = WLProp.size(); + } + } + } + } + } + } + + template > + void computeValues(uint32_t SPId) { + auto SP = NodeCompressor[SPId]; + auto Fun = ICFG.getFunctionOf(SP); + + for (ByConstRef Inst : ICFG.getAllInstructionsOf(Fun)) { + if (Inst == SP) { + continue; + } + + if constexpr (EnableJumpFunctionGC == + JumpFunctionGCMode::EnabledAggressively && + has_isInteresting_v) { + if (!Problem.isInteresting(Inst)) { + continue; + } + } + + auto InstId = NodeCompressor.getOrInsert(Inst); + for (auto [SrcTgtFactId, EF] : JumpFunctions[InstId].cells()) { + auto [SrcFactId, TgtFactId] = splitId(SrcTgtFactId); + + ByConstRef Val = get(SPId, SrcFactId); + ByConstRef StoredVal = get(InstId, TgtFactId); + + auto NewVal = Problem.join(StoredVal, EF.computeTarget(std::move(Val))); + if (NewVal != StoredVal) { + set(InstId, TgtFactId, std::move(NewVal)); + } + } + } + } + + llvm::SmallBitVector getCollectableFunctions() { + llvm::SmallVector FunWorkList; + FunWorkList.reserve(CandidateFunctionsForGC.count()); + + llvm::SmallBitVector FinalCandidates(CandidateFunctionsForGC.size()); + + for (auto C : CandidateFunctionsForGC.set_bits()) { + if (RefCountPerFunction[C]) { + auto Fun = FunCompressor[C]; + const auto &Callers = ICFG.getCallersOf(Fun); + for (ByConstRef CS : Callers) { + FunWorkList.push_back( + FunCompressor.getOrInsert(ICFG.getFunctionOf(CS))); + } + } else { + FinalCandidates.set(C); + } + } + + while (!FunWorkList.empty()) { + auto FunId = FunWorkList.pop_back_val(); + + if (!FinalCandidates.test(FunId)) { + continue; + } + + FinalCandidates.reset(FunId); + + auto Fun = FunCompressor[FunId]; + const auto &Callers = ICFG.getCallersOf(Fun); + for (ByConstRef CS : Callers) { + FunWorkList.push_back( + FunCompressor.getOrInsert(ICFG.getFunctionOf(CS))); + } + } + + return FinalCandidates; + } + + void removeJumpFunctionsFor(ByConstRef Inst) { + if (keepAnalysisInformationAt(Inst)) { + return; + } + + size_t InstId = NodeCompressor.getOrInsert(Inst); + + if constexpr (EnableJumpFunctionGC == + JumpFunctionGCMode::EnabledAggressively) { + static_assert( + has_isInteresting_v, + "Aggressive JumpFunctionGC requires the TabulationProblem " + "to define a function 'bool isInteresting(n_t)' that prevents " + "analysis information at that instruction to be removed. " + "Otherwise, the analysis results will be empty!"); + // this->base_results_t::ValTab[InstId].clear(); + } + + if constexpr (EnableStatistics) { + this->NumPathEdges -= JumpFunctions[InstId].size(); + } + + JumpFunctions[InstId].clear(); + } + + void cleanupInterJobsFor(unsigned FunId) { + /// XXX: Use std::erase_if when upgrading to C++20 + + auto Cells = SourceFactAndFuncToInterJob.cells(); + for (auto Iter = Cells.begin(), End = Cells.end(); Iter != End;) { + auto It = Iter++; + auto CalleeId = splitId(It->first).second; + if (CalleeId == FunId) { + SourceFactAndFuncToInterJob.erase(It); + } + } + } + + void collectFunction(unsigned FunId) { + for (ByConstRef Inst : + ICFG.getAllInstructionsOf(FunCompressor[FunId])) { + removeJumpFunctionsFor(Inst); + } + + cleanupInterJobsFor(FunId); + + CandidateFunctionsForGC.reset(FunId); + } + + void runGC() { + llvm::errs() << "runGC() with " << CandidateFunctionsForGC.count() + << " candidates\n"; + + size_t NumCollectedFuns = 0; + + scope_exit FinishGC = [&NumCollectedFuns] { + llvm::errs() << "> Finished GC run (collected " << NumCollectedFuns + << " functions)\n"; + }; + + auto FinalCandidates = getCollectableFunctions(); + + for (auto Candidate : FinalCandidates.set_bits()) { + collectFunction(Candidate); + ++NumCollectedFuns; + } + } + + static constexpr uint64_t combineIds(uint32_t LHS, uint32_t RHS) noexcept { + return (uint64_t(LHS) << 32) | RHS; + } + static constexpr std::pair splitId(uint64_t Id) noexcept { + return {uint32_t(Id >> 32), uint32_t(Id & UINT32_MAX)}; + } + + ProblemTy &Problem; + const i_t &ICFG; + + Compressor FunCompressor{}; + + worklist_t WorkList{NodeCompressor, ICFG}; + + /// --> Begin InterPropagationJobs + + StableVector AllInterPropagationsOwner; + /// Intentionally storing pointers, since we capture them in the containers + /// below + DenseSet + AllInterPropagations{}; + /// Stores keys of the SourceFactAndFuncToInterJob map. All mapped values + /// should be part of the set + DenseSet CallWL{}; + // Stores pointers into AllInterPropagations building up an intrusive + // linked list of all jobs matching the key (CalleeSourceFact x Callee). + // == Reverse Lookup + DenseTable1d + SourceFactAndFuncToInterJob{}; + // Stores pointers into AllInterPropagations building up an intrusive + // linked list of all jobs matching the key (CSSourceFact x CS) + // == Forward Lookup + DenseTable1d + SourceFactAndCSToInterJob{}; + + /// <-- End InterPropagationJobs + + worklist_t WLProp{NodeCompressor, ICFG}; + DenseSet WLComp{}; + + /// Index represents the instruction + llvm::SmallVector JumpFunctions{}; + /// Index represents the function + std::conditional_t, + EmptyType> + EndSummaryTab; + + llvm::OwningArrayRef RefCountPerFunction{}; + llvm::BitVector CandidateFunctionsForGC{}; + + // FlowFunctionCache FFCache{ + // &MRes}; + // EdgeFunctionCache EFCache{&MRes}; + + flow_edge_function_cache_t FECache{Problem}; +}; + +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h new file mode 100644 index 000000000..9dfa44edf --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h @@ -0,0 +1,165 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVERBASE_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVERBASE_H + +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/PointerUtils.h" +#include "phasar/Utils/TableWrappers.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseMapInfo.h" + +#include + +namespace psr { + +template struct IterIDEPropagationJob { + [[no_unique_address]] EdgeFunctionPtrType SourceEF{}; + + uint32_t AtInstruction{}; + uint32_t SourceFact{}; + uint32_t PropagatedFact{}; +}; + +template +class IterativeIDESolverBase { +public: + static constexpr bool ComputeValues = StaticSolverConfigTy::ComputeValues; + static constexpr bool EnableStatistics = + StaticSolverConfigTy::EnableStatistics; + /// NOTE: EdgeFunctionPtrType may be either std::shared_ptr> + /// or llvm::IntrusiveRefCntPtr> once this is supported + using EdgeFunctionPtrType = + std::conditional_t; + +protected: + template + using map_t = typename StaticSolverConfigTy::template map_t; + + template + using set_t = typename StaticSolverConfigTy::template set_t; + + template + using worklist_t = typename StaticSolverConfigTy::template worklist_t; + + template + using flow_edge_function_cache_t = + typename StaticSolverConfigTy::template flow_edge_function_cache_t< + ProblemTy, AutoAddZero>; + + using PropagationJob = IterIDEPropagationJob; + + struct InterPropagationJob { + [[no_unique_address]] EdgeFunctionPtrType SourceEF{}; + + uint32_t SourceFact{}; + uint32_t Callee{}; + + uint32_t CallSite{}; + uint32_t FactInCallee{}; + + /// NOTE: The Next-pointer must be mutable, such that we are able to mutate + /// it from inside a set. Recall that the next pointer does *not* affect + /// equality or hashing + mutable const InterPropagationJob *NextWithSameSourceFactAndCallee{}; + [[no_unique_address]] mutable std::conditional_t< + ComputeValues, const InterPropagationJob *, EmptyType> + NextWithSameSourceFactAndCS{}; + + [[nodiscard]] bool + operator==(const InterPropagationJob &Other) const noexcept { + return SourceFact == Other.SourceFact && Callee == Other.Callee && + CallSite == Other.CallSite && FactInCallee == Other.FactInCallee && + SourceEF == Other.SourceEF; + } + + [[nodiscard]] bool + operator!=(const InterPropagationJob &Other) const noexcept { + return !(*this == Other); + } + + [[nodiscard]] size_t getHashCode() const noexcept { + if constexpr (ComputeValues) { + return llvm::hash_combine(SourceFact, Callee, CallSite, FactInCallee, + SourceEF.getOpaqueValue()); + } else { + return llvm::hash_combine(SourceFact, Callee, CallSite, FactInCallee); + } + } + }; + struct InterPropagationJobRef { + InterPropagationJob *JobPtr{}; + }; + + struct InterPropagationJobRefDSI { + static InterPropagationJobRef getEmptyKey() noexcept { + return {llvm::DenseMapInfo::getEmptyKey()}; + } + static InterPropagationJobRef getTombstoneKey() noexcept { + return {llvm::DenseMapInfo::getTombstoneKey()}; + } + static auto getHashValue(InterPropagationJobRef Job) noexcept { + assert(Job.JobPtr != nullptr); + assert(Job.JobPtr != getEmptyKey().JobPtr); + assert(Job.JobPtr != getTombstoneKey().JobPtr); + return Job.JobPtr->getHashCode(); + } + static bool isEqual(InterPropagationJobRef LHS, + InterPropagationJobRef RHS) noexcept { + if (LHS.JobPtr == RHS.JobPtr) { + return true; + } + if (LHS.JobPtr == getEmptyKey().JobPtr || + LHS.JobPtr == getTombstoneKey().JobPtr || + RHS.JobPtr == getEmptyKey().JobPtr || + RHS.JobPtr == getTombstoneKey().JobPtr) { + return false; + } + + assert(LHS.JobPtr != nullptr); + assert(RHS.JobPtr != nullptr); + return *LHS.JobPtr == *RHS.JobPtr; + } + }; + + union ValueComputationJob { + uint64_t Value{}; + struct { + uint32_t StartPoint; + uint32_t SourceFact; + }; + + ValueComputationJob(uint64_t Value) noexcept : Value(Value) {} + ValueComputationJob(uint32_t SP, uint32_t SF) noexcept + : StartPoint(SP), SourceFact(SF) {} + }; + + struct ValuePropagationJob { + uint32_t Inst{}; + uint32_t Fact{}; + [[no_unique_address]] std::conditional_t< + ComputeValues, typename std::decay_t::l_t, EmptyType> + Value{}; + }; + + struct SummaryEdge { + uint32_t TargetFact{}; + EdgeFunctionPtrType EF{}; + }; + + /// Key is TargetFact + + using SummaryEdges = SmallDenseTable1d; + using SummaryEdges_JF1 = + std::conditional_t, + llvm::SmallDenseSet>>; + + using summaries_t = detail::CellVecSmallVectorTy, + DummyPair>>; +}; + +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_ITERATIVEIDESOLVERBASE_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h new file mode 100644 index 000000000..28ba33542 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h @@ -0,0 +1,56 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVERRESULTS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVERRESULTS_H + +#include "phasar/DataFlow/IfdsIde/Solver/Compressor.h" +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Table.h" +#include "phasar/Utils/TableWrappers.h" + +#include "llvm/ADT/ArrayRef.h" + +#include + +namespace psr::detail { +template class IterativeIDESolverResults { + using inner_map_t = std::conditional_t< + std::is_same_v, + DummyDenseTable1d::id_type>, + DenseTable1d::id_type>>; + +public: + using n_t = N; + using d_t = D; + using l_t = L; + + typename NodeCompressorTraits::type NodeCompressor; + Compressor FactCompressor; + [[no_unique_address]] typename ValCompressorTraits::type ValCompressor; + llvm::SmallVector ValTab; + + using ValTab_value_type = typename decltype(ValTab)::value_type; + + static_assert(std::is_same_v); + + auto ValTab_cellVec() const { + std::vector::Cell> Ret; + Ret.reserve(ValTab.size()); + + for (const auto &[M1, Inst] : llvm::zip(ValTab, NodeCompressor)) { + for (ByConstRef M2 : M1.cells()) { + Ret.emplace_back(Inst, FactCompressor[M2.first], + ValCompressor[M2.second]); + } + } + return Ret; + } +}; + +template +using IterativeIDESolverResults_P = + IterativeIDESolverResults; +} // namespace psr::detail + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_ITERATIVEIDESOLVERRESULTS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.h new file mode 100644 index 000000000..4c705ea74 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.h @@ -0,0 +1,61 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVERSTATS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVERSTATS_H + +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.h" + +#include + +namespace psr { +struct IterativeIDESolverStats { + FlowEdgeFunctionCacheStats FEStats; + + size_t NumAllInterPropagations = 0; + size_t AllInterPropagationsBytes = 0; + size_t SourceFactAndCSToInterJobSize = 0; + size_t SourceFactAndCSToInterJobBytes = 0; + size_t SourceFactAndFuncToInterJobSize = 0; + size_t SourceFactAndFuncToInterJobBytes = 0; + size_t NumPathEdges = 0; + size_t NumPathEdgesHighWatermark = 0; + size_t JumpFunctionsMapBytes = 0; + size_t MaxESGEdgesPerInst = 0; + double AvgESGEdgesPerInst = 0; + size_t CumulESGEdges = 0; + size_t ValTabBytes = 0; + size_t WorkListHighWatermark = 0; + size_t CallWLHighWatermark = 0; + size_t WLPropHighWatermark = 0; + size_t WLCompHighWatermark = 0; + size_t NumFlowFacts = 0; + size_t InstCompressorCapacity = 0; + size_t FactCompressorCapacity = 0; + size_t FunCompressorCapacity = 0; + + size_t TotalNumRelevantCalls = 0; + size_t CumulNumInterJobsPerRelevantCall = 0; + size_t MaxNumInterJobsPerRelevantCall = 0; + size_t TotalNumLinearSearchForSummary = 0; + size_t CumulLinearSearchLenForSummary = 0; + size_t MaxLenLinearSearchForSummary = 0; + size_t CumulDiffNumSummariesFound = 0; + size_t MaxDiffNumSummariesFound = 0; + double CumulRelDiffNumSummariesFound = 0; + + size_t NumEndSummaries = 0; + size_t EndSummaryTabSize = 0; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const IterativeIDESolverStats &S); +}; + +template +struct SolverStatsSelector {}; + +template +struct SolverStatsSelector< + StaticSolverConfigTy, + std::enable_if_t> + : IterativeIDESolverStats {}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_ITERATIVEIDESOLVERSTATS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h new file mode 100644 index 000000000..1dc231652 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h @@ -0,0 +1,103 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_STATICIDESOLVERCONFIG_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_STATICIDESOLVERCONFIG_H + +#include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" +#include "phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h" +#include "phasar/Utils/TableWrappers.h" +#include "phasar/Utils/TypeTraits.h" + +#include +#include + +namespace psr { +enum class JumpFunctionGCMode { + /// Perform no semantic garbage collection on jump functions + Disabled, + /// Perform JF GC, but do not delete any elements from the SolverResults, so + /// all SolverResults are still present, only the edge-values are not computed + /// everywhere + Enabled, + /// Perform JF GC as in 'Enabled', but also delete entries from the + /// SolverResults + EnabledAggressively +}; + +struct IDESolverConfigBase { + template + static inline constexpr bool + IsSimple1d = sizeof(std::pair) <= 32 && + std::is_nothrow_move_constructible_v + &&std::is_nothrow_move_constructible_v + &&has_llvm_dense_map_info; + + template + static inline constexpr bool + IsSimpleVal = sizeof(T) <= 32 && std::is_nothrow_move_constructible_v + &&has_llvm_dense_map_info; + + template + using map_t = std::conditional_t, DenseTable1d, + UnorderedTable1d>; + + template + using set_t = + std::conditional_t, DenseSet, UnorderedSet>; + + template using worklist_t = VectorWorkList; + + template + using flow_edge_function_cache_t = + FlowEdgeFunctionCacheNG; + + template using EdgeFunctionPtrType = EdgeFunction; + + static inline constexpr bool AutoAddZero = true; + static inline constexpr bool EnableStatistics = false; + static inline constexpr JumpFunctionGCMode EnableJumpFunctionGC = + JumpFunctionGCMode::Disabled; + static inline constexpr bool UseEndSummaryTab = false; +}; + +template +struct WithComputeValues : Base { + static constexpr bool ComputeValues = ComputeValuesVal; +}; + +template struct WithGCMode : Base { + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = GCMode; +}; + +template struct WithStats : Base { + static constexpr bool EnableStatistics = EnableStats; +}; + +template typename WorkList> +struct WithWorkList : Base { + template using worklist_t = WorkList; +}; + +template struct WithEndSummaryTab : Base { + static inline constexpr bool UseEndSummaryTab = UseEST; +}; + +using IDESolverConfig = WithComputeValues; +using IFDSSolverConfig = WithComputeValues; +using IDESolverConfigWithStats = WithStats; +using IFDSSolverConfigWithStats = WithStats; +using IFDSSolverConfigWithStatsAndGC = + WithGCMode; + +template +struct DefaultIDESolverConfig : IDESolverConfig {}; + +template +struct DefaultIDESolverConfig< + ProblemTy, + std::enable_if_t, + ProblemTy>>> : IFDSSolverConfig {}; + +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_STATICIDESOLVERCONFIG_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h b/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h new file mode 100644 index 000000000..560c378dd --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h @@ -0,0 +1,122 @@ +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_WORKLISTTRAITS_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_WORKLISTTRAITS_H + +#include "phasar/DataFlow/IfdsIde/Solver/Compressor.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Compiler.h" + +#include +#include +#include +#include +namespace psr { + +template class VectorWorkList { +public: + VectorWorkList() noexcept = default; + template + VectorWorkList(const NodeCompressor &NC, const ICFGTy &ICF) noexcept {} + + void reserve(size_t Capacity) { WL.reserve(Capacity); } + + [[nodiscard]] bool empty() const noexcept { return WL.empty(); } + + template void emplace(ArgTys &&...Args) { + WL.emplace_back(std::forward(Args)...); + } + + template + void processEntriesUntilEmpty(HandlerFun Handler) { + while (!WL.empty()) { + auto Item = std::move(WL.back()); + WL.pop_back(); + std::invoke(Handler, std::move(Item)); + } + } + + void clear() noexcept { + WL.clear(); + WL.shrink_to_fit(); + } + + [[nodiscard]] LLVM_DUMP_METHOD size_t size() const noexcept { + return WL.size(); + } + +private: + std::vector WL; +}; + +template class SmallVectorWorkList { + SmallVectorWorkList() noexcept = default; + template + SmallVectorWorkList(const NodeCompressor &NC, const ICFGTy &ICF) noexcept {} + + void reserve(size_t Capacity) { WL.reserve(Capacity); } + + [[nodiscard]] bool empty() const noexcept { return WL.empty(); } + + template void emplace(ArgTys &&...Args) { + WL.emplace_back(std::forward(Args)...); + } + + template + void processEntriesUntilEmpty(HandlerFun Handler) { + while (!WL.empty()) { + auto Item = WL.pop_back_val(); + std::invoke(Handler, std::move(Item)); + } + } + + void clear() noexcept { + /// llvm::SmallVector does not have a shrink_to_fit() function, so use this + /// workaround: + llvm::SmallVector Empty; + swap(Empty, WL); + } + + [[nodiscard]] size_t size() const noexcept { return WL.size(); } + +private: + llvm::SmallVector WL; +}; + +template class DequeWorkList { +public: + DequeWorkList() noexcept = default; + + template + DequeWorkList(const NodeCompressor &NC, const ICFGTy &ICF) noexcept {} + + void reserve(size_t Capacity) { WL.reserve(Capacity); } + + [[nodiscard]] bool empty() const noexcept { return WL.empty(); } + + template void emplace(ArgTys &&...Args) { + WL.emplace_back(std::forward(Args)...); + } + + template + void processEntriesUntilEmpty(HandlerFun Handler) { + while (!WL.empty()) { + auto Item = std::move(WL.front()); + WL.pop_front(); + std::invoke(Handler, std::move(Item)); + } + } + + void clear() noexcept { + WL.clear(); + WL.shrink_to_fit(); + } + + [[nodiscard]] size_t size() const noexcept { return WL.size(); } + +private: + std::deque WL; +}; + +} // namespace psr + +#endif // PHASAR_DATAFLOW_IFDSIDE_SOLVER_WORKLISTTRAITS_H diff --git a/include/phasar/DataFlow/IfdsIde/SolverResults.h b/include/phasar/DataFlow/IfdsIde/SolverResults.h index 65da6d5e8..664d0e111 100644 --- a/include/phasar/DataFlow/IfdsIde/SolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/SolverResults.h @@ -65,6 +65,18 @@ class SolverResultsBase { return self().Results.row(Stmt); } + [[nodiscard]] const auto &rowMapView() const { + return self().Results.rowMapView(); + } + + [[nodiscard]] bool containsNode(ByConstRef Stmt) const { + return self().Results.containsRow(Stmt); + } + + [[nodiscard]] const auto &row(ByConstRef Stmt) const { + return self().Results.row(Stmt); + } + // this function only exists for IFDS problems which use BinaryDomain as their // value domain L template void dumpResults(const ICFGTy &ICF, llvm::raw_ostream &OS = llvm::outs()) const { @@ -180,6 +194,15 @@ class SolverResultsBase { STOP_TIMER("DFA IDE Result Dumping", Full); } + template + void foreachResultEntry(HandlerFn Handler) const { + for (const auto &[Row, RowMap] : rowMapView()) { + for (const auto &[Col, Val] : RowMap) { + std::invoke(Handler, std::make_tuple(Row, Col, Val)); + } + } + } + private: [[nodiscard]] const Derived &self() const noexcept { static_assert(std::is_base_of_v); diff --git a/include/phasar/Domain/LatticeDomain.h b/include/phasar/Domain/LatticeDomain.h index b7b7f1540..d45f7bc45 100644 --- a/include/phasar/Domain/LatticeDomain.h +++ b/include/phasar/Domain/LatticeDomain.h @@ -280,4 +280,19 @@ struct NonTopBotValue< } // namespace psr +namespace std { +template struct hash> { + size_t operator()(const psr::LatticeDomain &LD) noexcept { + if (LD.isBottom()) { + return SIZE_MAX; + } + if (LD.isTop()) { + return SIZE_MAX - 1; + } + assert(LD.getValueOrNull() != nullptr); + return std::hash{}(*LD.getValueOrNull()); + } +}; +} // namespace std + #endif diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.h index c9bbb5b99..c3ac4191b 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.h @@ -69,6 +69,7 @@ class LLVMBasedBackwardICFG : public LLVMBasedBackwardCFG, void printAsJsonImpl(llvm::raw_ostream &OS) const; [[nodiscard, deprecated]] nlohmann::json getAsJsonImpl() const; [[nodiscard]] const CallGraph &getCallGraphImpl() const noexcept; + [[nodiscard]] size_t getNumCallSitesImpl() const noexcept; llvm::LLVMContext BackwardRetsCtx; llvm::DenseMap BackwardRets; diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index 5af8300cd..333ce033f 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -157,6 +157,10 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { return CG; } + [[nodiscard]] size_t getNumCallSitesImpl() const noexcept { + return CG.getNumVertexCallSites(); + } + [[nodiscard]] llvm::Function *buildCRuntimeGlobalCtorsDtorsModel( llvm::Module &M, llvm::ArrayRef UserEntryPoints); diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h index e7dceb8eb..34012ec33 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h @@ -42,7 +42,6 @@ namespace psr { class LLVMBasedICFG; -template class SolverResults; struct IDEExtendedTaintAnalysisDomain : public LLVMAnalysisDomainDefault { using d_t = AbstractMemoryLocation; @@ -170,7 +169,7 @@ class IDEExtendedTaintAnalysis SourceConfigTy &&SourceConfig, SinkConfigTy &&SinkConfig); - void doPostProcessing(const SolverResults &SR); + void doPostProcessing(GenericSolverResults SR); public: /// Constructor. If EntryPoints is empty, use the TaintAPI functions as @@ -250,7 +249,7 @@ class IDEExtendedTaintAnalysis // Printing functions - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS = llvm::outs()) override; private: @@ -289,13 +288,13 @@ class IDEExtendedTaintAnalysis /// may not be sanitized. /// /// This function involves a post-processing step the first time it is called. - const LeakMap_t &getAllLeaks(const SolverResults &SR) &; + const LeakMap_t &getAllLeaks(GenericSolverResults SR) &; /// Return a map from llvm::Instruction to sets of leaks (llvm::Values) that /// may not be sanitized. /// /// This function involves a post-processing step the first time it is called. - LeakMap_t getAllLeaks(const SolverResults &SR) &&; + LeakMap_t getAllLeaks(GenericSolverResults SR) &&; /// Return a map from llvm::Instruction to sets of leaks (llvm::Values) that /// may or may not be sanitized. /// diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h index edde5b462..0b1de3088 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h @@ -108,10 +108,10 @@ class IDEGeneralizedLCA : public IDETabulationProblem { // void printIDEReport(llvm::raw_ostream &OS, // SolverResults &SR) override; - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS) override; - lca_results_t getLCAResults(SolverResults SR); + lca_results_t getLCAResults(GenericSolverResults SR); private: const LLVMBasedICFG *ICF{}; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h index b4410d1f9..4ab227cd1 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h @@ -29,6 +29,7 @@ #include "phasar/Utils/BitVectorSet.h" #include "phasar/Utils/ByRef.h" #include "phasar/Utils/Logger.h" +#include "phasar/Utils/Printer.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Twine.h" @@ -1099,7 +1100,7 @@ class IDEInstInteractionAnalysisT } } - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS = llvm::outs()) override { OS << "\n====================== IDE-Inst-Interaction-Analysis Report " "======================\n"; @@ -1135,7 +1136,7 @@ class IDEInstInteractionAnalysisT /// Computes all variables where a result set has been computed using the /// edge functions (and respective value domain). inline std::unordered_set - getAllVariables(const SolverResults & /* Solution */) const { + getAllVariables(GenericSolverResults /* Solution */) const { std::unordered_set Variables; // collect all variables that are available const llvm::Module *M = this->IRDB->getModule(); @@ -1160,7 +1161,7 @@ class IDEInstInteractionAnalysisT /// Computes all variables for which an empty set has been computed using the /// edge functions (and respective value domain). inline std::unordered_set getAllVariablesWithEmptySetValue( - const SolverResults &Solution) const { + GenericSolverResults Solution) const { return removeVariablesWithoutEmptySetValue(Solution, getAllVariables(Solution)); } @@ -1178,18 +1179,8 @@ class IDEInstInteractionAnalysisT } else { auto LSet = std::get>(EdgeFact); OS << "(set size: " << LSet.size() << ") values: "; - if constexpr (std::is_same_v) { - for (const auto &LElem : LSet) { - std::string IRBuffer; - llvm::raw_string_ostream RSO(IRBuffer); - LElem->print(RSO); - RSO.flush(); - OS << IRBuffer << ", "; - } - } else { - for (const auto &LElem : LSet) { - OS << LElem << ", "; - } + for (const auto &LElem : LSet) { + OS << LToString(LElem) << ", "; } } } @@ -1210,33 +1201,29 @@ class IDEInstInteractionAnalysisT /// Filters out all variables that had a non-empty set during edge functions /// computations. inline std::unordered_set removeVariablesWithoutEmptySetValue( - const SolverResults &Solution, + GenericSolverResults Solution, std::unordered_set Variables) const { // Check the solver results and remove all variables for which a // non-empty set has been computed - auto Results = Solution.getAllResultEntries(); - for (const auto &Result : Results) { + // auto Results = Solution.getAllResultEntries(); + Solution.foreachResultEntry([&Variables](const auto &Result) { // We do not care for the concrete instruction at which data-flow facts - // hold, instead we just wish to find out if a variable has been generated - // at some point. Therefore, we only care for the variables and their - // associated values and ignore at which point a variable may holds as a - // data-flow fact. - const auto &Variable = Result.getColumnKey(); - const auto &Value = Result.getValue(); + // hold, instead we just wish to find out if a variable has been + // generated at some point. Therefore, we only care for the variables + // and their associated values and ignore at which point a variable may + // holds as a data-flow fact. + const d_t &Variable = std::get<1>(Result); + const l_t &Value = std::get<2>(Result); // skip result entry if variable is not in the set of all variables - if (Variables.find(Variable) == Variables.end()) { - continue; + if (!Variables.count(Variable)) { + return; } - // skip result entry if the computed value is not of type BitVectorSet - if (!std::holds_alternative>(Value)) { - continue; - } - // remove variable from result set if a non-empty that has been computed - auto &Values = std::get>(Value); - if (!Values.empty()) { + if (const auto *Values = Value.getValueOrNull(); + Values && !Values->empty()) { Variables.erase(Variable); } - } + }); + return Variables; } @@ -1255,4 +1242,36 @@ using IDEInstInteractionAnalysis = IDEInstInteractionAnalysisT<>; } // namespace psr +// Compatibility with llvm::DenseMap/DenseSet: +namespace llvm { +template <> struct DenseMapInfo { + static psr::IDEIIAFlowFact getEmptyKey() { + return psr::IDEIIAFlowFact( + DenseMapInfo::getEmptyKey()); + } + static psr::IDEIIAFlowFact getTombstoneKey() { + return psr::IDEIIAFlowFact( + DenseMapInfo::getTombstoneKey()); + } + static bool isEqual(const psr::IDEIIAFlowFact &L, + const psr::IDEIIAFlowFact &R) { + const auto *Empty = DenseMapInfo::getEmptyKey(); + const auto *TS = DenseMapInfo::getTombstoneKey(); + if (L.getBase() == Empty) { + return R.getBase() == Empty; + } + if (L.getBase() == TS) { + return R.getBase() == TS; + } + if (R.getBase() == Empty || R.getBase() == TS) { + return false; + } + return L == R; + } + static unsigned getHashValue(const psr::IDEIIAFlowFact &FF) { + return std::hash{}(FF); + } +}; +} // namespace llvm + #endif diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h index 2e7c6d873..725c60fcc 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h @@ -119,9 +119,10 @@ class IDELinearConstantAnalysis // Helper functions - [[nodiscard]] lca_results_t getLCAResults(SolverResults SR); + [[nodiscard]] lca_results_t + getLCAResults(GenericSolverResults SR); - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS = llvm::outs()) override; private: diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.h index 81036cdbe..1780d2d79 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.h @@ -105,7 +105,7 @@ class IDESecureHeapPropagation n_t RetSite, d_t RetSiteNode) override; - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS) override; }; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h index be868e560..53c0224f7 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h @@ -517,7 +517,7 @@ class IDETypeStateAnalysis return Name.contains(TSD->getTypeNameOfInterest()); } - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS = llvm::outs()) override { LLVMBasedCFG CFG; for (const auto &F : this->IRDB->getAllFunctions()) { @@ -555,6 +555,19 @@ class IDETypeStateAnalysis this->Printer->onFinalize(); } + [[nodiscard]] bool + isInteresting(const llvm::Instruction *Inst) const noexcept { + const auto *Call = llvm::dyn_cast(Inst); + if (!Call) { + return false; + } + if (const auto *StaticCallee = Call->getCalledFunction()) { + return TSD->isAPIFunction(StaticCallee->getName().str()); + } + + return true; + } + private: const TypeStateDescriptionTy *TSD{}; }; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.h index 590513a44..8d6774154 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.h @@ -144,7 +144,7 @@ class IFDSConstAnalysis [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept override; - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS = llvm::outs()) override; /** diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h index f4fc2ff45..9564e1b8b 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h @@ -80,9 +80,12 @@ class IFDSTaintAnalysis bool isZeroValue(d_t FlowFact) const noexcept override; - void emitTextReport(const SolverResults &SR, + void emitTextReport(GenericSolverResults SR, llvm::raw_ostream &OS = llvm::outs()) override; + [[nodiscard]] bool + isInteresting(const llvm::Instruction *Inst) const noexcept; + private: const LLVMTaintConfig *Config{}; LLVMAliasInfoRef PT{}; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.h index c6a92f457..5b91aae39 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.h @@ -62,7 +62,7 @@ class IFDSUninitializedVariables [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept override; - void emitTextReport(const SolverResults &Results, + void emitTextReport(GenericSolverResults Results, llvm::raw_ostream &OS = llvm::outs()) override; [[nodiscard]] const std::map> &getAllUndefUses() const; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/TypeStateDescriptions/CSTDFILEIOTypeStateDescription.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/TypeStateDescriptions/CSTDFILEIOTypeStateDescription.h index c7cad7975..9c4da46d7 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/TypeStateDescriptions/CSTDFILEIOTypeStateDescription.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/TypeStateDescriptions/CSTDFILEIOTypeStateDescription.h @@ -13,6 +13,8 @@ #include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/TypeStateDescriptions/TypeStateDescription.h" +#include "llvm/Support/raw_ostream.h" + #include #include #include @@ -28,6 +30,12 @@ enum class CSTDFILEIOState { BOT = 4 }; llvm::StringRef to_string(CSTDFILEIOState State) noexcept; + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + CSTDFILEIOState State) { + return OS << to_string(State); +} + template <> struct JoinLatticeTraits { static constexpr CSTDFILEIOState top() noexcept { return CSTDFILEIOState::TOP; diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMAliasSet.h b/include/phasar/PhasarLLVM/Pointer/LLVMAliasSet.h index 55c150df7..92644c828 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMAliasSet.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMAliasSet.h @@ -30,6 +30,8 @@ namespace llvm { class Value; class Instruction; class GlobalVariable; +class GlobalObject; +class DataLayout; class Function; } // namespace llvm diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.h b/include/phasar/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.h index c37cfda21..1195da6c8 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.h @@ -18,6 +18,7 @@ namespace llvm { class Value; class Function; class Instruction; +class AAResults; } // namespace llvm namespace psr { @@ -39,8 +40,6 @@ class LLVMBasedAliasAnalysis { LLVMBasedAliasAnalysis &operator=(const LLVMBasedAliasAnalysis &) = delete; ~LLVMBasedAliasAnalysis(); - void print(llvm::raw_ostream &OS = llvm::outs()) const; - [[nodiscard]] inline llvm::AAResults *getAAResults(llvm::Function *F) { if (!hasAliasInfo(*F)) { computeAliasInfo(*F); diff --git a/include/phasar/PhasarLLVM/Utils/BasicBlockOrdering.h b/include/phasar/PhasarLLVM/Utils/BasicBlockOrdering.h index 4ec3570b5..96e2012d0 100644 --- a/include/phasar/PhasarLLVM/Utils/BasicBlockOrdering.h +++ b/include/phasar/PhasarLLVM/Utils/BasicBlockOrdering.h @@ -21,7 +21,6 @@ namespace llvm { class Function; class BasicBlock; class Instruction; -class DominatorTree; } // namespace llvm namespace psr { diff --git a/include/phasar/Utils/BitVectorSet.h b/include/phasar/Utils/BitVectorSet.h index 308245c82..1bb5cf58f 100644 --- a/include/phasar/Utils/BitVectorSet.h +++ b/include/phasar/Utils/BitVectorSet.h @@ -368,4 +368,12 @@ template class BitVectorSet { } // namespace psr +namespace std { +template struct hash> { + size_t operator()(const psr::BitVectorSet &BVS) noexcept { + return hash_value(BVS); + } +}; +} // namespace std + #endif diff --git a/include/phasar/Utils/EmptyBaseOptimizationUtils.h b/include/phasar/Utils/EmptyBaseOptimizationUtils.h new file mode 100644 index 000000000..a592780bf --- /dev/null +++ b/include/phasar/Utils/EmptyBaseOptimizationUtils.h @@ -0,0 +1,82 @@ +#ifndef PHASAR_UTILS_EMPTYBASEOPTIMIZATIONUTILS_H +#define PHASAR_UTILS_EMPTYBASEOPTIMIZATIONUTILS_H + +#include "phasar/Utils/ByRef.h" + +#include "llvm/ADT/DenseMapInfo.h" + +#include +#include + +namespace psr { +/// A dummy type that takes no space in memory when used with the empty-base +/// optimization or with [[no_unique_address]] +struct EmptyType { + constexpr friend bool operator==(EmptyType /*LHS*/, + EmptyType /*RHS*/) noexcept { + return true; + } + constexpr friend bool operator!=(EmptyType /*LHS*/, + EmptyType /*RHS*/) noexcept { + return false; + } +}; + +/// A wrapper over a single object that pretends to be a std::pair +template struct DummyPair { + T first; // NOLINT -- Need to have the same interface as std::pair + [[no_unique_address]] EmptyType second; // NOLINT -- '' + + [[nodiscard]] auto getHashCode() const noexcept { + return std::hash{}(first); + } + template + friend std::enable_if_t, bool> + operator==(DummyPair LHS, + DummyPair RHS) noexcept(noexcept(LHS.first == RHS.first)) { + return LHS.first == RHS.first; + } + + template + friend std::enable_if_t, bool> + operator==(const DummyPair &LHS, + const DummyPair &RHS) noexcept(noexcept(LHS.first == RHS.first)) { + return LHS.first == RHS.first; + } + + template + friend std::enable_if_t, bool> + operator!=(DummyPair LHS, DummyPair RHS) noexcept(noexcept(LHS == RHS)) { + return !(LHS == RHS); + } + + template + friend std::enable_if_t, bool> + operator!=(const DummyPair &LHS, + const DummyPair &RHS) noexcept(noexcept(LHS == RHS)) { + return !(LHS == RHS); + } +}; +} // namespace psr + +namespace llvm { +template struct DenseMapInfo> { + using value_type = psr::DummyPair; + + static value_type getEmptyKey() noexcept { + return {DenseMapInfo::getEmptyKey(), {}}; + } + static value_type getTombstoneKey() noexcept { + return {DenseMapInfo::getTombstoneKey(), {}}; + } + static auto getHashValue(psr::ByConstRef DP) noexcept { + return DP.getHashCode(); + } + static bool isEqual(psr::ByConstRef LHS, + psr::ByConstRef RHS) noexcept { + return DenseMapInfo::isEqual(LHS.first, RHS.first); + } +}; +} // namespace llvm + +#endif // PHASAR_UTILS_EMPTYBASEOPTIMIZATIONUTILS_H_ diff --git a/include/phasar/Utils/EquivalenceClassMap.h b/include/phasar/Utils/EquivalenceClassMap.h index c93508fe3..a11d3fe4e 100644 --- a/include/phasar/Utils/EquivalenceClassMap.h +++ b/include/phasar/Utils/EquivalenceClassMap.h @@ -10,13 +10,16 @@ #ifndef PHASAR_UTILS_EQUIVALENCECLASSMAP_H #define PHASAR_UTILS_EQUIVALENCECLASSMAP_H +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator_range.h" +#include #include +#include #include #include -#include namespace psr { @@ -28,7 +31,9 @@ namespace psr { template struct EquivalenceClassMap { template using SetType = std::set; using EquivalenceClassBucketT = std::pair, ValueT>; - using StorageT = std::vector; + // Use SmallVector here, since it has a smaller struct-size than std::vector; + // we may store a lot of them in the FlowEdgeFunctionCache + using StorageT = llvm::SmallVector; public: using size_type = size_t; @@ -169,6 +174,126 @@ template struct EquivalenceClassMap { StorageT StoredData{}; }; +template > +class EquivalenceClassMapNG { + using SetTy = llvm::SmallDenseSet; + +public: + // NOLINTNEXTLINE(readability-identifier-naming) + class const_iterator { + + public: + using value_type = std::pair; + using reference = std::pair; + using pointer = reference *; + using difference_type = ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + const_iterator &operator++() noexcept { + ++Val; + ++Ky; + return *this; + } + + reference operator*() noexcept { return reference(*Ky, *Val); } + + pointer operator->() noexcept { + TempStorage.emplace(*Ky, *Val); + return &*TempStorage; + } + + bool operator==(const const_iterator &Other) const noexcept { + return Val == Other.Val; + } + + bool operator!=(const const_iterator &Other) const noexcept { + return !(*this == Other); + } + + private: + friend EquivalenceClassMapNG; + + const_iterator(const TValue *Val, const SetTy *Ky) noexcept + : Val(Val), Ky(Ky) {} + + const TValue *Val; + const SetTy *Ky; + + std::optional TempStorage; + }; + + EquivalenceClassMapNG() noexcept = default; + + void reserve(size_t InitialCapacity) { + Values.reserve(InitialCapacity); + Keys.reserve(InitialCapacity); + } + + template + std::pair insert(KK &&Key, VV &&Value) { + ValueComparator VComp; + for (size_t I = 0, End = Values.size(); I != End; ++I) { + if (VComp(Values[I], Value)) { + return {getIterator(I), Keys[I].insert(std::forward(Key)).second}; + } + } + + Values.emplace_back(std::forward(Value)); + Keys.emplace_back().insert(std::forward(Key)); + return {getIterator(Values.size() - 1), true}; + } + + template + const TValue &getOrInsertLazy(KK &&Key, VCtor &&MakeV) { + for (size_t I = 0, End = Keys.size(); I != End; ++I) { + if (Keys[I].count(Key)) { + return Values[I]; + } + } + return (*insert(std::forward(Key), std::invoke(MakeV)).first).second; + } + + const_iterator begin() const noexcept { + return {Values.begin(), Keys.begin()}; + } + const_iterator end() const noexcept { return {Values.end(), Keys.end()}; } + + const_iterator find(const TKey &Ky) const { + for (size_t I = 0, End = Keys.size(); I < End; ++I) { + if (Keys[I].count(Ky)) { + return getIterator(I); + } + } + + return end(); + } + + [[nodiscard]] inline size_t numEquivalenceClasses() const noexcept { + return Values.size(); + } + + // Returns the size of the map, i.e., the number of equivalence classes. + [[nodiscard]] inline size_t size() const noexcept { + return numEquivalenceClasses(); + } + + [[nodiscard]] bool empty() const noexcept { return Values.empty(); } + + void clear() noexcept { + Values.clear(); + Keys.clear(); + } + +private: + const_iterator getIterator(size_t I) const noexcept { + return {std::next(Values.begin(), I), std::next(Keys.begin(), I)}; + } + + llvm::SmallVector Values; + llvm::SmallVector Keys; +}; + } // namespace psr #endif diff --git a/include/phasar/Utils/PointerUtils.h b/include/phasar/Utils/PointerUtils.h new file mode 100644 index 000000000..117a5bea9 --- /dev/null +++ b/include/phasar/Utils/PointerUtils.h @@ -0,0 +1,29 @@ +#ifndef PHASAR_UTILS_POINTERUTILS_H +#define PHASAR_UTILS_POINTERUTILS_H + +#include "llvm/ADT/IntrusiveRefCntPtr.h" + +#include + +namespace psr { + +/// A simple helper function to get a raw pointer from an arbitrary pointer type +/// in generic code. This overload set is extendable. + +template T *getPointerFrom(T *Ptr) noexcept { return Ptr; } +template +constexpr T *getPointerFrom(const std::unique_ptr &Ptr) noexcept { + return Ptr.get(); +} +template +constexpr T *getPointerFrom(const std::shared_ptr &Ptr) noexcept { + return Ptr.get(); +} +template +constexpr T *getPointerFrom(const llvm::IntrusiveRefCntPtr &Ptr) noexcept { + return Ptr.get(); +} + +} // namespace psr + +#endif // PHASAR_UTILS_POINTERUTILS_H diff --git a/include/phasar/Utils/StableVector.h b/include/phasar/Utils/StableVector.h index 12c18853e..8406bea98 100644 --- a/include/phasar/Utils/StableVector.h +++ b/include/phasar/Utils/StableVector.h @@ -320,12 +320,20 @@ class StableVector { /// In theory, we could allocate as many blocks as necessary, such that the /// accumulated size of them is exactly SIZE_MAX+1, but this will already /// overflow the Size field and we still need to store the blocks and the - /// other metadata somewhere, such that we will never be able to allocate - /// the last block (with size SIZE_MAX/2). So, the maximum number of + /// other metadata somewhere, so we will never be able to allocate + /// the last block (with size SIZE_MAX/2). Hence, the maximum number of /// elements will be SIZE_MAX/2; return (SIZE_MAX / 2) / sizeof(T); }; + [[nodiscard]] size_t capacity() const noexcept { + return llvm::NextPowerOf2(std::max(InitialCapacity, size())); + } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return capacity() * sizeof(T) + Blocks.capacity_in_bytes(); + } + [[nodiscard]] T &front() noexcept { assert(!empty() && "Do not call front() on an empty StableVector!"); return *Blocks[0]; diff --git a/include/phasar/Utils/Table.h b/include/phasar/Utils/Table.h index 24ac50ac0..0a8a3b33a 100644 --- a/include/phasar/Utils/Table.h +++ b/include/phasar/Utils/Table.h @@ -22,6 +22,7 @@ #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -82,6 +83,24 @@ template class Table { [[nodiscard]] size_t size() const noexcept { return Tab.size(); } + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + size_t Sz = + Tab.bucket_count() * sizeof(void *) + + Tab.size() * + sizeof( + std::tuple); + + for (const auto &[RowKey, Row] : Tab) { + Sz += + Row.bucket_count() * sizeof(void *) + + Row.size() * + sizeof( + std::tuple::value_type>); + } + return Sz; + } + [[nodiscard]] std::set cellSet() const { // Returns a set of all row key / column key / value triplets. std::set Result; @@ -162,6 +181,34 @@ template class Table { return Tab[std::move(RowKey)][std::move(ColumnKey)]; } + [[nodiscard]] V getOrDefault(ByConstRef RowKey, + ByConstRef ColumnKey) const { + auto OuterIt = Tab.find(RowKey); + if (OuterIt == Tab.end()) { + return V(); + } + auto InnerIt = OuterIt->second.find(ColumnKey); + if (InnerIt == OuterIt->second.end()) { + return V(); + } + + return InnerIt->second; + } + + [[nodiscard]] std::optional tryGet(ByConstRef RowKey, + ByConstRef ColumnKey) { + auto OuterIt = Tab.find(RowKey); + if (OuterIt == Tab.end()) { + return std::nullopt; + } + auto InnerIt = OuterIt->second.find(ColumnKey); + if (InnerIt == OuterIt->second.end()) { + return std::nullopt; + } + + return InnerIt->second; + } + [[nodiscard]] ByConstRef get(ByConstRef RowKey, ByConstRef ColumnKey) const noexcept { // Returns the value corresponding to the given row and column keys, or V() @@ -220,12 +267,26 @@ template class Table { } [[nodiscard]] const std::unordered_map> & - rowMap() const noexcept { + rowMap() const &noexcept { + // Returns a view that associates each row key with the corresponding map + // from column keys to values. + return Tab; + } + [[nodiscard]] std::unordered_map> && + rowMap() &&noexcept { + // Returns a view that associates each row key with the corresponding map + // from column keys to values. + return std::move(Tab); + } + [[nodiscard]] const std::unordered_map> & + rowMapView() const noexcept { // Returns a view that associates each row key with the corresponding map // from column keys to values. return Tab; } + void reserve(size_t Capacity) { Tab.reserve(Capacity); } + bool operator==(const Table &Other) noexcept { return Tab == Other.Tab; } diff --git a/include/phasar/Utils/TableWrappers.h b/include/phasar/Utils/TableWrappers.h new file mode 100644 index 000000000..18accb1b1 --- /dev/null +++ b/include/phasar/Utils/TableWrappers.h @@ -0,0 +1,1037 @@ +#ifndef PHASAR_UTILS_TABLEWRAPPERS_H +#define PHASAR_UTILS_TABLEWRAPPERS_H + +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/MemoryResource.h" +#include "phasar/Utils/TypeTraits.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/iterator_range.h" + +#include +#include +#include +#include +#include +#include + +namespace psr { + +template +struct has_getHashCode : std::false_type {}; +template +struct has_getHashCode().getHashCode())> + : std::true_type {}; + +template +struct has_std_hash : std::false_type {}; +template +struct has_std_hash{}(std::declval))> + : std::true_type {}; + +namespace detail { +template struct DummyTransform { + ByConstRef Value; + + // 'const' needed for decltype within llvm::mapped_iterator + // NOLINTNEXTLINE(readability-const-return-type) + const std::pair operator()(ByConstRef Key) const { + return {Key, Value}; + } +}; + +template using CellVecSmallVectorTy = llvm::SmallVector; + +template struct Hasher { + size_t operator()(ByConstRef Key) const noexcept { + if constexpr (has_getHashCode::value) { + return Key.getHashCode(); + } else { + return std::hash{}(Key); + } + } +}; + +} // namespace detail + +template class UnorderedTable1d { + using Hasher = detail::Hasher; + +public: + using value_type = std::pair; + using iterator = typename std::unordered_map::iterator; + using const_iterator = + typename std::unordered_map::const_iterator; + + UnorderedTable1d() noexcept = default; + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count(Key); + } + + V &getOrCreate(K Key) { return Map[std::move(Key)]; } + + auto insert(K Key, V Value) { + return Map.try_emplace(std::move(Key), std::move(Value)); + } + + template >> + V getOr(ByConstRef Key, V Or) const { + if (auto It = Map.find(Key); It != Map.end()) { + return It->second; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, const V &Or) const { + if (auto It = Map.find(Key); It != Map.end()) { + return It->second; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, V &&Or) const = delete; + + const_iterator find(ByConstRef Key) const { return Map.find(Key); } + + void erase(ByConstRef Key) { Map.erase(Key); } + + void erase(const_iterator It) { Map.erase(It); } + + auto cells() noexcept { return llvm::make_range(Map.begin(), Map.end()); } + auto cells() const noexcept { + return llvm::make_range(Map.begin(), Map.end()); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + Ret.insert(Ret.end(), Map.begin(), Map.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + std::pair, V>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + std::pair, V>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem.first) == Of) { + Ret.emplace_back(std::invoke(ResProj, Elem.first), Elem.second); + } + } + return Ret; + } + + void clear() { + std::unordered_map Empty{}; + swap(Empty, Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.bucket_count() * sizeof(void *) + + Map.size() * sizeof(std::tuple); + } + +private: + std::unordered_map Map; +}; + +template class UnorderedTable1d { + using Hasher = detail::Hasher; + +public: + using value_type = DummyPair; + using iterator = typename std::unordered_set>::iterator; + using const_iterator = + typename std::unordered_set>::const_iterator; + + UnorderedTable1d() noexcept = default; + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count({Key, {}}); + } + + EmptyType getOrCreate(K Key) { + Map.insert({std::move(Key), {}}); + return {}; + } + + auto insert(K Key) { return Map.insert({std::move(Key), {}}); } + auto insert(K Key, EmptyType /*Value*/) { return insert(std::move(Key)); } + + const_iterator find(ByConstRef Key) const { return Map.find({Key, {}}); } + + void erase(ByConstRef Key) { Map.erase({Key, {}}); } + + void erase(const_iterator It) { Map.erase(It); } + + auto cells() noexcept { return llvm::make_range(Map.begin(), Map.end()); } + auto cells() const noexcept { + return llvm::make_range(Map.begin(), Map.end()); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + Ret.insert(Ret.end(), Map.begin(), Map.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + DummyPair>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + DummyPair>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem.first) == Of) { + Ret.push_back({std::invoke(ResProj, Elem.first), {}}); + } + } + return Ret; + } + + void clear() { + std::unordered_set, Hasher> Empty{}; + swap(Empty, Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.bucket_count() * sizeof(void *) + + Map.size() * sizeof(std::tuple); + } + +private: + std::unordered_set, Hasher> Map; +}; + +template class DummyUnorderedTable1d { + struct Hasher { + size_t operator()(ByConstRef Key) const noexcept { + if constexpr (has_getHashCode::value) { + return Key.getHashCode(); + } else { + return std::hash{}(Key); + } + } + }; + +public: + using value_type = std::pair; + using iterator = llvm::mapped_iterator< + typename std::unordered_set::const_iterator, + detail::DummyTransform>; + using const_iterator = iterator; + + explicit DummyUnorderedTable1d(V Value) noexcept( + std::is_nothrow_move_constructible_v) + : Value(std::move(Value)) {} + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count(Key); + } + + const V &getOrCreate(K Key) { + Map.insert(std::move(Key)); + return Value; + } + + template >> + V getOr(ByConstRef Key, V Or) const { + if (Map.count(Key)) { + return Value; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, const V &Or) const { + if (Map.count(Key)) { + return Value; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, V &&Or) const = delete; + + const_iterator find(ByConstRef Key) const { + return llvm::map_iterator(Map.find(Key), + detail::DummyTransform{Value}); + } + + void erase(ByConstRef Key) { Map.erase(Key); } + + void erase(iterator It) { Map.erase(It.getCurrent()); } + + auto cells() const noexcept { + return llvm::map_range(llvm::make_range(Map.begin(), Map.end()), + detail::DummyTransform{Value}); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + auto Cells = cells(); + Ret.insert(Ret.end(), Cells.begin(), Cells.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.emplace_back(Elem, Value); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + std::pair, V>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + std::pair, V>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem) == Of) { + Ret.emplace_back(std::invoke(ResProj, Elem), Value); + } + } + return Ret; + } + + void clear() { + std::unordered_set Empty{}; + swap(Empty, Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.bucket_count() * sizeof(void *) + + Map.size() * sizeof(std::tuple); + } + +private: + std::unordered_set Map; + V Value{}; +}; + +template class UnorderedSet { +public: + using value_type = T; + UnorderedSet() noexcept = default; + + void reserve(size_t Capacity) { Set.reserve(Capacity); } + + auto insert(T Val) { return Set.insert(std::move(Val)); } + + bool contains(ByConstRef Val) const noexcept { return Set.count(Val); } + + bool erase(ByConstRef Val) { return Set.erase(Val); } + + auto begin() const noexcept { return Set.begin(); } + auto end() const noexcept { return Set.end(); } + + void + clear() noexcept(std::is_nothrow_default_constructible_v) { + std::unordered_set Empty{}; + swap(Set, Empty); + } + + auto cells() const noexcept { return llvm::make_range(begin(), end()); } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Set.size()); + Ret.insert(Ret.end(), Set.begin(), Set.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Set) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy> Ret; + for (const auto &Elem : Set) { + if (std::invoke(Proj, Elem) == Of) { + Ret.push_back(std::invoke(ResProj, Elem)); + } + } + return Ret; + } + + [[nodiscard]] size_t size() const noexcept { return Set.size(); } + [[nodiscard]] bool empty() const noexcept { return Set.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Set.bucket_count() * sizeof(void *) + + Set.size() * sizeof(std::tuple); + } + +private: + struct Hasher { + size_t operator()(ByConstRef Key) const noexcept { + if constexpr (has_getHashCode::value) { + return Key.getHashCode(); + } else { + return std::hash{}(Key); + } + } + }; + + std::unordered_set Set; +}; + +template class DenseTable1d { + +public: + using value_type = std::pair; + using iterator = typename llvm::DenseMap::iterator; + using const_iterator = typename llvm::DenseMap::const_iterator; + + DenseTable1d() noexcept = default; + /// Dummy ctor for compatibility with UnorderedTable1d + template + explicit DenseTable1d(Allocator /*Alloc*/) noexcept {} + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count(Key); + } + + V &getOrCreate(K Key) { return Map[std::move(Key)]; } + + auto insert(K Key, V Value) { + return Map.try_emplace(std::move(Key), std::move(Value)); + } + + template >> + V getOr(ByConstRef Key, V Or) const { + if (auto It = Map.find(Key); It != Map.end()) { + return It->second; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, const V &Or) const { + if (auto It = Map.find(Key); It != Map.end()) { + return It->second; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, V &&Or) const = delete; + + const_iterator find(ByConstRef Key) const { return Map.find(Key); } + + void erase(ByConstRef Key) { Map.erase(Key); } + void erase(iterator It) { Map.erase(It); } + + auto cells() noexcept { return llvm::make_range(Map.begin(), Map.end()); } + auto cells() const noexcept { + return llvm::make_range(Map.begin(), Map.end()); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + Ret.insert(Ret.end(), Map.begin(), Map.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + std::pair, V>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + std::pair, V>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem.first) == Of) { + Ret.emplace_back(std::invoke(ResProj, Elem.first), Elem.second); + } + } + return Ret; + } + + void clear() noexcept { + llvm::DenseMap Empty; + Empty.swap(Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.getMemorySize(); + } + +private: + llvm::DenseMap Map; +}; + +template class DenseTable1d { + +public: + using value_type = DummyPair; + using iterator = typename llvm::DenseSet>::iterator; + using const_iterator = typename llvm::DenseSet>::const_iterator; + + DenseTable1d() noexcept = default; + /// Dummy ctor for compatibility with UnorderedTable1d + template + explicit DenseTable1d(Allocator /*Alloc*/) noexcept {} + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count({Key, {}}); + } + + EmptyType getOrCreate(K Key) { + Map.insert({std::move(Key), {}}); + return {}; + } + + auto insert(K Key) { return Map.insert({std::move(Key), {}}); } + auto insert(K Key, EmptyType /*Value*/) { return insert(std::move(Key)); } + + const_iterator find(ByConstRef Key) const { return Map.find({Key, {}}); } + + void erase(ByConstRef Key) { Map.erase({Key, {}}); } + void erase(iterator It) { Map.erase(It); } + + auto cells() noexcept { return llvm::make_range(Map.begin(), Map.end()); } + auto cells() const noexcept { + return llvm::make_range(Map.begin(), Map.end()); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + Ret.insert(Ret.end(), Map.begin(), Map.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + DummyPair>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + DummyPair>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem.first) == Of) { + Ret.push_back({std::invoke(ResProj, Elem.first), {}}); + } + } + return Ret; + } + + void clear() noexcept { + llvm::DenseSet> Empty; + Empty.swap(Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.getMemorySize(); + } + +private: + llvm::DenseSet> Map; +}; + +template class SmallDenseTable1d { + +public: + using container_type = llvm::SmallDenseMap; + using value_type = std::pair; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + + SmallDenseTable1d() noexcept = default; + /// Dummy ctor for compatibility with UnorderedTable1d + template + explicit SmallDenseTable1d(Allocator /*Alloc*/) noexcept {} + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count(Key); + } + + V &getOrCreate(K Key) { return Map[std::move(Key)]; } + + auto insert(K Key, V Value) { + return Map.try_emplace(std::move(Key), std::move(Value)); + } + + template >> + V getOr(ByConstRef Key, V Or) const { + if (auto It = Map.find(Key); It != Map.end()) { + return It->second; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, const V &Or) const { + if (auto It = Map.find(Key); It != Map.end()) { + return It->second; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, V &&Or) const = delete; + + const_iterator find(ByConstRef Key) const { return Map.find(Key); } + + void erase(ByConstRef Key) { Map.erase(Key); } + void erase(iterator It) { Map.erase(It); } + + auto cells() noexcept { return llvm::make_range(Map.begin(), Map.end()); } + auto cells() const noexcept { + return llvm::make_range(Map.begin(), Map.end()); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + Ret.insert(Ret.end(), Map.begin(), Map.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + std::pair, V>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + std::pair, V>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem.first) == Of) { + Ret.emplace_back(std::invoke(ResProj, Elem.first), Elem.second); + } + } + return Ret; + } + + void clear() noexcept { + container_type Empty; + Empty.swap(Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.getMemorySize(); + } + +private: + container_type Map; +}; + +template class SmallDenseTable1d { + +public: + using container_type = llvm::SmallDenseSet>; + using value_type = DummyPair; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + + SmallDenseTable1d() noexcept = default; + /// Dummy ctor for compatibility with UnorderedTable1d + template + explicit SmallDenseTable1d(Allocator /*Alloc*/) noexcept {} + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.count({Key, {}}); + } + + EmptyType getOrCreate(K Key) { + Map.insert({std::move(Key), {}}); + return {}; + } + + auto insert(K Key) { return Map.insert({std::move(Key), {}}); } + auto insert(K Key, EmptyType /*Value*/) { return insert(std::move(Key)); } + + const_iterator find(ByConstRef Key) const { return Map.find({Key, {}}); } + + void erase(ByConstRef Key) { Map.erase({Key, {}}); } + void erase(iterator It) { Map.erase(It); } + + auto cells() noexcept { return llvm::make_range(Map.begin(), Map.end()); } + auto cells() const noexcept { + return llvm::make_range(Map.begin(), Map.end()); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + Ret.insert(Ret.end(), Map.begin(), Map.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + DummyPair>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + DummyPair>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem.first) == Of) { + Ret.push_back({std::invoke(ResProj, Elem.first), {}}); + } + } + return Ret; + } + + void clear() noexcept { + container_type Empty; + Empty.swap(Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.getMemorySize(); + } + +private: + container_type Map; +}; + +/// A set that appears as map mapping to a constant +template class DummyDenseTable1d { + +public: + using value_type = typename std::pair; + using iterator = + typename llvm::mapped_iterator::const_iterator, + detail::DummyTransform>; + using const_iterator = iterator; + + DummyDenseTable1d() noexcept = default; + /// Dummy ctor for compatibility with UnorderedTable1d + template + explicit DummyDenseTable1d(Allocator /*Alloc*/) noexcept {} + template + explicit DummyDenseTable1d(Allocator /*Alloc*/, V Value) noexcept + : Value(std::move(Value)) {} + + void reserve(size_t Capacity) { Map.reserve(Capacity); } + + [[nodiscard]] bool contains(ByConstRef Key) const noexcept { + return Map.contains(Key); + } + + const V &getOrCreate(K Key) { + Map.insert(std::move(Key)); + return Value; + } + + template >> + V getOr(ByConstRef Key, V Or) const { + if (Map.contains(Key)) { + return Value; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, const V &Or) const { + if (Map.contains(Key)) { + return Value; + } + return Or; + } + + template >> + const V &getOr(ByConstRef Key, V &&Or) const = delete; + + const_iterator find(ByConstRef Key) const { + return llvm::map_iterator(Map.find(Key), + detail::DummyTransform{Value}); + } + + void erase(ByConstRef Key) { Map.erase(Key); } + void erase(iterator It) { Map.erase(It.getCurrent()); } + + auto cells() const noexcept { + return llvm::map_range(llvm::make_range(Map.begin(), Map.end()), + detail::DummyTransform{Value}); + } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Map.size()); + auto Cells = cells(); + Ret.insert(Ret.end(), Cells.begin(), Cells.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Map) { + if (std::invoke(Filter, Elem)) { + Ret.emplace_back(Elem, Value); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy< + std::pair, V>> + allOf(Projection Proj, ByConstRef> Of, + ResultProjection ResProj = {}) const { + detail::CellVecSmallVectorTy< + std::pair, V>> + Ret; + for (const auto &Elem : Map) { + if (std::invoke(Proj, Elem) == Of) { + Ret.emplace_back(std::invoke(ResProj, Elem), Value); + } + } + return Ret; + } + + void clear() noexcept { + llvm::DenseSet Empty; + Empty.swap(Map); + } + + [[nodiscard]] size_t size() const noexcept { return Map.size(); } + [[nodiscard]] bool empty() const noexcept { return Map.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Map.getMemorySize(); + } + + [[nodiscard]] iterator begin() const noexcept { + return llvm::map_iterator(Map.begin(), detail::DummyTransform{Value}); + } + [[nodiscard]] iterator end() const noexcept { + return llvm::map_iterator(Map.end(), detail::DummyTransform{Value}); + } + +private: + llvm::DenseSet Map; + V Value{}; +}; + +template > class DenseSet { +public: + using value_type = T; + DenseSet() noexcept = default; + template DenseSet(Allocator /*Alloc*/) noexcept {} + + void reserve(size_t Capacity) { Set.reserve(Capacity); } + + auto insert(T Val) { return Set.insert(std::move(Val)); } + + bool contains(ByConstRef Val) const noexcept { return Set.count(Val); } + + bool erase(ByConstRef Val) { return Set.erase(Val); } + + auto begin() const noexcept { return Set.begin(); } + auto end() const noexcept { return Set.end(); } + + void clear() noexcept { + llvm::DenseSet Empty; + Empty.swap(Set); + } + + auto cells() const noexcept { return llvm::make_range(begin(), end()); } + + detail::CellVecSmallVectorTy cellVec() const { + detail::CellVecSmallVectorTy Ret; + Ret.reserve(Set.size()); + Ret.insert(Ret.end(), Set.begin(), Set.end()); + return Ret; + } + + template + detail::CellVecSmallVectorTy cellVec(FilterFn Filter) const { + detail::CellVecSmallVectorTy Ret; + for (const auto &Elem : Set) { + if (std::invoke(Filter, Elem)) { + Ret.push_back(Elem); + } + } + return Ret; + } + + template + detail::CellVecSmallVectorTy> + allOf(Projection Proj, + ByConstRef> Of) const { + detail::CellVecSmallVectorTy> Ret; + for (const auto &Elem : Set) { + auto &&ProjKey = std::invoke(Proj, Elem); + if (ProjKey == Of) { + Ret.push_back(std::forward(ProjKey)); + } + } + return Ret; + } + + [[nodiscard]] size_t size() const noexcept { return Set.size(); } + [[nodiscard]] bool empty() const noexcept { return Set.empty(); } + + [[nodiscard]] size_t getApproxSizeInBytes() const noexcept { + return Set.getMemorySize(); + } + +private: + llvm::DenseSet Set; +}; + +} // namespace psr + +#endif // PHASAR_UTILS_TABLEWRAPPERS diff --git a/include/phasar/Utils/TypeTraits.h b/include/phasar/Utils/TypeTraits.h index 8be00acd3..9a80759b8 100644 --- a/include/phasar/Utils/TypeTraits.h +++ b/include/phasar/Utils/TypeTraits.h @@ -174,6 +174,26 @@ struct variant_idx, T> size_t, std::variant...>(type_identity{}).index()> {}; +template +struct has_isInteresting : std::false_type {}; // NOLINT +template +struct has_isInteresting< + ProblemTy, + decltype(std::declval>().isInteresting( + std::declval()))> + : std::true_type {}; + +template +struct has_llvm_dense_map_info : std::false_type {}; +template +struct has_llvm_dense_map_info< + T, std::void_t::getEmptyKey()), + decltype(llvm::DenseMapInfo::getTombstoneKey()), + decltype(llvm::DenseMapInfo::getHashValue( + std::declval())), + decltype(llvm::DenseMapInfo::isEqual(std::declval(), + std::declval()))>> + : std::true_type {}; } // namespace detail template @@ -243,6 +263,13 @@ PSR_CONCEPT IsEqualityComparable = detail::IsEqualityComparable::value; template PSR_CONCEPT AreEqualityComparable = detail::AreEqualityComparable::value; +template +PSR_CONCEPT has_isInteresting_v = // NOLINT + detail::has_isInteresting::value; + +template +static constexpr bool has_llvm_dense_map_info = + detail::has_llvm_dense_map_info::value; template using type_identity_t = typename type_identity::type; template @@ -268,15 +295,6 @@ struct FalseFn { } }; -struct EmptyType { - friend constexpr bool operator==(EmptyType /*L*/, EmptyType /*R*/) noexcept { - return true; - } - friend constexpr bool operator!=(EmptyType /*L*/, EmptyType /*R*/) noexcept { - return false; - } -}; - /// Delegates to the ctor of T template struct DefaultConstruct { template @@ -302,6 +320,12 @@ template >> return to_string(Val); } +struct IdentityFn { + template decltype(auto) operator()(T &&Val) const noexcept { + return std::forward(Val); + } +}; + // NOLINTEND(readability-identifier-naming) } // namespace psr diff --git a/include/phasar/Utils/Utilities.h b/include/phasar/Utils/Utilities.h index 6be344536..5715c242e 100644 --- a/include/phasar/Utils/Utilities.h +++ b/include/phasar/Utils/Utilities.h @@ -303,19 +303,25 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, return OS; } -template -LLVM_ATTRIBUTE_ALWAYS_INLINE void assertNotNull(const T & /*Value*/) {} +template LLVM_ATTRIBUTE_ALWAYS_INLINE T &assertNotNull(T &Value) { + return Value; +} template -LLVM_ATTRIBUTE_ALWAYS_INLINE void -assertNotNull([[maybe_unused]] const std::optional &Value) { +LLVM_ATTRIBUTE_ALWAYS_INLINE const T & +assertNotNull(const std::optional &Value) { assert(Value.has_value()); + return *Value; } - template -LLVM_ATTRIBUTE_ALWAYS_INLINE void -assertNotNull([[maybe_unused]] const T *Value) { +LLVM_ATTRIBUTE_ALWAYS_INLINE T &assertNotNull(std::optional &Value) { + assert(Value.has_value()); + return *Value; +} + +template LLVM_ATTRIBUTE_ALWAYS_INLINE T &assertNotNull(T *Value) { assert(Value != nullptr); + return *Value; } template void assertAllNotNull([[maybe_unused]] const T &Range) { diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.cpp index f029a39d9..53a3a3adb 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.cpp @@ -77,6 +77,11 @@ auto LLVMBasedBackwardICFG::getCallGraphImpl() const noexcept return ForwardICFG->getCallGraph(); } +[[nodiscard]] size_t +LLVMBasedBackwardICFG::getNumCallSitesImpl() const noexcept { + return ForwardICFG->getNumCallSites(); +} + template class ICFGBase; } // namespace psr diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.cpp index f5b799d90..b617db988 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.cpp @@ -746,7 +746,8 @@ auto IDEExtendedTaintAnalysis::getSummaryEdgeFunction(n_t Curr, d_t CurrNode, // Printing functions: void IDEExtendedTaintAnalysis::emitTextReport( - const SolverResults &SR, llvm::raw_ostream &OS) { + GenericSolverResults SR, llvm::raw_ostream &OS) { + OS << "===== IDEExtendedTaintAnalysis-Results =====\n"; if (!PostProcessed) { doPostProcessing(SR); @@ -834,7 +835,7 @@ IDEExtendedTaintAnalysis::getApproxLoadFrom(const llvm::Value *V) const { } void IDEExtendedTaintAnalysis::doPostProcessing( - const SolverResults &SR) { + GenericSolverResults SR) { PostProcessed = true; llvm::SmallVector RemInst; for (auto &[Inst, PotentialLeaks] : Leaks) { @@ -894,7 +895,7 @@ void IDEExtendedTaintAnalysis::doPostProcessing( } const LeakMap_t &IDEExtendedTaintAnalysis::getAllLeaks( - const SolverResults &SR) & { + GenericSolverResults SR) & { if (!PostProcessed) { doPostProcessing(SR); } @@ -902,7 +903,7 @@ const LeakMap_t &IDEExtendedTaintAnalysis::getAllLeaks( } LeakMap_t IDEExtendedTaintAnalysis::getAllLeaks( - const SolverResults &SR) && { + GenericSolverResults SR) && { if (!PostProcessed) { doPostProcessing(SR); } diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp index 7ce08382f..a14acd478 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp @@ -501,8 +501,9 @@ EdgeFunction IDEGeneralizedLCA::allTopFunction() { }*/ void IDEGeneralizedLCA::emitTextReport( - const SolverResults &SR, + GenericSolverResults + SR, llvm::raw_ostream &Os) { Os << "\n====================== IDE-Linear-Constant-Analysis Report " @@ -557,8 +558,8 @@ void IDEGeneralizedLCA::stripBottomResults( } IDEGeneralizedLCA::lca_results_t IDEGeneralizedLCA::getLCAResults( - SolverResults + GenericSolverResults SR) { std::map> AggResults; llvm::outs() << "\n==== Computing LCA Results ====\n"; diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.cpp index 1836e070f..7c75fccba 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.cpp @@ -586,7 +586,7 @@ IDELinearConstantAnalysis::getSummaryEdgeFunction(n_t Curr, d_t CurrNode, } void IDELinearConstantAnalysis::emitTextReport( - const SolverResults &SR, llvm::raw_ostream &OS) { + GenericSolverResults SR, llvm::raw_ostream &OS) { OS << "\n====================== IDE-Linear-Constant-Analysis Report " "======================\n"; if (!IRDB->debugInfoAvailable()) { @@ -639,7 +639,8 @@ void IDELinearConstantAnalysis::stripBottomResults( } IDELinearConstantAnalysis::lca_results_t -IDELinearConstantAnalysis::getLCAResults(SolverResults SR) { +IDELinearConstantAnalysis::getLCAResults( + GenericSolverResults SR) { std::map> AggResults; llvm::outs() << "\n==== Computing LCA Results ====\n"; for (const auto *F : ICF->getAllFunctions()) { diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.cpp index 225b701fa..e3c3931d0 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDESecureHeapPropagation.cpp @@ -165,7 +165,7 @@ IDESecureHeapPropagation::getSummaryEdgeFunction(n_t /*CallSite*/, } void IDESecureHeapPropagation::emitTextReport( - const SolverResults &SR, llvm::raw_ostream &Os) { + GenericSolverResults SR, llvm::raw_ostream &Os) { LLVMBasedCFG CFG; for (const auto *F : IRDB->getAllFunctions()) { diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.cpp index 4580fdac5..0a8853338 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.cpp @@ -303,7 +303,8 @@ auto IDETypeStateAnalysisBase::getLocalAliasesAndAllocas( } bool IDETypeStateAnalysisBase::hasMatchingTypeName(const llvm::Type *Ty) { - if (const auto *StructTy = llvm::dyn_cast(Ty)) { + if (const auto *StructTy = llvm::dyn_cast(Ty); + StructTy && StructTy->hasName()) { return isTypeNameOfInterest(StructTy->getName()); } // primitive type diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp index 69cd1f1a2..ecc95e0fa 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp @@ -239,8 +239,9 @@ size_t IFDSConstAnalysis::initMemoryLocationCount() { } void IFDSConstAnalysis::emitTextReport( - const SolverResults &SR, + GenericSolverResults + SR, llvm::raw_ostream &OS) { LLVMBasedCFG CFG; diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp index 5a525093f..b744fbcf3 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp @@ -514,10 +514,21 @@ bool IFDSTaintAnalysis::isZeroValue(d_t FlowFact) const noexcept { } void IFDSTaintAnalysis::emitTextReport( - const SolverResults & /*SR*/, + GenericSolverResults /*SR*/, llvm::raw_ostream &OS) { OS << "\n----- Found the following leaks -----\n"; Printer->onFinalize(); } +bool IFDSTaintAnalysis::isInteresting( + const llvm::Instruction *Inst) const noexcept { + if (const auto *Call = llvm::dyn_cast(Inst)) { + if (const auto *StaticCallee = Call->getCalledFunction()) { + return Config->mayLeakValuesAt(Inst, StaticCallee); + } + return true; + } + return Config->mayLeakValuesAt(Inst, nullptr); +} + } // namespace psr diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp index 68e360b35..2b84b4d7b 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp @@ -395,9 +395,7 @@ bool IFDSUninitializedVariables::isZeroValue( } void IFDSUninitializedVariables::emitTextReport( - const SolverResults & /*Result*/, - llvm::raw_ostream &OS) { + GenericSolverResults /*Result*/, llvm::raw_ostream &OS) { OS << "====================== IFDS-Uninitialized-Analysis Report " "======================\n"; if (UndefValueUses.empty()) { diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.cpp new file mode 100644 index 000000000..023cf9705 --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.cpp @@ -0,0 +1,18 @@ +#include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCacheStats.h" + +#include "llvm/Support/raw_ostream.h" + +llvm::raw_ostream &psr::operator<<(llvm::raw_ostream &OS, + const EdgeFunctionCacheStats &S) { + OS << "EdgeFunctionCache Sizes:\n"; + OS << "> NormalAndCtrEFCache:\t" << S.NormalAndCtrEFCacheCumulSize << '\n'; + OS << ">> With EquivalenceClassMap:\t" + << S.NormalAndCtrEFCacheCumulSizeReduced << '\n'; + OS << ">> Avg EF per CFG Edge:\t" << S.AvgEFPerEdge << '\n'; + OS << ">> Med EF per CFG Edge:\t" << S.MedianEFPerEdge << '\n'; + OS << ">> Max EF per CFG Edge:\t" << S.MaxEFPerEdge << '\n'; + OS << "> CallEFCache:\t\t" << S.CallEFCacheCumulSize << '\n'; + OS << "> RetEFCache:\t\t" << S.RetEFCacheCumulSize << '\n'; + OS << "> SummaryEFCache:\t" << S.SummaryEFCacheCumulSize << '\n'; + return OS; +} diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.cpp new file mode 100644 index 000000000..f81bcfc2f --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.cpp @@ -0,0 +1,9 @@ +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheStats.h" + +#include "llvm/Support/raw_ostream.h" + +llvm::raw_ostream &psr::operator<<(llvm::raw_ostream &OS, + const FlowEdgeFunctionCacheStats &Stats) { + return OS << static_cast(Stats) + << static_cast(Stats); +} diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.cpp new file mode 100644 index 000000000..ac19df69a --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.cpp @@ -0,0 +1,15 @@ +#include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h" + +#include "llvm/Support/raw_ostream.h" + +llvm::raw_ostream &psr::operator<<(llvm::raw_ostream &OS, + const FlowFunctionCacheStats &S) { + OS << "FlowFunctionCache Sizes:\n"; + OS << "> NormalFFCache:\t" << S.NormalFFCacheSize << '\n'; + OS << "> CallFFCache:\t\t" << S.CallFFCacheSize << '\n'; + OS << "> ReturnFFCache:\t" << S.ReturnFFCacheSize << '\n'; + OS << "> SimpleRetFFCache:\t" << S.SimpleRetFFCacheSize << '\n'; + OS << "> CallToRetFFCache:\t" << S.CallToRetFFCacheSize << '\n'; + OS << "> SummaryFFCache:\t" << S.SummaryFFCacheSize << '\n'; + return OS; +} diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.cpp new file mode 100644 index 000000000..4c1e72e4d --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.cpp @@ -0,0 +1,73 @@ +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverStats.h" + +#include "llvm/Support/Format.h" +#include "llvm/Support/raw_ostream.h" + +llvm::raw_ostream &psr::operator<<(llvm::raw_ostream &OS, + const IterativeIDESolverStats &S) { + OS << S.FEStats; + + OS << "General Solver Stats:\n"; + OS << "> AllInterPropagations:\t\t" << S.NumAllInterPropagations << '\n'; + OS << "> AllInterPropagations(Bytes):\t~" << S.AllInterPropagationsBytes + << '\n'; + OS << "> SourceFactAndCSToInterJob:\t" << S.SourceFactAndCSToInterJobSize + << '\n'; + OS << "> SourceFactAndCSToInterJob(Bytes):\t~" + << S.SourceFactAndCSToInterJobBytes << '\n'; + OS << "> SourceFactAndFuncToInterJob:\t" << S.SourceFactAndFuncToInterJobSize + << '\n'; + OS << "> SourceFactAndFuncToInterJob(Bytes):\t~" + << S.SourceFactAndFuncToInterJobBytes << '\n'; + OS << "> MaxInnerMapSize:\t" << S.MaxESGEdgesPerInst << '\n'; + OS << "> AvgInnerMapSize:\t" << llvm::format("%g", S.AvgESGEdgesPerInst) + << '\n'; + OS << "> JumpFunctions Map Size(Bytes):\t~" << S.JumpFunctionsMapBytes + << '\n'; + OS << "> ValTab Size(Bytes):\t\t~" << S.ValTabBytes << '\n'; + OS << "> NumFlowFacts:\t\t" << S.NumFlowFacts << '\n'; + + OS << "Compressor Capacities:\n"; + OS << "> NodeCompressor:\t" << S.InstCompressorCapacity << '\n'; + OS << "> FactCompressor:\t" << S.FactCompressorCapacity << '\n'; + OS << "> FunCompressor:\t" << S.FunCompressorCapacity << '\n'; + + OS << "High Watermarks:\n"; + OS << "> JumpFunctions:\t" << S.NumPathEdgesHighWatermark << '\n'; + OS << "> WorkList:\t" << S.WorkListHighWatermark << '\n'; + OS << "> CallWL:\t" << S.CallWLHighWatermark << '\n'; + OS << "> WLProp:\t" << S.WLPropHighWatermark << '\n'; + OS << "> WLComp:\t" << S.WLCompHighWatermark << '\n'; + + OS << "InterPropagationJobs:\n"; + OS << "> Total calls to propagateProcedureSummaries: " + << S.TotalNumRelevantCalls << '\n'; + OS << "> Max InterJobs per relevant call: " + << S.MaxNumInterJobsPerRelevantCall << '\n'; + OS << "> Avg InterJobs per relevant call: " + << llvm::format("%g", double(S.CumulNumInterJobsPerRelevantCall) / + double(S.TotalNumRelevantCalls)) + << '\n'; + OS << "> Total num of linear searches for summaries: " + << S.TotalNumLinearSearchForSummary << '\n'; + OS << "> Max Length of linear search for summaries: " + << S.MaxLenLinearSearchForSummary << '\n'; + OS << "> Avg Length of linear search for summaries: " + << llvm::format("%g", double(S.CumulLinearSearchLenForSummary) / + double(S.TotalNumLinearSearchForSummary)) + << '\n'; + OS << "> Max Diff of summaries found vs search length: " + << S.MaxDiffNumSummariesFound << '\n'; + OS << "> Avg Diff of summaries found vs search length: " + << llvm::format("%g", double(S.CumulDiffNumSummariesFound) / + double(S.TotalNumLinearSearchForSummary)) + << '\n'; + OS << "> Rel Diff of summaries found vs search length: " + << llvm::format("%g", double(S.CumulRelDiffNumSummariesFound) / + double(S.TotalNumLinearSearchForSummary)) + << '\n'; + OS << "> Num Cached EndSummaries: " << S.NumEndSummaries << '\n'; + OS << "> EndSummaryTab(Bytes): " << S.EndSummaryTabSize << '\n'; + + return OS; +} diff --git a/lib/PhasarLLVM/Pointer/LLVMAliasGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMAliasGraph.cpp index ef1378b6e..26a0acac8 100644 --- a/lib/PhasarLLVM/Pointer/LLVMAliasGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMAliasGraph.cpp @@ -135,8 +135,8 @@ std::string LLVMAliasGraph::EdgeProperties::getValueAsString() const { // points-to graph stuff LLVMAliasGraph::LLVMAliasGraph(LLVMProjectIRDB &IRDB, bool UseLazyEvaluation, - AliasAnalysisType PATy) - : PTA(IRDB, UseLazyEvaluation, PATy) {} + AliasAnalysisType /*PATy*/) + : PTA(IRDB, UseLazyEvaluation) {} void LLVMAliasGraph::computeAliasGraph(const llvm::Value *V) { // FIXME when fixed in LLVM @@ -249,7 +249,7 @@ void LLVMAliasGraph::computeAliasGraph(llvm::Function *F) { bool LLVMAliasGraph::isInterProcedural() const noexcept { return false; } AliasAnalysisType LLVMAliasGraph::getAliasAnalysisType() const noexcept { - return PTA.getPointerAnalysisType(); + return AliasAnalysisType::Invalid; } AliasResult LLVMAliasGraph::alias(const llvm::Value *V1, const llvm::Value *V2, diff --git a/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp b/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp index bf5ad618c..4473b19fe 100644 --- a/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp @@ -44,56 +44,6 @@ struct LLVMBasedAliasAnalysis::Impl { llvm::FunctionPassManager FPM{}; }; -static void printResults(llvm::AliasResult AR, bool P, const llvm::Value *V1, - const llvm::Value *V2, const llvm::Module *M) { - if (P) { - std::string O1; - - std::string O2; - { - llvm::raw_string_ostream OS1(O1); - - llvm::raw_string_ostream OS2(O2); - V1->printAsOperand(OS1, true, M); - V2->printAsOperand(OS2, true, M); - } - - if (O2 < O1) { - std::swap(O1, O2); - } - llvm::errs() << " " << AR << ":\t" << O1 << ", " << O2 << "\n"; - } -} - -static inline void printModRefResults(const char *Msg, bool P, - const llvm::Instruction *I, - const llvm::Value *Ptr, - const llvm::Module *M) { - if (P) { - llvm::errs() << " " << Msg << ": Ptr: "; - Ptr->printAsOperand(llvm::errs(), true, M); - llvm::errs() << "\t<->" << *I << '\n'; - } -} - -static inline void printModRefResults(const char *Msg, bool P, - const llvm::CallBase *CallA, - const llvm::CallBase *CallB, - const llvm::Module * /*M*/) { - if (P) { - llvm::errs() << " " << Msg << ": " << *CallA << " <-> " << *CallB << '\n'; - } -} - -static inline void printLoadStoreResults(llvm::AliasResult AR, bool P, - const llvm::Value *V1, - const llvm::Value *V2, - const llvm::Module * /*M*/) { - if (P) { - llvm::errs() << " " << AR << ": " << *V1 << " <-> " << *V2 << '\n'; - } -} - bool LLVMBasedAliasAnalysis::hasAliasInfo(const llvm::Function &Fun) const { return AAInfos.find(&Fun) != AAInfos.end(); } @@ -158,264 +108,4 @@ LLVMBasedAliasAnalysis::LLVMBasedAliasAnalysis(LLVMProjectIRDB &IRDB, LLVMBasedAliasAnalysis::~LLVMBasedAliasAnalysis() = default; -void LLVMBasedAliasAnalysis::print(llvm::raw_ostream &OS) const { - OS << "Points-to Info:\n"; - for (const auto &[Fn, AA] : AAInfos) { - bool PrintAll = true; - bool PrintNoAlias = true; - bool PrintMayAlias = true; - bool PrintPartialAlias = true; - bool PrintMustAlias = true; - bool EvalAAMD = true; - bool PrintNoModRef = true; - bool PrintMod = true; - bool PrintRef = true; - bool PrintModRef = true; - bool PrintMust = true; - bool PrintMustMod = true; - bool PrintMustRef = true; - bool PrintMustModRef = true; - - // taken from llvm/Analysis/AliasAnalysisEvaluator.cpp - const llvm::DataLayout &DL = Fn->getParent()->getDataLayout(); - - llvm::SetVector Pointers; - llvm::SmallSetVector Calls; - llvm::SetVector Loads; - llvm::SetVector Stores; - - for (const auto &I : Fn->args()) { - if (I.getType()->isPointerTy()) { // Add all pointer arguments. - Pointers.insert(&I); - } - } - - for (llvm::const_inst_iterator I = inst_begin(*Fn), E = inst_end(*Fn); - I != E; ++I) { - if (I->getType()->isPointerTy()) { // Add all pointer instructions. - Pointers.insert(&*I); - } - if (EvalAAMD && llvm::isa(&*I)) { - Loads.insert(&*I); - } - if (EvalAAMD && llvm::isa(&*I)) { - Stores.insert(&*I); - } - const llvm::Instruction &Inst = *I; - if (const auto *Call = llvm::dyn_cast(&Inst)) { - llvm::Value *Callee = Call->getCalledOperand(); - // Skip actual functions for direct function calls. - if (!llvm::isa(Callee) && - isInterestingPointer(Callee)) { - Pointers.insert(Callee); - } - // Consider formals. - for (const llvm::Use &DataOp : Call->data_ops()) { - if (isInterestingPointer(DataOp)) { - Pointers.insert(DataOp); - } - } - Calls.insert(Call); - } else { - // Consider all operands. - for (llvm::Instruction::const_op_iterator OI = Inst.op_begin(), - OE = Inst.op_end(); - OI != OE; ++OI) { - if (isInterestingPointer(*OI)) { - Pointers.insert(*OI); - } - } - } - } - - if (PrintAll || PrintNoAlias || PrintMayAlias || PrintPartialAlias || - PrintMustAlias || PrintNoModRef || PrintMod || PrintRef || - PrintModRef) { - OS << "Function: " << Fn->getName() << ": " << Pointers.size() - << " pointers, " << Calls.size() << " call sites\n"; - } - - // iterate over the worklist, and run the full (n^2)/2 disambiguations - for (auto I1 = Pointers.begin(), E = Pointers.end(); I1 != E; ++I1) { - auto I1Size = llvm::LocationSize::beforeOrAfterPointer(); - llvm::Type *I1ElTy = - !(*I1)->getType()->isOpaquePointerTy() - ? (*I1)->getType()->getNonOpaquePointerElementType() - : nullptr; - if (!I1ElTy && I1ElTy->isSized()) { - I1Size = llvm::LocationSize::precise(DL.getTypeStoreSize(I1ElTy)); - } - for (auto I2 = Pointers.begin(); I2 != I1; ++I2) { - auto I2Size = llvm::LocationSize::beforeOrAfterPointer(); - llvm::Type *I2ElTy = - !(*I2)->getType()->isOpaquePointerTy() - ? (*I2)->getType()->getNonOpaquePointerElementType() - : nullptr; - if (I2ElTy && I2ElTy->isSized()) { - I2Size = llvm::LocationSize::precise(DL.getTypeStoreSize(I2ElTy)); - } - llvm::AliasResult AR = AA->alias(*I1, I1Size, *I2, I2Size); - switch (AR) { - case llvm::AliasResult::NoAlias: - printResults(AR, PrintNoAlias, *I1, *I2, Fn->getParent()); - break; - case llvm::AliasResult::MayAlias: - printResults(AR, PrintMayAlias, *I1, *I2, Fn->getParent()); - break; - case llvm::AliasResult::PartialAlias: - printResults(AR, PrintPartialAlias, *I1, *I2, Fn->getParent()); - break; - case llvm::AliasResult::MustAlias: - printResults(AR, PrintMustAlias, *I1, *I2, Fn->getParent()); - break; - } - } - } - - if (EvalAAMD) { - // iterate over all pairs of load, store - for (const llvm::Value *Load : Loads) { - for (const llvm::Value *Store : Stores) { - llvm::AliasResult AR = AA->alias( - llvm::MemoryLocation::get(llvm::cast(Load)), - llvm::MemoryLocation::get(llvm::cast(Store))); - switch (AR) { - case llvm::AliasResult::NoAlias: - printLoadStoreResults(AR, PrintNoAlias, Load, Store, - Fn->getParent()); - break; - case llvm::AliasResult::MayAlias: - printLoadStoreResults(AR, PrintMayAlias, Load, Store, - Fn->getParent()); - break; - case llvm::AliasResult::PartialAlias: - printLoadStoreResults(AR, PrintPartialAlias, Load, Store, - Fn->getParent()); - break; - case llvm::AliasResult::MustAlias: - printLoadStoreResults(AR, PrintMustAlias, Load, Store, - Fn->getParent()); - break; - } - } - } - - // iterate over all pairs of store, store - for (auto I1 = Stores.begin(), E = Stores.end(); I1 != E; ++I1) { - for (auto I2 = Stores.begin(); I2 != I1; ++I2) { - llvm::AliasResult AR = AA->alias( - llvm::MemoryLocation::get(llvm::cast(*I1)), - llvm::MemoryLocation::get(llvm::cast(*I2))); - switch (AR) { - case llvm::AliasResult::NoAlias: - printLoadStoreResults(AR, PrintNoAlias, *I1, *I2, Fn->getParent()); - break; - case llvm::AliasResult::MayAlias: - printLoadStoreResults(AR, PrintMayAlias, *I1, *I2, Fn->getParent()); - break; - case llvm::AliasResult::PartialAlias: - printLoadStoreResults(AR, PrintPartialAlias, *I1, *I2, - Fn->getParent()); - break; - case llvm::AliasResult::MustAlias: - printLoadStoreResults(AR, PrintMustAlias, *I1, *I2, - Fn->getParent()); - break; - } - } - } - } - - // Mod/ref alias analysis: compare all pairs of calls and values - for (const llvm::CallBase *Call : Calls) { - for (const auto *Pointer : Pointers) { - auto Size = llvm::LocationSize::beforeOrAfterPointer(); - llvm::Type *ElTy = - !Pointer->getType()->isOpaquePointerTy() - ? Pointer->getType()->getNonOpaquePointerElementType() - : nullptr; - if (ElTy && ElTy->isSized()) { - Size = llvm::LocationSize::precise(DL.getTypeStoreSize(ElTy)); - } - - switch (AA->getModRefInfo(Call, Pointer, Size)) { - case llvm::ModRefInfo::NoModRef: - printModRefResults("NoModRef", PrintNoModRef, Call, Pointer, - Fn->getParent()); - break; - case llvm::ModRefInfo::Mod: - printModRefResults("Just Mod", PrintMod, Call, Pointer, - Fn->getParent()); - break; - case llvm::ModRefInfo::Ref: - printModRefResults("Just Ref", PrintRef, Call, Pointer, - Fn->getParent()); - break; - case llvm::ModRefInfo::ModRef: - printModRefResults("Both ModRef", PrintModRef, Call, Pointer, - Fn->getParent()); - break; - case llvm::ModRefInfo::Must: - printModRefResults("Must", PrintMust, Call, Pointer, Fn->getParent()); - break; - case llvm::ModRefInfo::MustMod: - printModRefResults("Just Mod (MustAlias)", PrintMustMod, Call, - Pointer, Fn->getParent()); - break; - case llvm::ModRefInfo::MustRef: - printModRefResults("Just Ref (MustAlias)", PrintMustRef, Call, - Pointer, Fn->getParent()); - break; - case llvm::ModRefInfo::MustModRef: - printModRefResults("Both ModRef (MustAlias)", PrintMustModRef, Call, - Pointer, Fn->getParent()); - break; - } - } - } - - // Mod/ref alias analysis: compare all pairs of calls - for (const llvm::CallBase *CallA : Calls) { - for (const llvm::CallBase *CallB : Calls) { - if (CallA == CallB) { - continue; - } - switch (AA->getModRefInfo(CallA, CallB)) { - case llvm::ModRefInfo::NoModRef: - printModRefResults("NoModRef", PrintNoModRef, CallA, CallB, - Fn->getParent()); - break; - case llvm::ModRefInfo::Mod: - printModRefResults("Just Mod", PrintMod, CallA, CallB, - Fn->getParent()); - break; - case llvm::ModRefInfo::Ref: - printModRefResults("Just Ref", PrintRef, CallA, CallB, - Fn->getParent()); - break; - case llvm::ModRefInfo::ModRef: - printModRefResults("Both ModRef", PrintModRef, CallA, CallB, - Fn->getParent()); - break; - case llvm::ModRefInfo::Must: - printModRefResults("Must", PrintMust, CallA, CallB, Fn->getParent()); - break; - case llvm::ModRefInfo::MustMod: - printModRefResults("Just Mod (MustAlias)", PrintMustMod, CallA, CallB, - Fn->getParent()); - break; - case llvm::ModRefInfo::MustRef: - printModRefResults("Just Ref (MustAlias)", PrintMustRef, CallA, CallB, - Fn->getParent()); - break; - case llvm::ModRefInfo::MustModRef: - printModRefResults("Both ModRef (MustAlias)", PrintMustModRef, CallA, - CallB, Fn->getParent()); - break; - } - } - } - } -} - } // namespace psr diff --git a/test/llvm_test_code/linear_constant/external_fun.cpp b/test/llvm_test_code/linear_constant/external_fun.cpp new file mode 100644 index 000000000..60c9f5c4e --- /dev/null +++ b/test/llvm_test_code/linear_constant/external_fun.cpp @@ -0,0 +1,21 @@ +#include +#include + +extern void foo() noexcept; + +void usage(int p) { + int q = p + 1; + foo(); + puts("Usage: external_fun "); + abort(); +} + +int main(int argc, char **argv) { + if (argc <= 1) { + usage(0); + } + int x = 42; + int y = x + 1; + int z = y * 2; + return y; +} diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt index 9ab94b5db..7a2633228 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt @@ -4,6 +4,7 @@ set(IfdsIdeSources EdgeFunctionComposerTest.cpp EdgeFunctionSingletonCacheTest.cpp InteractiveIDESolverTest.cpp + IterativeIDESolverTest.cpp ) foreach(TEST_SRC ${IfdsIdeSources}) diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp new file mode 100644 index 000000000..6e75ad4bb --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp @@ -0,0 +1,255 @@ + +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h" + +#include "phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h" +#include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" +#include "phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h" +#include "phasar/DataFlow/IfdsIde/SolverResults.h" +#include "phasar/Domain/LatticeDomain.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h" +#include "phasar/PhasarLLVM/Passes/ValueAnnotationPass.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/TypeHierarchy/LLVMTypeHierarchy.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Logger.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace psr; + +/* ============== TEST FIXTURE ============== */ +class IterativeIDESolverTest : public ::testing::Test { +protected: + template + void doAnalysis(const llvm::Twine &LlvmFilePath, bool PrintDump = false) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + LlvmFilePath); + LLVMTypeHierarchy TH(IRDB); + LLVMAliasSet PT(&IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, &PT, + Soundness::Soundy, /*IncludeGlobals*/ true); + + IDELinearConstantAnalysis Problem(&IRDB, &ICFG, {"main"}); + IterativeIDESolver Solver( + &Problem, &ICFG); + + auto Start = std::chrono::steady_clock::now(); + Solver.solve(); + auto End = std::chrono::steady_clock::now(); + auto NewTime = End - Start; + llvm::errs() << "IterativeIDESolver Elapsed:\t" << NewTime.count() + << "ns\n"; + + IDESolver OldSolver(&Problem, &ICFG); + Start = std::chrono::steady_clock::now(); + OldSolver.solve(); + End = std::chrono::steady_clock::now(); + + auto OldTime = End - Start; + llvm::errs() << "IDESolver Elapsed:\t\t" << OldTime.count() << "ns\n"; + + if (PrintDump) { + Solver.dumpResults(); + OldSolver.dumpResults(); + } + + checkEquality(OldSolver.getSolverResults(), Solver.getSolverResults(), + SolverConfigTy{}); + + [[maybe_unused]] GenericSolverResults< + const llvm::Instruction *, const llvm::Value *, LatticeDomain> + SR = OldSolver.getSolverResults(); + [[maybe_unused]] GenericSolverResults< + const llvm::Instruction *, const llvm::Value *, + std::conditional_t, BinaryDomain>> + SR2 = Solver.getSolverResults(); + } + + struct NonGCIFDSSolverConfig : IFDSSolverConfig { + static inline constexpr auto EnableJumpFunctionGC = + JumpFunctionGCMode::Disabled; + }; + + template + void checkEquality(const SR1 &LHS, const SR2 &RHS, IDESolverConfig /*Tag*/) { + llvm::errs() << "IDE Equality Check\n"; + EXPECT_EQ(LHS.size(), RHS.size()) + << "The instructions, where results are computed differ"; + + for (const auto &[Row, ColVal] : LHS.rowMapView()) { + EXPECT_TRUE(RHS.containsNode(Row)) + << "The RHS does not contain results at inst " << llvmIRToString(Row); + + auto RHSColVal = RHS.row(Row); + EXPECT_EQ(ColVal.size(), RHSColVal.size()) + << "The number of dataflow facts at inst " << llvmIRToString(Row) + << " do not match"; + + for (const auto &[Col, Val] : ColVal) { + auto It = RHSColVal.find(Col); + + EXPECT_TRUE(It != RHSColVal.end()) + << "The RHS does not contain fact " << llvmIRToString(Col) + << " at inst " << llvmIRToString(Row); + if (It != RHSColVal.end()) { + EXPECT_TRUE(Val == It->second) + << "The edge values at inst " << llvmIRToString(Row) + << " and fact " << llvmIRToString(Col) << " do not match: " << Val + << " vs " << It->second; + } + } + } + } + + template + void checkEquality(const SR1 &LHS, const SR2 &RHS, IFDSSolverConfig /*Tag*/) { + llvm::errs() << "IFDS Equality Check\n"; + EXPECT_EQ(LHS.size(), RHS.size()) + << "The instructions, where results are computed differ"; + + for (const auto &[Row, ColVal] : LHS.rowMapView()) { + EXPECT_TRUE(RHS.containsNode(Row)) + << "The RHS does not contain results at inst " << llvmIRToString(Row); + + auto RHSColVal = RHS.row(Row); + EXPECT_EQ(ColVal.size(), RHSColVal.size()) + << "The number of dataflow facts at inst " << llvmIRToString(Row) + << " do not match"; + + for (const auto &[Col, Val] : ColVal) { + EXPECT_TRUE(RHSColVal.count(Col)) + << "The RHS does not contain fact " << llvmIRToString(Col) + << " at inst " << llvmIRToString(Row); + } + } + } + + void TearDown() override {} + +}; // Test Fixture + +/// --> Using IDESolverConfig + +TEST_F(IterativeIDESolverTest, IDESolverTestBranch) { + doAnalysis("control_flow/branch_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDESolverTestLoop) { + doAnalysis("control_flow/loop_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call06) { + doAnalysis("linear_constant/call_06_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call07) { + doAnalysis("linear_constant/call_07_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call08) { + doAnalysis("linear_constant/call_08_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call09) { + doAnalysis("linear_constant/call_09_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call10) { + doAnalysis("linear_constant/call_10_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call11) { + doAnalysis("linear_constant/call_11_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_Call12) { + doAnalysis("linear_constant/call_12_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_recursion_01) { + doAnalysis("linear_constant/recursion_01_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_recursion_02) { + doAnalysis("linear_constant/recursion_02_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_recursion_03) { + doAnalysis("linear_constant/recursion_03_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_01) { + doAnalysis("linear_constant/global_01_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_02) { + doAnalysis("linear_constant/global_02_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_03) { + doAnalysis("linear_constant/global_03_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_04) { + doAnalysis("linear_constant/global_04_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_05) { + doAnalysis("linear_constant/global_05_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_06) { + doAnalysis("linear_constant/global_06_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_07) { + doAnalysis("linear_constant/global_07_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_08) { + doAnalysis("linear_constant/global_08_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_09) { + doAnalysis("linear_constant/global_09_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_global_10) { + doAnalysis("linear_constant/global_10_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IDELinearConstant_external_fun_01) { + doAnalysis("linear_constant/external_fun_cpp.ll"); +} + +/// <-- Using IDESolverConfig + +/// --> Using IFDSSolverConfig + +TEST_F(IterativeIDESolverTest, IFDSSolverTestBranch) { + doAnalysis("control_flow/branch_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSSolverTestLoop) { + doAnalysis("control_flow/loop_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call06) { + doAnalysis("linear_constant/call_06_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call07) { + doAnalysis("linear_constant/call_07_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call08) { + doAnalysis("linear_constant/call_08_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call09) { + doAnalysis("linear_constant/call_09_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call10) { + doAnalysis("linear_constant/call_10_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call11) { + doAnalysis("linear_constant/call_11_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call12) { + doAnalysis("linear_constant/call_12_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_recursion_01) { + doAnalysis("linear_constant/recursion_01_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_recursion_02) { + doAnalysis("linear_constant/recursion_02_cpp.ll"); +} +TEST_F(IterativeIDESolverTest, IFDSLinearConstant_recursion_03) { + doAnalysis("linear_constant/recursion_03_cpp.ll"); +} + +/// <-- Using IFDSSolverConfig + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +}