From 9e7da87f4fe237d1d0b555ce60f9324e06e42aa2 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sat, 27 Dec 2025 16:54:50 +0100 Subject: [PATCH 01/75] Initial PAGBuilder --- .../Pointer/LLVMPointerAssignmentGraph.h | 134 ++++ .../phasar/PhasarLLVM/Utils/LLVMShorthands.h | 31 + .../phasar/Pointer/PointerAssignmentGraph.h | 160 +++++ include/phasar/Utils/ValueCompressor.h | 121 ++++ .../Pointer/LLVMPointerAssignmentGraph.cpp | 580 ++++++++++++++++++ lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 59 ++ 6 files changed, 1085 insertions(+) create mode 100644 include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h create mode 100644 include/phasar/Pointer/PointerAssignmentGraph.h create mode 100644 include/phasar/Utils/ValueCompressor.h create mode 100644 lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h new file mode 100644 index 0000000000..33f24b6ee8 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h @@ -0,0 +1,134 @@ +#pragma once + +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" +#include "phasar/Pointer/PointerAssignmentGraph.h" + +namespace llvm { +class Instruction; +class Value; +class Function; +} // namespace llvm + +namespace psr { + +/// The PAG-node used for LLVM-based pointer-assignment graphs. It consists of +/// regular LLVM values + special return-slots per non-void function. In that +/// case, the stored pointer is the corresponding function. +/// +/// This class is compatible with llvm::TinyPtrVector and other APIs relying on +/// llvm::PointerLikeTypeTraits. +struct PAGVariable : public llvm::PointerIntPair { + using Base = llvm::PointerIntPair; + + struct Return { + const llvm::Function *Fun; + }; + + PAGVariable() noexcept = default; // To enable move-assign in TinyPtrVector + PAGVariable(const llvm::Value *Var) noexcept : Base(Var, false) { + assert(Var != nullptr); + } + PAGVariable(Return RetVar) noexcept : Base(RetVar.Fun, true) { + assert(RetVar.Fun != nullptr); + } + + [[nodiscard]] bool isReturnVariable() const noexcept { + return this->getInt(); + } + + [[nodiscard]] bool isInFunction() const noexcept { + return isReturnVariable() || + llvm::isa(this->getPointer()); + } + + [[nodiscard]] const llvm::Function *getFunction() const noexcept { + const auto *Ptr = this->getPointer(); + if (isReturnVariable()) { + return llvm::cast(Ptr); + } + if (const auto *Arg = llvm::dyn_cast(Ptr)) { + return Arg->getParent(); + } + if (const auto *Inst = llvm::dyn_cast(Ptr)) { + return Inst->getFunction(); + } + + return nullptr; + } + + [[nodiscard]] const llvm::Value *get() const noexcept { + return this->getPointer(); + } + explicit operator const llvm::Value *() const noexcept { return get(); } + + [[nodiscard]] const llvm::Value *valueOrNull() const noexcept { + return isReturnVariable() ? nullptr : get(); + } + + friend bool operator==(PAGVariable V1, PAGVariable V2) noexcept { + return V1.getOpaqueValue() == V2.getOpaqueValue(); + } + friend bool operator!=(PAGVariable V1, PAGVariable V2) noexcept { + return !(V1 == V2); + } + + friend auto hash_value(PAGVariable V) noexcept { + return llvm::hash_value(V.getOpaqueValue()); + } +}; + +[[nodiscard]] std::string to_string(PAGVariable Var); + +struct LLVMPAGDomain : LLVMAnalysisDomainDefault { + using v_t = PAGVariable; +}; + +class LLVMPAGBuilder : public PAGBuilder { + struct PAGBuildData; + void buildPAG(const LLVMProjectIRDB &IRDB, ValueCompressor &VC, + PBStrategy &Strategy) override; +}; + +} // namespace psr + +namespace llvm { +template <> struct PointerLikeTypeTraits { + static inline void *getAsVoidPointer(psr::PAGVariable P) { + return P.getOpaqueValue(); + } + + static inline psr::PAGVariable getFromVoidPointer(void *P) { + psr::PAGVariable V{nullptr}; + static_cast(V) = + psr::PAGVariable::Base::getFromOpaqueValue(P); + return V; + } + + static inline psr::PAGVariable getFromVoidPointer(const void *P) { + psr::PAGVariable V{nullptr}; + static_cast(V) = + psr::PAGVariable::Base::getFromOpaqueValue(P); + return V; + } + + static constexpr int NumLowBitsAvailable = + PointerLikeTypeTraits::NumLowBitsAvailable; +}; + +template <> struct DenseMapInfo { + static psr::PAGVariable getEmptyKey() noexcept { + return DenseMapInfo::getEmptyKey(); + } + static psr::PAGVariable getTombstoneKey() noexcept { + return DenseMapInfo::getTombstoneKey(); + } + static hash_code getHashValue(psr::PAGVariable V) noexcept { + return hash_value(V); + } + + static bool isEqual(psr::PAGVariable V1, psr::PAGVariable V2) noexcept { + return V1 == V2; + } +}; +} // namespace llvm diff --git a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h index 582fef9500..711820f467 100644 --- a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h +++ b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h @@ -19,6 +19,10 @@ #include "phasar/Utils/Utilities.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Type.h" +#include "llvm/Support/Casting.h" + #include #include @@ -256,6 +260,33 @@ bool isGuardVariable(const llvm::Value *V); */ bool isStaticVariableLazyInitializationBranch(const llvm::BranchInst *Inst); +/// Approximates, whether the given LLVM type may not contain a pointer. +/// This check is designed to be extremely lightweight and is therefore not very +/// precise. +/// +/// \returns True, iff it can be proven that Ty does *not* contain a pointer +[[nodiscard]] inline bool definitelyContainsNoPointer(const llvm::Type *Ty) { + return Ty->isIntOrIntVectorTy() || Ty->isFloatingPointTy() || + Ty->isFPOrFPVectorTy() || Ty->isVoidTy(); +} +/// Approximates, whether the given LLVM value may not contain a pointer. +/// This check is designed to be extremely lightweight and is therefore not very +/// precise. +/// +/// \returns True, iff it can be proven that Val does *not* contain a pointer +[[nodiscard]] inline bool definitelyContainsNoPointer(const llvm::Value *Val) { + return llvm::isa(Val) || + definitelyContainsNoPointer(Val->getType()); +} + +/// Approximates, whether the given LLVM value may be address-taken, i.e., +/// whether its pointer value is used for other purposes than just +/// store/load/gep. +/// +/// This check is designed to be rather lightweight and may therefore not be +/// precise in all cases. +[[nodiscard]] bool isAddressTakenVariable(const llvm::Value *Var) noexcept; + /** * Tests for * e.g. diff --git a/include/phasar/Pointer/PointerAssignmentGraph.h b/include/phasar/Pointer/PointerAssignmentGraph.h new file mode 100644 index 0000000000..1d3231c19a --- /dev/null +++ b/include/phasar/Pointer/PointerAssignmentGraph.h @@ -0,0 +1,160 @@ +#pragma once + +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/Nullable.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/Utilities.h" +#include "phasar/Utils/ValueCompressor.h" + +#include +#include +#include + +namespace psr { + +/// A utility-class that can be used to build a pointer-assignment graph (PAG). +/// +/// This class does not enforce a specifiy graph-layout -- it does not even +/// require that you explicitly store a graph in memory. Instead, you get +/// notified about every node and edge that should be created in the PAG. +/// +/// \tparam AnalysisDomainT The analysis domain to use. +template class PAGBuilder { +public: + using n_t = typename AnalysisDomainT::n_t; + using v_t = typename AnalysisDomainT::v_t; + using f_t = typename AnalysisDomainT::f_t; + using db_t = typename AnalysisDomainT::db_t; + + struct Assign {}; + struct Gep { + std::optional Offset; + }; + struct Call { + uint16_t ArgNo; + }; + struct Return {}; + struct Load {}; + struct Store {}; + struct StorePOI {}; + struct Copy {}; + + struct Edge : public std::variant { + using Base = + std::variant; + using std::variant::variant; + + template static constexpr size_t kindOf() noexcept { + return psr::variant_idx; + } + + [[nodiscard]] constexpr size_t kind() const noexcept { + return this->index(); + } + + template + [[nodiscard]] constexpr bool isa() const noexcept { + return std::holds_alternative(*this) || + (... || std::holds_alternative(*this)); + } + + template + [[nodiscard]] constexpr std::optional dyn_cast() const noexcept { + if (const auto *Ptr = std::get_if(this)) { + return *Ptr; + } + + return std::nullopt; + } + + template + constexpr decltype(auto) apply(HandlerFn &&Handler) const { + return std::visit(PSR_FWD(Handler), *this); + } + + [[nodiscard]] friend constexpr llvm::StringRef to_string(Edge E) noexcept { + return E.apply(psr::Overloaded{ + [](Assign) { return "Assign"; }, + // TODO: Print offset, if present + [](Gep) { return "Gep"; }, + // TODO: Print ArgNo + [](Call) { return "Call"; }, + [](Return) { return "Return"; }, + [](Load) { return "Load"; }, + [](Store) { return "Store"; }, + [](StorePOI) { return "StorePOI"; }, + [](Copy) { return "Copy"; }, + }); + } + }; + + /// The main customization point for PAG building. + /// Implement this interface to provide several callbacks to the PAGBuilder, + /// specifying how the PAG should be built. + struct PBStrategy { + constexpr PBStrategy() noexcept = default; + virtual ~PBStrategy() = default; + + /// Called by buildPAG() for every (unique) edge that should be added to the + /// PAG + /// + /// \param From The source-node. + /// \param To The destination-node. + /// \param E The edge-label that specifies the kind of data-flow that is + /// denoted by this edge. + /// \param AtInstruction Optionally holds the instruction that caused this + /// edge to be created. + virtual void onAddEdge(ValueId From, ValueId To, Edge E, + Nullable AtInstruction) = 0; + + /// Called by buildPAG() for every (unique) node that should be added to the + /// PAG, *excluding* nodes that have already been registered in the used + /// ValueCompressor before buildPAG() was called. + /// + /// \param Variable The IR-specific variable/value for which the new node + /// has been created. + /// \param VId The value-id of the newly created node. + virtual void onAddValue(ByConstRef Variable, ValueId VId) { + // Do things like allocating a new slot in the adjacency-list here + } + + /// Estimates the maximum number of values created in the ValueCompressor. + /// Must not be exact, this is just for pre-allocating buffers for + /// optimization purposes. + [[nodiscard]] virtual size_t + getNumPossibleValues(const db_t &IRDB) const noexcept { + // May use a different heuristic here... + return IRDB.getNumInstructions(); + } + + /// Invokes a call-back with each function that may be called at the given + /// call-site. Usually, you want to use a CallGraph or a Resolver here. + /// + /// \param CS The call-site. + /// \param WithCallee The callback to be invoked for each callee. + virtual void + withCalleesOfCallAt(ByConstRef CS, + llvm::function_ref WithCallee) const = 0; + }; + + constexpr PAGBuilder() noexcept = default; + virtual ~PAGBuilder() = default; + + /// Iterates the passed IRDB to build a PAG, based on the given Strategy. + /// + /// \param IRDB The IR program to analyze. + /// \param VC The value-compressor to use for assigning unique sequential + /// integer-ids to each PAG-node. This VC may be pre-populated. You can expect + /// that buildPAG() will respect the pre-population in that case. Otherwise, + /// ids will be assigned in visitation order (which is not stable and should + /// not be relied on!). + /// \param Strategy The customization-point for this function. See the docs of + /// PBStrategy for more information. + virtual void buildPAG(const db_t &IRDB, ValueCompressor &VC, + PBStrategy &Strategy) = 0; +}; + +} // namespace psr diff --git a/include/phasar/Utils/ValueCompressor.h b/include/phasar/Utils/ValueCompressor.h new file mode 100644 index 0000000000..f03dba2be3 --- /dev/null +++ b/include/phasar/Utils/ValueCompressor.h @@ -0,0 +1,121 @@ +#pragma once + +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/TypedVector.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/TinyPtrVector.h" +#include "llvm/Support/PointerLikeTypeTraits.h" + +#include +#include + +namespace psr { + +namespace detail { +template +static constexpr bool IsPointerWithAtleastOneFreeLowBit = false; +template + requires requires() { llvm::PointerLikeTypeTraits::NumLowBitsAvailable; } +static constexpr bool IsPointerWithAtleastOneFreeLowBit = + llvm::PointerLikeTypeTraits::NumLowBitsAvailable > 0; +} // namespace detail + +/// The id-type for values inserted into the ValueCompressor. +enum class ValueId : uint32_t {}; + +/// A utility-class that assigns sequential integer-like ids (psr::ValueId) to +/// every inserted value. +/// +/// In contrast to psr::Compressor, the ValueCompressor can optionally +/// assign the same id to multiple values, and it can even produce ids without +/// corresponding values. +/// +/// \tparam T The type of inserted values. Expected to be mostly small and +/// trivial. +template class ValueCompressor { +public: + using value_type = T; + using id_type = ValueId; + using var_aliases_type = std::conditional_t< + detail::IsPointerWithAtleastOneFreeLowBit, llvm::TinyPtrVector, + // Note: The inline-buffer of SmallVector is pointer-aligned, so it would + // be wasteful to only allow one, e.g., int16 + llvm::SmallVector>; + + [[nodiscard]] constexpr const auto &id2vars() const noexcept + [[clang::lifetimebound]] { + return Id2Vars; + } + [[nodiscard]] constexpr const auto &id2vars(ValueId Id) const noexcept + [[clang::lifetimebound]] { + return Id2Vars[Id]; + } + [[nodiscard]] constexpr const auto &var2id() const noexcept + [[clang::lifetimebound]] { + return Var2Id; + } + + std::pair insert(ByConstRef Var) { + auto [It, Inserted] = Var2Id.try_emplace(Var, ValueId(Id2Vars.size())); + if (Inserted) { + Id2Vars.emplace_back().push_back(Var); + } + return {It->second, Inserted}; + } + + bool addAlias(ByConstRef Var, ValueId Id) { + assert(Id2Vars.inbounds(Id) && + "Can only add an alias to an already existing Id!"); + if (Var2Id.try_emplace(Var, Id).second) { + Id2Vars[Id].push_back(Var); + return true; + } + return false; + } + + [[nodiscard]] ValueId addDummy() { + auto Ret = ValueId(Id2Vars.size()); + Id2Vars.emplace_back(); + return Ret; + } + + [[nodiscard]] std::optional getOrNull(ByConstRef Var) const { + auto It = Var2Id.find(Var); + if (It == Var2Id.end()) { + return std::nullopt; + } + return It->second; + } + + void reserve(size_t Capa) { + Var2Id.reserve(Capa); + Id2Vars.reserve(Capa); + } + + [[nodiscard]] size_t size() const noexcept { return Id2Vars.size(); } + [[nodiscard]] size_t empty() const noexcept { return Id2Vars.empty(); } + +private: + llvm::DenseMap Var2Id; + TypedVector Id2Vars; +}; + +} // namespace psr + +namespace llvm { +template <> struct DenseMapInfo { + static constexpr psr::ValueId getEmptyKey() noexcept { + return psr::ValueId(UINT32_MAX); + } + static constexpr psr::ValueId getTombstoneKey() noexcept { + return psr::ValueId(UINT32_MAX - 1); + } + static inline llvm::hash_code getHashValue(psr::ValueId VId) { + return llvm::hash_value(std::underlying_type_t(VId)); + } + static constexpr bool isEqual(psr::ValueId L, psr::ValueId R) noexcept { + return L == R; + } +}; +} // namespace llvm diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp new file mode 100644 index 0000000000..0faf7b8869 --- /dev/null +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -0,0 +1,580 @@ +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" + +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/BitSet.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/BitVector.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Operator.h" + +#include + +using namespace psr; + +static_assert(detail::IsPointerWithAtleastOneFreeLowBit); + +std::string psr::to_string(PAGVariable Var) { + auto Ret = llvmIRToString(Var.get()); + if (Var.isReturnVariable()) { + Ret += "."; + } + return Ret; +} + +namespace { + +using LLVMPBStrategy = LLVMPAGBuilder::PBStrategy; + +struct GlobalCache { + const llvm::DataLayout &DL; // NOLINT + // Due to the recursion in getOrCreateGCacheEntry, we need pointer stability + std::unordered_map> + Cache{}; + + [[nodiscard]] llvm::ArrayRef getOrCreateGCacheEntry( + LLVMPBStrategy &Strategy, const llvm::Constant *Const, + std::invocable auto GetVariable) { + if (definitelyContainsNoPointer(Const)) { + return {}; + } + + auto [It, Inserted] = Cache.try_emplace(Const); + if (!Inserted) { + return It->second; + } + + auto &Vec = It->second; + + // We do not care about null here + if (llvm::isa(Const)) { + return {}; + } + + if (const auto *CGep = llvm::dyn_cast(Const)) { + // TODO: Properly handle constant GEPs + return getOrCreateGCacheEntry( + Strategy, llvm::cast(CGep->getPointerOperand()), + GetVariable); + } + + if (Const->getType()->isPointerTy()) { + Vec.push_back(GetVariable(Const, Strategy)); + + return Vec; + } + + if (!llvm::isa(Const)) { + return {}; + } + + // TODO: Get rid of the recursion + + if (const auto *Arr = llvm::dyn_cast(Const)) { + if (Arr->getType()->isArrayTy() && + definitelyContainsNoPointer(Arr->getType()->getArrayElementType())) { + return {}; + } + + size_t ArrayLen = Arr->getNumOperands(); + for (size_t I = 0; I < ArrayLen; ++I) { + auto *Elem = llvm::cast( + Arr->getAggregateElement(I)->stripPointerCastsAndAliases()); + auto ElemVars = getOrCreateGCacheEntry(Strategy, Elem, GetVariable); + Vec.append(ElemVars.begin(), ElemVars.end()); + } + return Vec; + } + + // TODO: more + + return Vec; + } +}; +} // namespace +struct LLVMPAGBuilder::PAGBuildData { + const llvm::DataLayout &DL; // NOLINT + ValueCompressor &VC; // NOLINT + + llvm::DenseMap TheOneLoad{}; + BitSet OnlyIncomingStoresAndOutgoingLoads{}; + TypedVector> IncomingStores{}; + TypedVector> OutgoingLoads{}; + llvm::DenseMap> + LocalGeps{}; + + [[nodiscard]] ValueId getVariable(PAGVariable V, LLVMPBStrategy &Strategy) { + auto [Id, Inserted] = VC.insert(V); + if (Inserted) { + OnlyIncomingStoresAndOutgoingLoads.insert(Id); + IncomingStores.emplace_back(); + OutgoingLoads.emplace_back(); + Strategy.onAddValue(V, Id); + } + + return Id; + } + + void addAllIncomingStores(PBStrategy &Strategy, ValueId To, + llvm::SmallDenseMap &Froms) { + for (auto [From, E] : Froms) { + Strategy.onAddEdge(From, To, E, nullptr); + } + Froms.clear(); + } + void addAllOutgoingLoads(PBStrategy &Strategy, ValueId From, + llvm::SmallDenseSet &Tos) { + for (auto To : Tos) { + Strategy.onAddEdge(From, To, Load{}, nullptr); + } + Tos.clear(); + } + + void eraseFromSimpleStoreLoad(PBStrategy &Strategy, ValueId VId) { + if (OnlyIncomingStoresAndOutgoingLoads.tryErase(VId)) { + addAllIncomingStores(Strategy, VId, IncomingStores[VId]); + addAllOutgoingLoads(Strategy, VId, OutgoingLoads[VId]); + } + } + + void addEdge(PBStrategy &Strategy, ValueId From, ValueId To, Edge E, + const llvm::Instruction *AtInstruction) { + // llvm::errs() << "[addEdge]: " << int(From) << "->" << int(To) + // << " :: " << to_string(E) << '\n'; + if (!E.isa()) { + eraseFromSimpleStoreLoad(Strategy, To); + } else if (OnlyIncomingStoresAndOutgoingLoads.contains(To)) { + eraseFromSimpleStoreLoad(Strategy, From); + + // We delay store edges + IncomingStores[To].try_emplace(From, E); + return; + } + + if (!E.isa()) { + eraseFromSimpleStoreLoad(Strategy, From); + } else if (OnlyIncomingStoresAndOutgoingLoads.contains(From)) { + eraseFromSimpleStoreLoad(Strategy, To); + // We delay load edges + OutgoingLoads[From].insert(To); + return; + } + + Strategy.onAddEdge(From, To, E, AtInstruction); + } + + void initializeGlobals(const LLVMProjectIRDB &IRDB, + LLVMPBStrategy &Strategy) { + GlobalCache GCache{IRDB.getModule()->getDataLayout()}; + + for (const auto &Glob : IRDB.getModule()->globals()) { + if (definitelyContainsNoPointer(Glob.getValueType())) { + continue; + } + + if (Glob.hasInitializer()) { + initializeGlobal(GCache, Strategy, Glob); + } + } + } + + void initializeGlobal(GlobalCache &GCache, LLVMPBStrategy &Strategy, + const llvm::GlobalVariable &Glob) { + auto GlobObj = getVariable(&Glob, Strategy); + auto Stores = GCache.getOrCreateGCacheEntry( + Strategy, Glob.getInitializer(), + [this](const llvm::Value *V, LLVMPBStrategy &Strategy) { + return getVariable(V, Strategy); + }); + + for (auto Src : Stores) { + // NOTE: We don't consider this a POI for now; probably, that's fine + addEdge(Strategy, Src, GlobObj, Store{}, nullptr); + } + } + + void initializeFunctions(const LLVMProjectIRDB &IRDB, PBStrategy &Strategy) { + // TODO: Use LibrarySummary here + // TODO: Skip functions that have been proven unreachable previously + + for (const auto *Fun : IRDB.getAllFunctions()) { + if (!Fun->isDeclaration()) { + initializeFun(Strategy, *Fun); + } + } + + addDelayedEdges(Strategy); + } + + void initializeFun(PBStrategy &Strategy, const llvm::Function &Fun) { + // TODO: RPO Order to profit from OTF-merged values + for (const auto &BB : Fun) { + propagateBB(Strategy, BB); + } + } + + void addDelayedEdges(PBStrategy &Strategy) { + OnlyIncomingStoresAndOutgoingLoads.foreach ([this, &Strategy](auto Val) { + auto VId = ValueId(Val); + + for (auto [IncStore, _] : IncomingStores[VId]) { + for (auto OutLoad : OutgoingLoads[VId]) { + Strategy.onAddEdge(IncStore, OutLoad, Assign{}, nullptr); + } + if (OutgoingLoads[VId].empty()) { + Strategy.onAddEdge(IncStore, VId, StorePOI{}, nullptr); + } + } + if (IncomingStores[VId].empty()) { + for (auto OutLoad : OutgoingLoads[VId]) { + Strategy.onAddEdge(VId, OutLoad, Load{}, nullptr); + } + } + }); + } + + void propagateBB(PBStrategy &Strategy, const llvm::BasicBlock &BB) { + const auto *Inst = &BB.front(); + if (Inst->isDebugOrPseudoInst()) { + Inst = Inst->getNextNonDebugInstruction(); + } + + for (; Inst; Inst = Inst->getNextNonDebugInstruction()) { + dispatch(Strategy, *Inst); + } + } + + void dispatch(PBStrategy &Strategy, const llvm::Instruction &I) { + if (const auto *Alloca = llvm::dyn_cast(&I)) { + return (void)getVariable(Alloca, Strategy); + } + + if (const auto *Store = llvm::dyn_cast(&I)) { + return handleStore(Strategy, Store); + } + + if (const auto *Load = llvm::dyn_cast(&I)) { + return handleLoad(Strategy, Load); + } + + if (const auto *Cast = llvm::dyn_cast(&I)) { + return handleCast(Strategy, Cast); + } + + if (const auto *GEP = llvm::dyn_cast(&I)) { + return handleGep(Strategy, GEP); + } + + if (const auto *MemTrn = llvm::dyn_cast(&I)) { + return handleMemTransfer(Strategy, MemTrn); + } + + if (const auto *Call = llvm::dyn_cast(&I)) { + return handleCall(Strategy, Call); + } + + if (const auto *Ret = llvm::dyn_cast(&I)) { + return handleReturn(Strategy, Ret); + } + + if (const auto *Phi = llvm::dyn_cast(&I)) { + return handlePhi(Strategy, Phi); + } + + if (const auto *Select = llvm::dyn_cast(&I)) { + return handleSelect(Strategy, Select); + } + } + + static void handleOperand(const llvm::Value *RawOp, + std::invocable auto Handler) { + RawOp = RawOp->stripPointerCastsAndAliases(); + const auto *RawOpCExpr = llvm::dyn_cast(RawOp); + if (!RawOpCExpr) [[likely]] { + // fast-path: + return (void)std::invoke(Handler, RawOp); + } + + llvm::SmallDenseSet Seen = {RawOp}; + llvm::SmallVector WL = {RawOpCExpr}; + do { + const auto *Curr = WL.pop_back_val(); + for (const auto *Op : Curr->operand_values()) { + if (definitelyContainsNoPointer(Op) || !Seen.insert(Op).second) { + continue; + } + + if (const auto *GObj = llvm::dyn_cast(Op)) { + std::invoke(Handler, GObj); + continue; + } + + // TODO: Handle constant GEP! + + if (const auto *OpUser = llvm::dyn_cast(Op)) { + WL.push_back(OpUser); + continue; + } + } + + } while (!WL.empty()); + } + + void handleStore(PBStrategy &Strategy, const llvm::StoreInst *Store) { + + if (definitelyContainsNoPointer(Store->getValueOperand())) { + return; + } + + handleOperand(Store->getPointerOperand(), [&](const auto *PointerOp) { + auto PointerObj = getVariable(PointerOp, Strategy); + handleOperand(Store->getValueOperand(), [&](const auto *ValueOp) { + auto ValueObj = getVariable(ValueOp, Strategy); + addEdge(Strategy, ValueObj, PointerObj, StorePOI{}, Store); + }); + }); + } + + void handleLoad(PBStrategy &Strategy, const llvm::LoadInst *Ld) { + if (definitelyContainsNoPointer(Ld)) { + return; + } + + handleOperand(Ld->getPointerOperand(), [&](const auto *PointerOp) { + auto PointerObj = getVariable(PointerOp, Strategy); + if (llvm::isa(PointerOp)) { + auto [It, Inserted] = TheOneLoad.try_emplace(PointerObj, ValueId{}); + + if (!Inserted) { + VC.addAlias(Ld, It->second); + return; + } + + auto LoadObj = getVariable(Ld, Strategy); + It->second = LoadObj; + addEdge(Strategy, PointerObj, LoadObj, Load{}, Ld); + return; + } + + auto LoadObj = getVariable(Ld, Strategy); + addEdge(Strategy, PointerObj, LoadObj, Load{}, Ld); + }); + } + + void handleCastImpl(PBStrategy &Strategy, const llvm::User *Cast, + const llvm::Value *Op) { + auto OperandObj = getVariable(Op, Strategy); + + if (llvm::isa(Cast)) { + if (const auto *LocalGeps = psr::getOrNull(this->LocalGeps, Cast)) { + for (auto [_, Gep] : *LocalGeps) { + addEdge(Strategy, OperandObj, Gep, Assign{}, nullptr); + } + return; + } + } + + VC.addAlias(Cast, OperandObj); + } + + void handleCast(PBStrategy &Strategy, const llvm::User *Cast) { + if (definitelyContainsNoPointer(Cast)) { + return; + } + + handleOperand(Cast->getOperand(0), + [&](const auto *Op) { handleCastImpl(Strategy, Cast, Op); }); + } + void handleGep(PBStrategy &Strategy, const llvm::GEPOperator *Gep) { + handleOperand(Gep->getPointerOperand(), [&](const auto *PointerOp) { + if (psr::isAllocaInstOrHeapAllocaFunction(PointerOp) && + !isAddressTakenVariable(PointerOp) && Gep->hasAllConstantIndices() && + !Gep->hasAllZeroIndices()) { + llvm::APInt Offset(64, 0); + if (Gep->accumulateConstantOffset(DL, Offset)) { + auto [It, Inserted] = + LocalGeps[PointerOp].try_emplace(Offset.getSExtValue()); + if (!Inserted) { + VC.addAlias(Gep, It->second); + return; + } + + auto GepObj = getVariable(Gep, Strategy); + It->second = GepObj; + // Do not add an edge! Geps should be decoupled! + return; + } + } + + handleCastImpl(Strategy, Gep, PointerOp); + }); + } + + void handleMemTransfer(PBStrategy &Strategy, + const llvm::MemTransferInst *MemTrn) { + + handleOperand(MemTrn->getDest(), [&](const auto *Dest) { + auto DestObj = getVariable(Dest, Strategy); + + handleOperand(MemTrn->getSource(), [&](const auto *Src) { + auto SourceObj = getVariable(Src, Strategy); + addEdge(Strategy, SourceObj, DestObj, Copy{}, MemTrn); + + const auto &DestGeps = getOrDefault(LocalGeps, Dest); + for (const auto &[Offset, SrcGep] : getOrDefault(LocalGeps, Src)) { + if (const auto *DestGep = getOrNull(DestGeps, Offset)) { + addEdge(Strategy, SrcGep, *DestGep, Copy{}, MemTrn); + } else { + addEdge(Strategy, SrcGep, DestObj, Copy{}, MemTrn); + } + } + }); + }); + } + + void handleCallTarget(PBStrategy &Strategy, const llvm::CallBase *Call, + const llvm::Function *Callee, + llvm::ArrayRef> Args, + std::optional CSVal) { + // TODO: Handle library summaries here! + if (Callee->isDeclaration()) { + return; + } + + if (CSVal) { + auto RetObj = getVariable(PAGVariable::Return{Callee}, Strategy); + addEdge(Strategy, RetObj, *CSVal, Return{}, Call); + } + + for (const auto &[Param, Arg] : llvm::zip(Callee->args(), Args)) { + if (Arg.empty()) { + continue; + } + + // XXX: Check, whether Arg is an address-taken variable. Then we can + // differentiate between Call and CallPOI; not relevant for now, though, + // because we ignore call-pois + auto ParamObj = getVariable(&Param, Strategy); + for (auto ArgVal : Arg) { + addEdge(Strategy, ArgVal, ParamObj, + LLVMPAGBuilder::Call{uint16_t(Param.getArgNo())}, Call); + } + } + + // TODO: Varargs + } + + void handleCall(PBStrategy &Strategy, const llvm::CallBase *Call) { + const auto *FnPtr = Call->getCalledOperand()->stripPointerCastsAndAliases(); + + llvm::SmallVector> Args; + for (const auto &Arg : Call->args()) { + auto &ArgVal = Args.emplace_back(); + if (definitelyContainsNoPointer(Arg)) { + continue; + } + + handleOperand(Arg.get(), [&](const auto *ArgOp) { + ArgVal.insert(getVariable(ArgOp, Strategy)); + }); + } + std::optional CSVal; + if (Call->getType()->isPointerTy()) { + CSVal = getVariable(Call, Strategy); + } + + if (const auto *Callee = llvm::dyn_cast(FnPtr)) { + return handleCallTarget(Strategy, Call, Callee, Args, CSVal); + } + + Strategy.withCalleesOfCallAt(Call, [&](const auto *Callee) { + handleCallTarget(Strategy, Call, Callee, Args, CSVal); + }); + } + + void handleReturn(PBStrategy &Strategy, const llvm::ReturnInst *Ret) { + const auto *RetVal = Ret->getReturnValue(); + if (!RetVal || definitelyContainsNoPointer(RetVal)) { + return; + } + + auto RetObj = + getVariable(PAGVariable::Return{Ret->getFunction()}, Strategy); + handleOperand(RetVal, [&](const auto *ExitOp) { + auto ExitObj = getVariable(ExitOp, Strategy); + addEdge(Strategy, ExitObj, RetObj, Assign{}, Ret); + }); + } + + void handlePhi(PBStrategy &Strategy, const llvm::PHINode *Phi) { + if (definitelyContainsNoPointer(Phi)) { + return; + } + + auto PhiObj = getVariable(Phi, Strategy); + + for (const auto &Inc : Phi->incoming_values()) { + if (definitelyContainsNoPointer(Inc)) { + continue; + } + + // Here, we should make sure that all incoming BBs are already handled... + + handleOperand(Inc.get(), [&](const auto *IncOp) { + auto IncObj = getVariable(IncOp, Strategy); + addEdge(Strategy, IncObj, PhiObj, Assign{}, Phi); + }); + } + } + + void handleSelect(PBStrategy &Strategy, const llvm::SelectInst *Select) { + if (definitelyContainsNoPointer(Select)) { + return; + } + + const auto *TrueVal = Select->getTrueValue()->stripPointerCastsAndAliases(); + const auto *FalseVal = + Select->getFalseValue()->stripPointerCastsAndAliases(); + + auto SelectObj = getVariable(Select, Strategy); + // In the union-find, these are all merged; maybe be a bit smarter here... + + if (!definitelyContainsNoPointer(TrueVal)) { + handleOperand(TrueVal, [&](const auto *TrueOp) { + auto TrueObj = getVariable(TrueOp, Strategy); + addEdge(Strategy, TrueObj, SelectObj, Assign{}, Select); + }); + } + if (!definitelyContainsNoPointer(FalseVal)) { + handleOperand(FalseVal, [&](const auto *FalseOp) { + auto TrueObj = getVariable(FalseOp, Strategy); + addEdge(Strategy, TrueObj, SelectObj, Assign{}, Select); + }); + } + } +}; + +void psr::LLVMPAGBuilder::buildPAG(const LLVMProjectIRDB &IRDB, + ValueCompressor &VC, + PBStrategy &Strategy) { + PAGBuildData BData{IRDB.getModule()->getDataLayout(), VC}; + + const size_t NumPossibleValues = Strategy.getNumPossibleValues(IRDB); + const size_t NumPresentValues = VC.size(); + + BData.IncomingStores.reserve(NumPossibleValues); + BData.IncomingStores.resize(NumPresentValues); + + BData.OutgoingLoads.reserve(NumPossibleValues); + BData.OutgoingLoads.resize(NumPresentValues); + + BData.OnlyIncomingStoresAndOutgoingLoads.reserve(NumPossibleValues); + + BData.initializeGlobals(IRDB, Strategy); + BData.initializeFunctions(IRDB, Strategy); +} diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index 3af67f9d8d..6f67426f2a 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -34,6 +34,7 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/IR/ModuleSlotTracker.h" +#include "llvm/IR/Operator.h" #include "llvm/IR/Value.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" @@ -165,6 +166,9 @@ std::string psr::llvmIRToString(const llvm::Value *V) { if (const auto *F = llvm::dyn_cast(V)) { return "fun @" + F->getName().str(); } + if (const auto *BB = llvm::dyn_cast(V)) { + return "block " + BB->getName().str(); + } std::string IRBuffer; llvm::raw_string_ostream RSO(IRBuffer); @@ -493,6 +497,61 @@ bool psr::isGuardVariable(const llvm::Value *V) { return false; } +static bool isAllocationSiteOrSimilar(const llvm::Value *V) { + if (const auto *Arg = llvm::dyn_cast(V)) { + return Arg->hasStructRetAttr(); + } + return isAllocaInstOrHeapAllocaFunction(V); +} + +bool psr::isAddressTakenVariable(const llvm::Value *Var) noexcept { + if (!isAllocationSiteOrSimilar(Var)) { + return true; + } + + llvm::SmallVector WL = {Var}; + llvm::SmallDenseSet Seen = {Var}; + + while (!WL.empty()) { + const auto *CurrVal = WL.pop_back_val(); + + for (const auto &Use : CurrVal->uses()) { + if (llvm::isa(Use.getUser())) { + continue; + } + if (const auto *Store = llvm::dyn_cast(Use.getUser())) { + if (Store->getValueOperand() == Use) { + continue; + } + } else if (llvm::isa(Use.getUser())) { + // Approximation! + continue; + } else if (llvm::isa(Use.getUser())) { + if (!CurrVal->getType()->isPointerTy()) { + return true; // ptrtoint + } + + if (!Seen.insert(Use.getUser()).second) { + WL.push_back(Use.getUser()); + } + + continue; + } else if (const auto *GEP = + llvm::dyn_cast(Use.getUser())) { + if (Use == GEP->getPointerOperand() && !Seen.insert(GEP).second) { + WL.push_back(GEP); + } + + continue; + } + + return true; + } + } + + return false; +} + bool psr::isStaticVariableLazyInitializationBranch( const llvm::BranchInst *Inst) { if (Inst->isUnconditional()) { From d94d8c09254eb5ee0520a53e2beb761898b30ef8 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Mon, 29 Dec 2025 15:14:40 +0100 Subject: [PATCH 02/75] Add UnionFind + minor --- .../Pointer/LLVMPointerAssignmentGraph.h | 2 + include/phasar/Utils/UnionFind.h | 56 +++++++++++++++++++ .../Pointer/LLVMPointerAssignmentGraph.cpp | 7 +-- 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 include/phasar/Utils/UnionFind.h diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h index 33f24b6ee8..5cfe8000a1 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h @@ -85,7 +85,9 @@ struct LLVMPAGDomain : LLVMAnalysisDomainDefault { }; class LLVMPAGBuilder : public PAGBuilder { +private: struct PAGBuildData; + void buildPAG(const LLVMProjectIRDB &IRDB, ValueCompressor &VC, PBStrategy &Strategy) override; }; diff --git a/include/phasar/Utils/UnionFind.h b/include/phasar/Utils/UnionFind.h new file mode 100644 index 0000000000..66b5539f8e --- /dev/null +++ b/include/phasar/Utils/UnionFind.h @@ -0,0 +1,56 @@ +#pragma once + +#include "llvm/ADT/IntEqClasses.h" + +namespace psr { + +template +class CompressedUnionFind; + +template class UnionFind { +public: + UnionFind() noexcept = default; + explicit UnionFind(size_t InitSz) : Equiv(InitSz) {} + + IdT join(IdT L, IdT R) { return IdT(Equiv.join(unsigned(L), unsigned(R))); } + + [[nodiscard]] IdT find(IdT Val) const { + return IdT(Equiv.findLeader(unsigned(Val))); + } + + void grow(size_t NewSz) { Equiv.grow(NewSz); } + + template + [[nodiscard]] CompressedUnionFind compress() &&; + +private: + llvm::IntEqClasses Equiv; +}; + +template class CompressedUnionFind { +public: + [[nodiscard]] size_t size() const noexcept { return Equiv.getNumClasses(); } + + [[nodiscard]] bool inbounds(IdT Id) const noexcept { + return size_t(Id) < size(); + } + + [[nodiscard]] MappedIdT operator[](IdT Id) const { + assert(inbounds(Id)); + return MappedIdT(Equiv[unsigned(Id)]); + } + +private: + friend UnionFind; + CompressedUnionFind(llvm::IntEqClasses &&Equiv) : Equiv(std::move(Equiv)) {} + + llvm::IntEqClasses Equiv; +}; + +template +template +inline CompressedUnionFind UnionFind::compress() && { + Equiv.compress(); + return {std::move(Equiv)}; +} +} // namespace psr diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 0faf7b8869..084ca8b99f 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -97,7 +97,8 @@ struct GlobalCache { } }; } // namespace -struct LLVMPAGBuilder::PAGBuildData { + +struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { const llvm::DataLayout &DL; // NOLINT ValueCompressor &VC; // NOLINT @@ -219,9 +220,7 @@ struct LLVMPAGBuilder::PAGBuildData { } void addDelayedEdges(PBStrategy &Strategy) { - OnlyIncomingStoresAndOutgoingLoads.foreach ([this, &Strategy](auto Val) { - auto VId = ValueId(Val); - + OnlyIncomingStoresAndOutgoingLoads.foreach ([this, &Strategy](auto VId) { for (auto [IncStore, _] : IncomingStores[VId]) { for (auto OutLoad : OutgoingLoads[VId]) { Strategy.onAddEdge(IncStore, OutLoad, Assign{}, nullptr); From 2cb583dd7fc7955b5985d693b417caec58e425d6 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Tue, 30 Dec 2025 19:01:53 +0100 Subject: [PATCH 03/75] Optional type-erasure for PBStrategy + add PBStrategyCombinator --- .../Pointer/LLVMPointerAssignmentGraph.h | 2 +- .../phasar/Pointer/PointerAssignmentGraph.h | 369 +++++++++++++----- .../Pointer/LLVMPointerAssignmentGraph.cpp | 62 +-- 3 files changed, 294 insertions(+), 139 deletions(-) diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h index 5cfe8000a1..b7b21bed8c 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h @@ -89,7 +89,7 @@ class LLVMPAGBuilder : public PAGBuilder { struct PAGBuildData; void buildPAG(const LLVMProjectIRDB &IRDB, ValueCompressor &VC, - PBStrategy &Strategy) override; + pag::PBStrategyRef Strategy) override; }; } // namespace psr diff --git a/include/phasar/Pointer/PointerAssignmentGraph.h b/include/phasar/Pointer/PointerAssignmentGraph.h index 1d3231c19a..4bf3f66c84 100644 --- a/include/phasar/Pointer/PointerAssignmentGraph.h +++ b/include/phasar/Pointer/PointerAssignmentGraph.h @@ -7,139 +7,292 @@ #include "phasar/Utils/Utilities.h" #include "phasar/Utils/ValueCompressor.h" +#include #include +#include #include +#include #include namespace psr { -/// A utility-class that can be used to build a pointer-assignment graph (PAG). -/// -/// This class does not enforce a specifiy graph-layout -- it does not even -/// require that you explicitly store a graph in memory. Instead, you get -/// notified about every node and edge that should be created in the PAG. -/// -/// \tparam AnalysisDomainT The analysis domain to use. -template class PAGBuilder { +namespace pag { +struct Assign {}; +struct Gep { + std::optional Offset; +}; +struct Call { + uint16_t ArgNo; +}; +struct Return {}; +struct Load {}; +struct Store {}; +struct StorePOI {}; +struct Copy {}; + +struct Edge : public std::variant { + using Base = + std::variant; + using std::variant::variant; + + template static constexpr size_t kindOf() noexcept { + return psr::variant_idx; + } + + [[nodiscard]] constexpr size_t kind() const noexcept { return this->index(); } + + template + [[nodiscard]] constexpr bool isa() const noexcept { + return std::holds_alternative(*this) || + (... || std::holds_alternative(*this)); + } + + template + [[nodiscard]] constexpr std::optional dyn_cast() const noexcept { + if (const auto *Ptr = std::get_if(this)) { + return *Ptr; + } + + return std::nullopt; + } + + template + constexpr decltype(auto) apply(HandlerFn &&Handler) const { + return std::visit(PSR_FWD(Handler), *this); + } + + [[nodiscard]] friend constexpr llvm::StringRef to_string(Edge E) noexcept { + return E.apply(psr::Overloaded{ + [](Assign) { return "Assign"; }, + // TODO: Print offset, if present + [](Gep) { return "Gep"; }, + // TODO: Print ArgNo + [](Call) { return "Call"; }, + [](Return) { return "Return"; }, + [](Load) { return "Load"; }, + [](Store) { return "Store"; }, + [](StorePOI) { return "StorePOI"; }, + [](Copy) { return "Copy"; }, + }); + } +}; + +template +concept PBStrategy = + requires(T &Strategy, const T &CStrategy, ValueId From, ValueId To, + const typename T::n_t &Inst, pag::Edge E) { + typename T::v_t; + typename T::db_t; + typename T::f_t; + typename T::n_t; + + { T() } noexcept; + Strategy.onAddEdge(From, To, E, Nullable{}); + CStrategy.withCalleesOfCallAt(Inst, [](typename T::f_t Callee) {}); + }; + +template +concept CanOnAddValue = requires(T &Strategy, const T::v_t &Var) { + Strategy.onAddValue(Var, ValueId{}); +}; + +template +concept CanGetNumPossibleValues = + requires(const T &CStrategy, const T::db_t &IRDB) { + { + CStrategy.getNumPossibleValues(IRDB) + } noexcept -> std::convertible_to; + }; + +template + requires(std::same_as && + std::same_as && + std::same_as && + std::same_as) +struct PBStrategyCombinator { + using n_t = typename FirstT::n_t; + using v_t = typename FirstT::v_t; + using f_t = typename FirstT::f_t; + using db_t = typename FirstT::db_t; + + [[no_unique_address]] FirstT First; + [[no_unique_address]] SecondT Second; + + constexpr void onAddEdge(ValueId From, ValueId To, Edge E, + Nullable AtInstruction) { + First.onAddEdge(From, To, E, AtInstruction); + Second.onAddEdge(From, To, E, AtInstruction); + } + + constexpr void onAddValue(ByConstRef Variable, ValueId VId) + requires(CanOnAddValue || CanOnAddValue) + { + if constexpr (CanOnAddValue) { + First.onAddValue(Variable, VId); + } + if constexpr (CanOnAddValue) { + Second.onAddValue(Variable, VId); + } + } + + [[nodiscard]] constexpr size_t + getNumPossibleValues(const db_t &IRDB) const noexcept + requires(CanGetNumPossibleValues || + CanGetNumPossibleValues) + { + if constexpr (CanGetNumPossibleValues) { + return Second.getNumPossibleValues(IRDB); + } + if constexpr (CanGetNumPossibleValues) { + return First.getNumPossibleValues(IRDB); + } + } + + constexpr void + withCalleesOfCallAt(ByConstRef CS, + llvm::function_ref WithCallee) const { + Second.withCalleesOfCallAt(CS, WithCallee); + } +}; + +/// The main customization point for PAG building. +/// Implement this interface to provide several callbacks to the PAGBuilder, +/// specifying how the PAG should be built. +template class PBStrategyRef final { public: using n_t = typename AnalysisDomainT::n_t; using v_t = typename AnalysisDomainT::v_t; using f_t = typename AnalysisDomainT::f_t; using db_t = typename AnalysisDomainT::db_t; - struct Assign {}; - struct Gep { - std::optional Offset; - }; - struct Call { - uint16_t ArgNo; - }; - struct Return {}; - struct Load {}; - struct Store {}; - struct StorePOI {}; - struct Copy {}; - - struct Edge : public std::variant { - using Base = - std::variant; - using std::variant::variant; - - template static constexpr size_t kindOf() noexcept { - return psr::variant_idx; - } - - [[nodiscard]] constexpr size_t kind() const noexcept { - return this->index(); - } + /// Creates a type-erased PBStrategyRef from a non-null pointer to a concrete + /// Strategy-object. + template + requires(!same_as_decay) + constexpr PBStrategyRef(ConcreteStrategyT *Strategy) noexcept + : VT(&VTableFor), Ctx(Strategy) { + assert(Strategy != nullptr); + } - template - [[nodiscard]] constexpr bool isa() const noexcept { - return std::holds_alternative(*this) || - (... || std::holds_alternative(*this)); - } + /// Called by buildPAG() for every (unique) edge that should be added to the + /// PAG + /// + /// \param From The source-node. + /// \param To The destination-node. + /// \param E The edge-label that specifies the kind of data-flow that is + /// denoted by this edge. + /// \param AtInstruction Optionally holds the instruction that caused this + /// edge to be created. + void onAddEdge(ValueId From, ValueId To, pag::Edge E, + Nullable AtInstruction) { + VT->OnAddEdge(Ctx, From, To, E, AtInstruction); + } - template - [[nodiscard]] constexpr std::optional dyn_cast() const noexcept { - if (const auto *Ptr = std::get_if(this)) { - return *Ptr; - } + /// Called by buildPAG() for every (unique) node that should be added to the + /// PAG, *excluding* nodes that have already been registered in the used + /// ValueCompressor before buildPAG() was called. + /// + /// \param Variable The IR-specific variable/value for which the new node + /// has been created. + /// \param VId The value-id of the newly created node. + void onAddValue(ByConstRef Variable, ValueId VId) { + VT->OnAddValue(Ctx, Variable, VId); + } - return std::nullopt; - } + /// Estimates the maximum number of values created in the ValueCompressor. + /// Must not be exact, this is just for pre-allocating buffers for + /// optimization purposes. + [[nodiscard]] size_t getNumPossibleValues(const db_t &IRDB) const noexcept { + return VT->GetNumPossibleValues(Ctx, IRDB); + } - template - constexpr decltype(auto) apply(HandlerFn &&Handler) const { - return std::visit(PSR_FWD(Handler), *this); - } + /// Invokes a call-back with each function that may be called at the given + /// call-site. Usually, you want to use a CallGraph or a Resolver to implement + /// this function. + /// + /// \param CS The call-site. + /// \param WithCallee The callback to be invoked for each callee. + void withCalleesOfCallAt(ByConstRef CS, + llvm::function_ref WithCallee) const { + VT->WithCalleesOfCallAt(Ctx, CS, WithCallee); + } - [[nodiscard]] friend constexpr llvm::StringRef to_string(Edge E) noexcept { - return E.apply(psr::Overloaded{ - [](Assign) { return "Assign"; }, - // TODO: Print offset, if present - [](Gep) { return "Gep"; }, - // TODO: Print ArgNo - [](Call) { return "Call"; }, - [](Return) { return "Return"; }, - [](Load) { return "Load"; }, - [](Store) { return "Store"; }, - [](StorePOI) { return "StorePOI"; }, - [](Copy) { return "Copy"; }, - }); - } +private: + struct VTable { + void (*OnAddEdge)(void *Ctx, ValueId From, ValueId To, pag::Edge E, + Nullable AtInstruction); + void (*OnAddValue)(void *Ctx, ByConstRef Variable, ValueId VId); + size_t (*GetNumPossibleValues)(const void *Ctx, const db_t &IRDB) noexcept; + void (*WithCalleesOfCallAt)(const void *Ctx, ByConstRef CS, + llvm::function_ref WithCallee); }; - /// The main customization point for PAG building. - /// Implement this interface to provide several callbacks to the PAGBuilder, - /// specifying how the PAG should be built. - struct PBStrategy { - constexpr PBStrategy() noexcept = default; - virtual ~PBStrategy() = default; - - /// Called by buildPAG() for every (unique) edge that should be added to the - /// PAG - /// - /// \param From The source-node. - /// \param To The destination-node. - /// \param E The edge-label that specifies the kind of data-flow that is - /// denoted by this edge. - /// \param AtInstruction Optionally holds the instruction that caused this - /// edge to be created. - virtual void onAddEdge(ValueId From, ValueId To, Edge E, - Nullable AtInstruction) = 0; - - /// Called by buildPAG() for every (unique) node that should be added to the - /// PAG, *excluding* nodes that have already been registered in the used - /// ValueCompressor before buildPAG() was called. - /// - /// \param Variable The IR-specific variable/value for which the new node - /// has been created. - /// \param VId The value-id of the newly created node. - virtual void onAddValue(ByConstRef Variable, ValueId VId) { - // Do things like allocating a new slot in the adjacency-list here + template + static void onAddEdgeThunk(void *Ctx, ValueId From, ValueId To, pag::Edge E, + Nullable AtInstruction) { + // Do things like allocating a new slot in the adjacency-list here + static_cast(Ctx)->onAddEdge(From, To, E, + AtInstruction); + } + + template + static void onAddValueThunk(void *Ctx, ByConstRef Variable, + ValueId VId) { + if constexpr (CanOnAddValue) { + static_cast(Ctx)->onAddValue(Variable, VId); } + } - /// Estimates the maximum number of values created in the ValueCompressor. - /// Must not be exact, this is just for pre-allocating buffers for - /// optimization purposes. - [[nodiscard]] virtual size_t - getNumPossibleValues(const db_t &IRDB) const noexcept { - // May use a different heuristic here... + template + static void getNumPossibleValuesThunk(const void *Ctx, + const db_t &IRDB) noexcept { + if constexpr (CanGetNumPossibleValues) { + return static_cast(Ctx)->getNumPossibleValues( + IRDB); + } else { + // Fallback-heuristic return IRDB.getNumInstructions(); } + } + + template + static void + withCalleesOfCallAtThunk(const void *Ctx, ByConstRef CS, + llvm::function_ref WithCallee) { - /// Invokes a call-back with each function that may be called at the given - /// call-site. Usually, you want to use a CallGraph or a Resolver here. - /// - /// \param CS The call-site. - /// \param WithCallee The callback to be invoked for each callee. - virtual void - withCalleesOfCallAt(ByConstRef CS, - llvm::function_ref WithCallee) const = 0; + static_cast(Ctx)->withCalleesOfCallAt( + CS, WithCallee); + } + + template + static constexpr VTable VTableFor = { + &onAddEdgeThunk, + &onAddValueThunk, + &getNumPossibleValuesThunk, + &withCalleesOfCallAtThunk, }; + const VTable *VT{}; + void *Ctx{}; +}; +} // namespace pag + +/// A utility-class that can be used to build a pointer-assignment graph (PAG). +/// +/// This class does not enforce a specifiy graph-layout -- it does not even +/// require that you explicitly store a graph in memory. Instead, you get +/// notified about every node and edge that should be created in the PAG. +/// +/// \tparam AnalysisDomainT The analysis domain to use. +template class PAGBuilder { +public: + using n_t = typename AnalysisDomainT::n_t; + using v_t = typename AnalysisDomainT::v_t; + using f_t = typename AnalysisDomainT::f_t; + using db_t = typename AnalysisDomainT::db_t; + constexpr PAGBuilder() noexcept = default; virtual ~PAGBuilder() = default; @@ -154,7 +307,7 @@ template class PAGBuilder { /// \param Strategy The customization-point for this function. See the docs of /// PBStrategy for more information. virtual void buildPAG(const db_t &IRDB, ValueCompressor &VC, - PBStrategy &Strategy) = 0; + pag::PBStrategyRef Strategy) = 0; }; } // namespace psr diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 084ca8b99f..11ec507f91 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -16,6 +16,8 @@ #include using namespace psr; +using namespace psr::pag; +using LLVMPBStrategyRef = pag::PBStrategyRef; static_assert(detail::IsPointerWithAtleastOneFreeLowBit); @@ -29,8 +31,6 @@ std::string psr::to_string(PAGVariable Var) { namespace { -using LLVMPBStrategy = LLVMPAGBuilder::PBStrategy; - struct GlobalCache { const llvm::DataLayout &DL; // NOLINT // Due to the recursion in getOrCreateGCacheEntry, we need pointer stability @@ -38,8 +38,8 @@ struct GlobalCache { Cache{}; [[nodiscard]] llvm::ArrayRef getOrCreateGCacheEntry( - LLVMPBStrategy &Strategy, const llvm::Constant *Const, - std::invocable auto GetVariable) { + LLVMPBStrategyRef Strategy, const llvm::Constant *Const, + std::invocable auto GetVariable) { if (definitelyContainsNoPointer(Const)) { return {}; } @@ -109,7 +109,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { llvm::DenseMap> LocalGeps{}; - [[nodiscard]] ValueId getVariable(PAGVariable V, LLVMPBStrategy &Strategy) { + [[nodiscard]] ValueId getVariable(PAGVariable V, LLVMPBStrategyRef Strategy) { auto [Id, Inserted] = VC.insert(V); if (Inserted) { OnlyIncomingStoresAndOutgoingLoads.insert(Id); @@ -121,14 +121,14 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { return Id; } - void addAllIncomingStores(PBStrategy &Strategy, ValueId To, + void addAllIncomingStores(LLVMPBStrategyRef Strategy, ValueId To, llvm::SmallDenseMap &Froms) { for (auto [From, E] : Froms) { Strategy.onAddEdge(From, To, E, nullptr); } Froms.clear(); } - void addAllOutgoingLoads(PBStrategy &Strategy, ValueId From, + void addAllOutgoingLoads(LLVMPBStrategyRef Strategy, ValueId From, llvm::SmallDenseSet &Tos) { for (auto To : Tos) { Strategy.onAddEdge(From, To, Load{}, nullptr); @@ -136,14 +136,14 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { Tos.clear(); } - void eraseFromSimpleStoreLoad(PBStrategy &Strategy, ValueId VId) { + void eraseFromSimpleStoreLoad(LLVMPBStrategyRef Strategy, ValueId VId) { if (OnlyIncomingStoresAndOutgoingLoads.tryErase(VId)) { addAllIncomingStores(Strategy, VId, IncomingStores[VId]); addAllOutgoingLoads(Strategy, VId, OutgoingLoads[VId]); } } - void addEdge(PBStrategy &Strategy, ValueId From, ValueId To, Edge E, + void addEdge(LLVMPBStrategyRef Strategy, ValueId From, ValueId To, Edge E, const llvm::Instruction *AtInstruction) { // llvm::errs() << "[addEdge]: " << int(From) << "->" << int(To) // << " :: " << to_string(E) << '\n'; @@ -170,7 +170,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } void initializeGlobals(const LLVMProjectIRDB &IRDB, - LLVMPBStrategy &Strategy) { + LLVMPBStrategyRef Strategy) { GlobalCache GCache{IRDB.getModule()->getDataLayout()}; for (const auto &Glob : IRDB.getModule()->globals()) { @@ -184,12 +184,12 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } } - void initializeGlobal(GlobalCache &GCache, LLVMPBStrategy &Strategy, + void initializeGlobal(GlobalCache &GCache, LLVMPBStrategyRef Strategy, const llvm::GlobalVariable &Glob) { auto GlobObj = getVariable(&Glob, Strategy); auto Stores = GCache.getOrCreateGCacheEntry( Strategy, Glob.getInitializer(), - [this](const llvm::Value *V, LLVMPBStrategy &Strategy) { + [this](const llvm::Value *V, LLVMPBStrategyRef Strategy) { return getVariable(V, Strategy); }); @@ -199,7 +199,8 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } } - void initializeFunctions(const LLVMProjectIRDB &IRDB, PBStrategy &Strategy) { + void initializeFunctions(const LLVMProjectIRDB &IRDB, + LLVMPBStrategyRef Strategy) { // TODO: Use LibrarySummary here // TODO: Skip functions that have been proven unreachable previously @@ -212,14 +213,14 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { addDelayedEdges(Strategy); } - void initializeFun(PBStrategy &Strategy, const llvm::Function &Fun) { + void initializeFun(LLVMPBStrategyRef Strategy, const llvm::Function &Fun) { // TODO: RPO Order to profit from OTF-merged values for (const auto &BB : Fun) { propagateBB(Strategy, BB); } } - void addDelayedEdges(PBStrategy &Strategy) { + void addDelayedEdges(LLVMPBStrategyRef Strategy) { OnlyIncomingStoresAndOutgoingLoads.foreach ([this, &Strategy](auto VId) { for (auto [IncStore, _] : IncomingStores[VId]) { for (auto OutLoad : OutgoingLoads[VId]) { @@ -237,7 +238,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void propagateBB(PBStrategy &Strategy, const llvm::BasicBlock &BB) { + void propagateBB(LLVMPBStrategyRef Strategy, const llvm::BasicBlock &BB) { const auto *Inst = &BB.front(); if (Inst->isDebugOrPseudoInst()) { Inst = Inst->getNextNonDebugInstruction(); @@ -248,7 +249,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } } - void dispatch(PBStrategy &Strategy, const llvm::Instruction &I) { + void dispatch(LLVMPBStrategyRef Strategy, const llvm::Instruction &I) { if (const auto *Alloca = llvm::dyn_cast(&I)) { return (void)getVariable(Alloca, Strategy); } @@ -324,7 +325,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } while (!WL.empty()); } - void handleStore(PBStrategy &Strategy, const llvm::StoreInst *Store) { + void handleStore(LLVMPBStrategyRef Strategy, const llvm::StoreInst *Store) { if (definitelyContainsNoPointer(Store->getValueOperand())) { return; @@ -339,7 +340,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void handleLoad(PBStrategy &Strategy, const llvm::LoadInst *Ld) { + void handleLoad(LLVMPBStrategyRef Strategy, const llvm::LoadInst *Ld) { if (definitelyContainsNoPointer(Ld)) { return; } @@ -365,7 +366,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void handleCastImpl(PBStrategy &Strategy, const llvm::User *Cast, + void handleCastImpl(LLVMPBStrategyRef Strategy, const llvm::User *Cast, const llvm::Value *Op) { auto OperandObj = getVariable(Op, Strategy); @@ -381,7 +382,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { VC.addAlias(Cast, OperandObj); } - void handleCast(PBStrategy &Strategy, const llvm::User *Cast) { + void handleCast(LLVMPBStrategyRef Strategy, const llvm::User *Cast) { if (definitelyContainsNoPointer(Cast)) { return; } @@ -389,7 +390,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { handleOperand(Cast->getOperand(0), [&](const auto *Op) { handleCastImpl(Strategy, Cast, Op); }); } - void handleGep(PBStrategy &Strategy, const llvm::GEPOperator *Gep) { + void handleGep(LLVMPBStrategyRef Strategy, const llvm::GEPOperator *Gep) { handleOperand(Gep->getPointerOperand(), [&](const auto *PointerOp) { if (psr::isAllocaInstOrHeapAllocaFunction(PointerOp) && !isAddressTakenVariable(PointerOp) && Gep->hasAllConstantIndices() && @@ -414,7 +415,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void handleMemTransfer(PBStrategy &Strategy, + void handleMemTransfer(LLVMPBStrategyRef Strategy, const llvm::MemTransferInst *MemTrn) { handleOperand(MemTrn->getDest(), [&](const auto *Dest) { @@ -436,7 +437,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void handleCallTarget(PBStrategy &Strategy, const llvm::CallBase *Call, + void handleCallTarget(LLVMPBStrategyRef Strategy, const llvm::CallBase *Call, const llvm::Function *Callee, llvm::ArrayRef> Args, std::optional CSVal) { @@ -461,14 +462,14 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { auto ParamObj = getVariable(&Param, Strategy); for (auto ArgVal : Arg) { addEdge(Strategy, ArgVal, ParamObj, - LLVMPAGBuilder::Call{uint16_t(Param.getArgNo())}, Call); + pag::Call{uint16_t(Param.getArgNo())}, Call); } } // TODO: Varargs } - void handleCall(PBStrategy &Strategy, const llvm::CallBase *Call) { + void handleCall(LLVMPBStrategyRef Strategy, const llvm::CallBase *Call) { const auto *FnPtr = Call->getCalledOperand()->stripPointerCastsAndAliases(); llvm::SmallVector> Args; @@ -496,7 +497,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void handleReturn(PBStrategy &Strategy, const llvm::ReturnInst *Ret) { + void handleReturn(LLVMPBStrategyRef Strategy, const llvm::ReturnInst *Ret) { const auto *RetVal = Ret->getReturnValue(); if (!RetVal || definitelyContainsNoPointer(RetVal)) { return; @@ -510,7 +511,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { }); } - void handlePhi(PBStrategy &Strategy, const llvm::PHINode *Phi) { + void handlePhi(LLVMPBStrategyRef Strategy, const llvm::PHINode *Phi) { if (definitelyContainsNoPointer(Phi)) { return; } @@ -531,7 +532,8 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } } - void handleSelect(PBStrategy &Strategy, const llvm::SelectInst *Select) { + void handleSelect(LLVMPBStrategyRef Strategy, + const llvm::SelectInst *Select) { if (definitelyContainsNoPointer(Select)) { return; } @@ -560,7 +562,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { void psr::LLVMPAGBuilder::buildPAG(const LLVMProjectIRDB &IRDB, ValueCompressor &VC, - PBStrategy &Strategy) { + LLVMPBStrategyRef Strategy) { PAGBuildData BData{IRDB.getModule()->getDataLayout(), VC}; const size_t NumPossibleValues = Strategy.getNumPossibleValues(IRDB); From ebe4ffaf937af00aeef2c8cdd95ff21673596486 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Fri, 2 Jan 2026 12:42:28 +0100 Subject: [PATCH 04/75] Add basic union-find based Steensgaard-style alias analysis (WIP) --- .../ControlFlow/VTA/TypePropagator.h | 2 +- .../Pointer/CallingContextConstructor.h | 148 ++++++++ .../phasar/Pointer/PointerAssignmentGraph.h | 15 +- include/phasar/Pointer/RawAliasSet.h | 75 ++++ include/phasar/Pointer/UnionFindAA.h | 353 ++++++++++++++++++ include/phasar/Utils/BitSet.h | 5 +- include/phasar/Utils/Compressor.h | 9 +- include/phasar/Utils/SCCGeneric.h | 67 +--- include/phasar/Utils/SCCId.h | 81 ++++ include/phasar/Utils/TypeTraits.h | 37 +- include/phasar/Utils/TypedVector.h | 3 +- include/phasar/Utils/UnionFind.h | 14 +- 12 files changed, 725 insertions(+), 84 deletions(-) create mode 100644 include/phasar/Pointer/CallingContextConstructor.h create mode 100644 include/phasar/Pointer/RawAliasSet.h create mode 100644 include/phasar/Pointer/UnionFindAA.h create mode 100644 include/phasar/Utils/SCCId.h diff --git a/include/phasar/PhasarLLVM/ControlFlow/VTA/TypePropagator.h b/include/phasar/PhasarLLVM/ControlFlow/VTA/TypePropagator.h index 2e6d6b874d..b6b12091b0 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/VTA/TypePropagator.h +++ b/include/phasar/PhasarLLVM/ControlFlow/VTA/TypePropagator.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_TYPEPROPAGATOR_H #include "phasar/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.h" +#include "phasar/Utils/SCCId.h" #include "phasar/Utils/TypedVector.h" #include "llvm/ADT/DenseSet.h" @@ -22,7 +23,6 @@ class Value; } // namespace llvm namespace psr { -template struct SCCId; template struct SCCHolder; template struct SCCDependencyGraph; template struct SCCOrder; diff --git a/include/phasar/Pointer/CallingContextConstructor.h b/include/phasar/Pointer/CallingContextConstructor.h new file mode 100644 index 0000000000..32e4976d60 --- /dev/null +++ b/include/phasar/Pointer/CallingContextConstructor.h @@ -0,0 +1,148 @@ +#pragma once + +#include "phasar/ControlFlow/CallGraph.h" +#include "phasar/ControlFlow/ICFGBase.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Compressor.h" + +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/Hashing.h" + +#include + +namespace psr { +template struct CallingContext { + using n_t = N; + + std::array Callers{}; + + [[nodiscard]] friend auto hash_value(const CallingContext &CC) { + return llvm::hash_combine_range(CC.Callers.begin(), CC.Callers.end()); + } + + [[nodiscard]] friend bool + operator==(const CallingContext &L, + const CallingContext &R) noexcept = default; + + [[nodiscard]] CallingContext withPrefix(n_t FirstCS) const noexcept { + auto Ret = *this; + auto Tmp = FirstCS; + for (size_t I = 0; I != K; ++I) { + auto NextTmp = Ret.Callers[I]; + Ret.Callers[I] = Tmp; + Tmp = NextTmp; + } + + return Ret; + } +}; +} // namespace psr + +namespace llvm { +template +struct DenseMapInfo> { + using CallingContext = psr::CallingContext; + + static CallingContext getEmptyKey() noexcept { + CallingContext Ret{}; + Ret.Callers[0] = llvm::DenseMapInfo::getEmptyKey(); + return Ret; + } + static CallingContext getTombstoneKey() noexcept { + CallingContext Ret{}; + Ret.Callers[0] = llvm::DenseMapInfo::getTombstoneKey(); + return Ret; + } + + static auto getHashValue(psr::ByConstRef CC) { + return hash_value(CC); + } + + static bool isEqual(psr::ByConstRef L, + psr::ByConstRef R) noexcept { + return L == R; + } +}; +} // namespace llvm + +namespace psr { +enum class CallingContextId : uint32_t { None = 0 }; + +template +class CallingContextConstructor { +public: + static_assert(K > 0); + static_assert(K < 10, "Do you really want a sooo large context-k-limit? " + "Reconsider your choices!"); + + using n_t = N; + using f_t = F; + + CallingContextConstructor() { + // Assign Id 0 === ContextId::None + CC2Id.getOrInsert(CallingContext{}); + } + + template + void visitAllCallingContexts( + const llvm::Function *Fun, const CallGraph &CG, + const CFGBase &CF, + std::invocable auto CCVisitor) { + CallingContext Ctx{}; + visitAllCallingContextsImpl<0>(Fun, CG, CF, CCVisitor, Ctx); + } + + template + void visitContextsAtCall(ByConstRef Call, const CallGraph &CG, + const CFGBase &CF, + std::invocable auto CCVisitor) { + CallingContext Ctx{}; + Ctx.Callers[0] = Call; + visitAllCallingContextsImpl<1>( + CF.getFunctionOf(Call), CG, CF, + [CCVisitor{std::move(CCVisitor)}](ByConstRef, + CallingContextId Ctx) { + std::invoke(CCVisitor, Ctx); + }, + Ctx); + } + + CallingContextId getOrInsert(ByConstRef> CC) { + return CC2Id.getOrInsert(CC); + } + + [[nodiscard]] ByConstRef> + operator[](CallingContextId Ctx) { + return CC2Id[Ctx]; + } + +private: + template + void visitAllCallingContextsImpl( + ByConstRef Fun, const CallGraph &CG, + const CFGBase &CF, + std::invocable auto &&CCVisitor, + CallingContext &CurrCtx) { + // TODO: Improve this algorithm + + if constexpr (Idx < K) { + auto &&CallersOfCS = CG.getCallersOf(Fun); + + for (const auto &CS : CallersOfCS) { + CurrCtx.Callers[Idx] = CS; + visitAllCallingContextsImpl<(Idx + 1)>(CF.getFunctionOf(CS), CG, CF, + CCVisitor, CurrCtx); + } + + if (!CallersOfCS.empty()) { + return; + } + } + + auto CtxId = CC2Id.getOrInsert(CurrCtx); + std::invoke(CCVisitor, CurrCtx.Callers[0], CtxId); + } + + Compressor, CallingContextId> CC2Id{}; +}; +} // namespace psr diff --git a/include/phasar/Pointer/PointerAssignmentGraph.h b/include/phasar/Pointer/PointerAssignmentGraph.h index 4bf3f66c84..0fb40c6771 100644 --- a/include/phasar/Pointer/PointerAssignmentGraph.h +++ b/include/phasar/Pointer/PointerAssignmentGraph.h @@ -7,6 +7,8 @@ #include "phasar/Utils/Utilities.h" #include "phasar/Utils/ValueCompressor.h" +#include "llvm/Support/Compiler.h" + #include #include #include @@ -37,11 +39,15 @@ struct Edge : public std::variant::variant; - template static constexpr size_t kindOf() noexcept { - return psr::variant_idx; + enum class EdgeKind : uint32_t {}; + + template static constexpr EdgeKind kindOf() noexcept { + return EdgeKind(psr::variant_idx); } - [[nodiscard]] constexpr size_t kind() const noexcept { return this->index(); } + [[nodiscard]] constexpr EdgeKind kind() const noexcept { + return EdgeKind(this->index()); + } template [[nodiscard]] constexpr bool isa() const noexcept { @@ -59,7 +65,8 @@ struct Edge : public std::variant - constexpr decltype(auto) apply(HandlerFn &&Handler) const { + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr decltype(auto) + apply(HandlerFn &&Handler) const { return std::visit(PSR_FWD(Handler), *this); } diff --git a/include/phasar/Pointer/RawAliasSet.h b/include/phasar/Pointer/RawAliasSet.h new file mode 100644 index 0000000000..97bc3630f1 --- /dev/null +++ b/include/phasar/Pointer/RawAliasSet.h @@ -0,0 +1,75 @@ +#pragma once + +#include "phasar/Utils/TypeTraits.h" + +#include "llvm/ADT/SparseBitVector.h" + +namespace psr { + +template +concept IsRawAliasSet = requires(ASet &MutSet, const ASet &ConstSet, + typename ASet::value_type ValId) { + ASet(); + ASet(ConstSet); + ASet(std::move(MutSet)); + MutSet = ConstSet; + MutSet = std::move(MutSet); + MutSet.insert(ValId); + { MutSet.tryInsert(ValId) } -> std::convertible_to; + { ConstSet.contains(ValId) } -> std::convertible_to; + // ConstSet.begin(); + // ConstSet.end(); + ConstSet.foreach (DummyFn{}); + MutSet |= ConstSet; + MutSet &= ConstSet; + { MutSet.tryMergeWith(ConstSet) } -> std::convertible_to; + { MutSet.clear() } noexcept; + { ConstSet.empty() } noexcept -> std::convertible_to; + { ConstSet.size() } noexcept -> std::convertible_to; + + // TODO: Difference? +}; + +template class RawAliasSet { +public: + using value_type = IdT; + + RawAliasSet() = default; + + void insert(IdT Id) { Bits.set(uint32_t(Id)); } + + [[nodiscard]] bool tryInsert(IdT Id) { + return Bits.test_and_set(uint32_t(Id)); + } + + [[nodiscard]] bool contains(IdT Id) const { return Bits.test(uint32_t(Id)); } + + LLVM_ATTRIBUTE_ALWAYS_INLINE void foreach ( + std::invocable auto Handler) const { + for (auto Bit : Bits) { + std::invoke(Handler, IdT(Bit)); + } + } + + void operator|=(const RawAliasSet &Other) { Bits |= Other.Bits; } + void operator&=(const RawAliasSet &Other) { Bits &= Other.Bits; } + + [[nodiscard]] bool empty() const noexcept { return Bits.empty(); } + [[nodiscard]] size_t size() const noexcept { return Bits.count(); } + + void clear() noexcept { Bits.clear(); } + + [[nodiscard]] auto begin() const noexcept { return Bits.begin(); } + [[nodiscard]] auto end() const noexcept { return Bits.end(); } + + [[nodiscard]] bool tryMergeWith(const RawAliasSet &Other) { + return Bits |= Other.Bits; + } + + void erase(IdT Id) { Bits.reset(uint32_t(Id)); } + +private: + llvm::SparseBitVector<> Bits; + // TODO: roaring::Roaring Bits; +}; +} // namespace psr diff --git a/include/phasar/Pointer/UnionFindAA.h b/include/phasar/Pointer/UnionFindAA.h new file mode 100644 index 0000000000..d8e6babed1 --- /dev/null +++ b/include/phasar/Pointer/UnionFindAA.h @@ -0,0 +1,353 @@ +#pragma once + +#include "phasar/Pointer/CallingContextConstructor.h" +#include "phasar/Pointer/PointerAssignmentGraph.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/Nullable.h" +#include "phasar/Utils/PointerUtils.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/UnionFind.h" +#include "phasar/Utils/Utilities.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/ErrorHandling.h" + +#include + +namespace psr { + +template +concept UnionFindAAResult = requires(const T &Result, ValueId Var) { + { T::isCached() } noexcept -> std::convertible_to; + { Result.getRawAliasSet(Var) } -> std::convertible_to>; + { Result.mayAlias(Var, Var) } -> std::convertible_to; +}; + +struct UnionFindAAResultBase { + enum class ObjectRepId : uint32_t {}; + + TypedVector> BackwardView; +}; + +struct BasicUnionFindAAResult : UnionFindAAResultBase { + TypedVector Var2Rep; + + static constexpr bool isCached() noexcept { return true; } + + [[nodiscard]] const RawAliasSet & + getRawAliasSet(ValueId Var) const noexcept { + auto Rep = Var2Rep[Var]; + return BackwardView[Rep]; + } + + [[nodiscard]] bool mayAlias(ValueId Var1, ValueId Var2) const noexcept { + if (Var1 == Var2) { + return true; + } + auto Rep1 = Var2Rep[Var1]; + auto Rep2 = Var2Rep[Var2]; + return Rep1 == Rep2; + } +}; + +struct CallingContextSensUnionFindAAResult : UnionFindAAResultBase { + TypedVector> Var2Rep{}; + + static constexpr bool isCached() noexcept { return false; } + + [[nodiscard]] RawAliasSet getRawAliasSet(ValueId Var) const { + RawAliasSet ResultSet; + for (auto Rep : Var2Rep[Var]) { + ResultSet |= BackwardView[Rep]; + } + return ResultSet; + } + + [[nodiscard]] bool mayAlias(ValueId Var1, ValueId Var2) const noexcept { + if (Var1 == Var2) { + return true; + } + + // Note: K is mostly small (1 or 2), so below loop should be very cheap + const auto &Reps1 = Var2Rep[Var1]; + const auto &Reps2 = Var2Rep[Var2]; + for (ObjectRepId R1 : Reps1) { + if (llvm::is_contained(Reps2, R1)) { + return true; + } + } + return false; + } +}; + +template +struct BasicUnionFindAA { + using ObjectId = ObjectIdT; + + using n_t = typename AnalysisDomainT::n_t; + using v_t = typename AnalysisDomainT::v_t; + using f_t = typename AnalysisDomainT::f_t; + using db_t = typename AnalysisDomainT::db_t; + + void onAddEdge(ObjectId From, ObjectId To, pag::Edge /*E*/, + Nullable /*AtInstruction*/) { + AliasSets.join(From, To); + } + + void onAddValue(ByConstRef /*Var*/, ObjectId VId) { + AliasSets.grow(size_t(VId) + 1); + } + + [[nodiscard]] BasicUnionFindAAResult + consumeAAResults(size_t NumVars, + std::invocable auto Var2Obj = IdentityFn{}) && { + auto Equiv = std::move(AliasSets) + .template compress(); + + static constexpr auto InvalidRep = + BasicUnionFindAAResult::ObjectRepId(UINT32_MAX); + + BasicUnionFindAAResult Result{}; + Result.BackwardView.resize(Equiv.size()); + Result.Var2Rep.resize(NumVars, InvalidRep); + + for (auto VId : iota(NumVars)) { + // TODO: Skip only-ret val-ids + auto ObjId = std::invoke(Var2Obj, VId); + auto Rep = Equiv[ObjId]; + assert(Result.Var2Rep[VId] == InvalidRep && + "Mapping from ValueId to AliasSet-Id must be injective!"); + Result.Var2Rep[VId] = Rep; + Result.BackwardView[Rep].insert(VId); + } + + return Result; + } + + UnionFind AliasSets{}; +}; + +template +class CallingContextSensUnionFindAA { +public: + enum class CtxObjectId : uint32_t {}; + + using n_t = typename AnalysisDomainT::n_t; + using v_t = typename AnalysisDomainT::v_t; + using f_t = typename AnalysisDomainT::f_t; + using db_t = typename AnalysisDomainT::db_t; + using i_t = typename AnalysisDomainT::i_t; + + CallingContextSensUnionFindAA(MaybeUniquePtr ICF) noexcept + : ICF(std::move(ICF)) { + assert(this->ICF != nullptr); + } + + void onAddEdge(ValueId From, ValueId To, pag::Edge E, + Nullable CallSite) { + E.apply(Overloaded{ + [&, this, CallSite](pag::Call) { + for (const auto &[ArgCtxId, FromObj] : Var2Obj[From]) { + auto ParamCtx = CC[ArgCtxId].withPrefix(CallSite); + auto ParamCtxId = CC.getOrInsert(ParamCtx); + + if (const auto *Dst = getObjectOrNull(To, ParamCtxId)) { + Base.AliasSets.join(FromObj, *Dst); + } + } + }, + [&, this, CallSite](pag::Return) { + for (const auto &[CSValCtxId, ToObj] : Var2Obj[To]) { + auto RetCtx = CC[CSValCtxId].withPrefix(CallSite); + auto RetCtxId = CC.getOrInsert(RetCtx); + + if (const auto *Src = getObjectOrNull(From, RetCtxId)) { + Base.AliasSets.join(*Src, ToObj); + } + } + }, + [&, this](auto) { + const auto &FromObjects = Var2Obj[From]; + for (const auto &[FromCtx, FromObj] : FromObjects) { + if (FromCtx == CallingContextId::None) { + for (const auto &[ToCtx, ToObj] : Var2Obj[To]) { + Base.AliasSets.join(FromObj, ToObj); + } + } else if (const auto *Dst = getObjectOrNull(To, FromCtx)) { + Base.AliasSets.join(FromObj, *Dst); + } + } + }, + }); + } + + void onAddValue(ByConstRef Var, ValueId VId) { + Var2Obj.emplace_back(); + if (const auto &Fun = getFunction(Var)) { + CC.visitAllCallingContexts( + unwrapNullable(Fun), *ICF, *ICF, + [this, VId](ByConstRef /*FirstCS*/, CallingContextId Ctx) { + std::ignore = getObject(VId, Ctx); + }); + + if (!ICF->getCallersOf(unwrapNullable(Fun)).empty()) { + return; + } + // fallback in case, we have no callers + } + std::ignore = getObject(VId, CallingContextId::None); + } + + [[nodiscard]] CallingContextSensUnionFindAAResult + consumeAAResults(size_t NumVars) && { + auto Equiv = std::move(Base.AliasSets) + .template compress(); + + CallingContextSensUnionFindAAResult Result{}; + Result.BackwardView.resize(Equiv.size()); + Result.Var2Rep.resize(NumVars); + + for (const auto &[ValId, Objects] : Var2Obj.enumerate()) { + // TODO: Skip only-ret val-ids + + for (const auto &[_, Obj] : Objects) { + auto Rep = Equiv[Obj]; + if (Result.BackwardView[Rep].try_insert(ValId)) { + Result.Var2Rep[ValId].push_back(Rep); + } + } + if (Objects.empty()) { + llvm::errs() << "Warning: Empty Objects-set for Var#" << uint32_t(ValId) + << '\n'; + } + } + + return Result; + } + +private: + [[nodiscard]] Nullable getFunction(ByConstRef Var) { + auto Ptr = getPointerFrom(Var); + if constexpr (requires() { + { + Ptr->getFunction() + } -> std::convertible_to>; + }) { + return Ptr->getFunction(); + } else { + return ICF.getFunction(Var); + } + } + + [[nodiscard]] CtxObjectId getObject(ValueId VId, CallingContextId Ctx) { + auto [It, Inserted] = + Var2Obj[VId].try_emplace(Ctx, CtxObjectId(Obj2Var.size())); + if (Inserted) { + Obj2Var.emplace_back(VId, Ctx); + Base.AliasSets.grow(size_t(It->second) + 1); + } + return It->second; + } + + MaybeUniquePtr ICF; + TypedVector> Obj2Var{}; + TypedVector> + Var2Obj{}; + + CallingContextConstructor CC{}; + BasicUnionFindAA Base{}; +}; + +template +class IndirectionSencUnionFindAA { +public: + static_assert(K > 0, "A depth-limit of 0 is invalid!"); + + enum class IndObjectId : uint32_t {}; + enum class IndDepth : uint32_t {}; + + using n_t = typename AnalysisDomainT::n_t; + using v_t = typename AnalysisDomainT::v_t; + using f_t = typename AnalysisDomainT::f_t; + using db_t = typename AnalysisDomainT::db_t; + + void onAddEdge(ValueId From, ValueId To, pag::Edge E, + Nullable /*CallSite*/) { + using namespace pag; + const auto &FromFldObjects = Var2Obj[From]; + for (const auto &[FromCtx, FromObj] : FromFldObjects.enumerate()) { + switch (E.kind()) { + case Edge::kindOf(): + if (FromCtx == IndDepth{}) { + break; + } + + // fallthrough + case Edge::kindOf(): + case Edge::kindOf(): + case Edge::kindOf(): + case Edge::kindOf(): + if (auto ToObj = getFldObjectOrNull(To, FromCtx)) { + Base.AliasSets.join(FromObj, *ToObj); + } + + break; + case Edge::kindOf(): + if (auto ToCtx = prevFldDepth(FromCtx)) { + if (auto ToObj = getFldObjectOrNull(To, *ToCtx)) { + Base.AliasSets.join(FromObj, *ToObj); + } + } + if (FromCtx == K - 1) { + // For soundness + if (auto ToObj = getFldObjectOrNull(To, FromCtx)) { + Base.AliasSets.join(FromObj, *ToObj); + } + } + break; + case Edge::kindOf(): + case Edge::kindOf(): + if (auto ToObj = getFldObjectOrNull(To, nextFldDepth(FromCtx))) { + Base.AliasSets.join(FromObj, ToObj); + } + + break; + default: + llvm_unreachable("This switch should cover all cases explicitly"); + } + } + } + + void onAddValue(ByConstRef /*Var*/, ValueId VId) { + auto Obj = Obj2Var.size(); + Base.AliasSets.grow(Obj + K); + // TODO: For some values, we can already see that depth>=2 does not make + // sense, so we could exit the below loop early + for (auto Depth : iota(K)) { + Var2Obj[VId].push_back(IndObjectId(Obj)); + Obj++; + Obj2Var.emplace_back(VId, Depth); + } + } + + [[nodiscard]] BasicUnionFindAAResult consumeAAResults(size_t NumVars) && { + return Base.consumeAAResults(NumVars, [this](ValueId VId) { + // When presenting the results, we only care about the values at depth 0 + return Var2Obj[VId][IndDepth{}]; + }); + } + +private: + // XXX: Replace inner TypedVector by sth like TypedInplaceVector (e.g., using + // std::inplace_vector once moving forward with the required C++ version) + TypedVector> Var2Obj{}; + TypedVector> Obj2Var{}; + BasicUnionFindAA Base{}; +}; +} // namespace psr diff --git a/include/phasar/Utils/BitSet.h b/include/phasar/Utils/BitSet.h index b49e1d647e..720fcddde2 100644 --- a/include/phasar/Utils/BitSet.h +++ b/include/phasar/Utils/BitSet.h @@ -10,6 +10,8 @@ #ifndef PHASAR_UTILS_BITSET_H #define PHASAR_UTILS_BITSET_H +#include "phasar/Utils/TypeTraits.h" + #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/Support/MathExtras.h" @@ -34,7 +36,8 @@ namespace psr { /// convertible from and to uint32_t. /// \tparam BitVectorTy The underlying bit-vector to use. Must be either /// llvm::BitVector or llvm::SmallBitVector. -template class BitSet { +template +class BitSet { static llvm::ArrayRef getWords(const llvm::BitVector &BV, uintptr_t & /*Store*/) { return BV.getData(); diff --git a/include/phasar/Utils/Compressor.h b/include/phasar/Utils/Compressor.h index c67f54aa11..3b920c8868 100644 --- a/include/phasar/Utils/Compressor.h +++ b/include/phasar/Utils/Compressor.h @@ -24,13 +24,13 @@ #include namespace psr { -template class Compressor; +template class Compressor; /// \brief A utility class that assigns a sequential Id to every inserted /// object. /// /// This specialization handles types that can be efficiently passed by value -template +template requires CanEfficientlyPassByValue class Compressor { public: @@ -56,8 +56,7 @@ class Compressor { return {It->second, Inserted}; } - [[nodiscard]] - std::optional getOrNull(T Elem) const { + [[nodiscard]] std::optional getOrNull(T Elem) const { if (auto It = ToInt.find(Elem); It != ToInt.end()) { return It->second; } @@ -104,7 +103,7 @@ class Compressor { /// object. /// /// This specialization handles types that cannot be efficiently passed by value -template +template requires(!CanEfficientlyPassByValue) class Compressor { public: diff --git a/include/phasar/Utils/SCCGeneric.h b/include/phasar/Utils/SCCGeneric.h index 9c31d325d0..9132a09c2b 100644 --- a/include/phasar/Utils/SCCGeneric.h +++ b/include/phasar/Utils/SCCGeneric.h @@ -15,10 +15,10 @@ #include "phasar/Utils/GraphTraits.h" #include "phasar/Utils/IotaIterator.h" #include "phasar/Utils/RepeatIterator.h" +#include "phasar/Utils/SCCId.h" #include "phasar/Utils/TypedVector.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" @@ -29,69 +29,6 @@ namespace psr { -namespace detail { -// Unfortunately, `enum class` cannot be templated, but we want type-safety for -// SCC-IDs... -struct SCCIdBase { - uint32_t Value{}; - - constexpr SCCIdBase() noexcept = default; - - explicit constexpr SCCIdBase(uint32_t Val) noexcept : Value(Val) {} - - explicit constexpr operator uint32_t() const noexcept { return Value; } - template - explicit constexpr operator size_t() const noexcept - requires(!std::is_same_v) - { - return Value; - } - - explicit constexpr operator ptrdiff_t() const noexcept { - return ptrdiff_t(Value); - } - - constexpr uint32_t operator+() const noexcept { return Value; } - - friend constexpr bool operator==(SCCIdBase L, SCCIdBase R) noexcept { - return L.Value == R.Value; - } - friend constexpr bool operator!=(SCCIdBase L, SCCIdBase R) noexcept { - return !(L == R); - } -}; -} // namespace detail - -/// \brief The Id of a strongly-connected component in a graph. -/// -/// \tparam GraphNodeId The vertex-type of the graph where this SCC was computed -/// for. -template struct SCCId : detail::SCCIdBase { - using detail::SCCIdBase::SCCIdBase; -}; - -} // namespace psr - -namespace llvm { -template struct DenseMapInfo> { - using SCCId = psr::SCCId; - - static constexpr SCCId getEmptyKey() noexcept { return SCCId(UINT32_MAX); } - static constexpr SCCId getTombstoneKey() noexcept { - return SCCId(UINT32_MAX - 1); - } - - static auto getHashValue(SCCId SCC) noexcept { - return llvm::hash_value(uint32_t(SCC)); - } - static constexpr bool isEqual(SCCId SCC1, SCCId SCC2) noexcept { - return SCC1 == SCC2; - } -}; -} // namespace llvm - -namespace psr { - /// \brief Holds the SCCs of a given graph. Each SCC is assigned a unique /// sequential id. template struct SCCHolder { @@ -169,7 +106,7 @@ struct GraphTraits> { using vertex_t = SCCId; using edge_t = vertex_t; - static inline constexpr auto Invalid = vertex_t(UINT32_MAX); + static constexpr auto Invalid = vertex_t(UINT32_MAX); [[nodiscard]] static constexpr const auto &outEdges(const graph_type &G, vertex_t Vtx) noexcept { diff --git a/include/phasar/Utils/SCCId.h b/include/phasar/Utils/SCCId.h new file mode 100644 index 0000000000..0cf4aeb0c0 --- /dev/null +++ b/include/phasar/Utils/SCCId.h @@ -0,0 +1,81 @@ +/****************************************************************************** + * Copyright (c) 2024 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and other + *****************************************************************************/ +#pragma once + +#include "phasar/Utils/TypeTraits.h" + +#include "llvm/ADT/DenseMapInfo.h" + +#include +#include + +namespace psr { + +namespace detail { +// Unfortunately, `enum class` cannot be templated, but we want type-safety for +// SCC-IDs... +struct SCCIdBase { + uint32_t Value{}; + + constexpr SCCIdBase() noexcept = default; + + explicit constexpr SCCIdBase(uint32_t Val) noexcept : Value(Val) {} + + explicit constexpr operator uint32_t() const noexcept { return Value; } + template + explicit constexpr operator size_t() const noexcept + requires(!std::is_same_v) + { + return Value; + } + + explicit constexpr operator ptrdiff_t() const noexcept { + return ptrdiff_t(Value); + } + + constexpr uint32_t operator+() const noexcept { return Value; } + + friend constexpr bool operator==(SCCIdBase L, SCCIdBase R) noexcept { + return L.Value == R.Value; + } + friend constexpr bool operator!=(SCCIdBase L, SCCIdBase R) noexcept { + return !(L == R); + } +}; +} // namespace detail + +/// \brief The Id of a strongly-connected component in a graph. +/// +/// \tparam GraphNodeId The vertex-type of the graph where this SCC was computed +/// for. +template struct SCCId : detail::SCCIdBase { + using detail::SCCIdBase::SCCIdBase; +}; + +static_assert(IdType>); + +} // namespace psr + +namespace llvm { +template struct DenseMapInfo> { + using SCCId = psr::SCCId; + + static constexpr SCCId getEmptyKey() noexcept { return SCCId(UINT32_MAX); } + static constexpr SCCId getTombstoneKey() noexcept { + return SCCId(UINT32_MAX - 1); + } + + static auto getHashValue(SCCId SCC) noexcept { + return llvm::hash_value(uint32_t(SCC)); + } + static constexpr bool isEqual(SCCId SCC1, SCCId SCC2) noexcept { + return SCC1 == SCC2; + } +}; +} // namespace llvm diff --git a/include/phasar/Utils/TypeTraits.h b/include/phasar/Utils/TypeTraits.h index 97e324c379..9bc4d3c8ad 100644 --- a/include/phasar/Utils/TypeTraits.h +++ b/include/phasar/Utils/TypeTraits.h @@ -16,6 +16,7 @@ #include "llvm/Support/raw_ostream.h" #include +#include #include #include #include @@ -56,6 +57,19 @@ class template_arg { decltype(getTemplateArgImpl(std::declval())); }; +template