diff --git a/LICENSE.txt b/LICENSE.txt index 6b44aff4be..ed8ad367db 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -208,3 +208,38 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +CRoaring +------------------------------------------------------------------------------- +The CRoaring project is under a dual license (Apache/MIT). +Users of the library may choose one or the other license. +--- + +MIT License + +Copyright 2016-2022 The CRoaring authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/include/phasar/ControlFlow/CallGraphBase.h b/include/phasar/ControlFlow/CallGraphBase.h index 2d39581e3c..186ca426f2 100644 --- a/include/phasar/ControlFlow/CallGraphBase.h +++ b/include/phasar/ControlFlow/CallGraphBase.h @@ -15,6 +15,7 @@ #include "phasar/Utils/Compressor.h" #include "phasar/Utils/GraphTraits.h" #include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/Macros.h" #include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/TypeTraits.h" @@ -147,13 +148,15 @@ template class ReverseCGGraph { typename CallGraphTy::f_t>; constexpr ReverseCGGraph( - NonNullPtr CGView, NonNullPtr IRDB, + NonNullPtr CGView PSR_LIFETIMEBOUND, + NonNullPtr IRDB PSR_LIFETIMEBOUND, Compressor FC) noexcept requires(NeedsMapping) : CGView(CGView), IRDB(IRDB), FC(std::move(FC)) {} - constexpr ReverseCGGraph(NonNullPtr CGView, - NonNullPtr IRDB) noexcept + constexpr ReverseCGGraph(NonNullPtr CGView + PSR_LIFETIMEBOUND, + NonNullPtr IRDB PSR_LIFETIMEBOUND) noexcept : CGView(CGView), IRDB(IRDB) { FC.reserve(CGView->getNumVertexFunctions()); for (const auto &Fun : CGView->getAllVertexFunctions()) { diff --git a/include/phasar/PhasarLLVM/Pointer.h b/include/phasar/PhasarLLVM/Pointer.h index 336d28a97c..713572dfb8 100644 --- a/include/phasar/PhasarLLVM/Pointer.h +++ b/include/phasar/PhasarLLVM/Pointer.h @@ -17,6 +17,7 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" #include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" diff --git a/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h index 8b718379de..055a0d7b07 100644 --- a/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h +++ b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h @@ -14,6 +14,7 @@ #include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" #include "phasar/Pointer/RawAliasSet.h" #include "phasar/Pointer/UnionFindAA.h" +#include "phasar/Utils/Macros.h" #include "phasar/Utils/MaybeUniquePtr.h" #include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/Soundness.h" @@ -22,6 +23,10 @@ #include "llvm/ADT/ArrayRef.h" +#include +#include +#include + namespace llvm { class Function; } // namespace llvm @@ -30,10 +35,54 @@ namespace psr { class LLVMProjectIRDB; +/// Opt-in call-string context-sensitivity for \c AndersenOTFSolver. +/// +/// The call-string k-limit is fixed at 1: a selected function gets one set of +/// PAG nodes per call-site that reaches it. Non-selected functions keep a +/// single set of nodes shared by all callers, exactly as before. +struct ContextSensitivityOptions { + enum class Mode : uint8_t { + Off, ///< Root context only; identical to the insensitive solver. + Manual, ///< Only functions matching \c AllowList. + Dynamic, ///< \c AllowList plus functions observed as precision-critical. + All, ///< Every function, until \c MaxContextualNodes is reached. + }; + + Mode SelectionMode = Mode::Off; + /// Function-name globs (\c llvm::GlobPattern). \c DenyList wins over + /// \c AllowList. + std::vector AllowList{}; + std::vector DenyList{}; + /// Hard cap on context-qualified PAG nodes. Once reached, no function is + /// newly selected and no already-selected function gets a further context: + /// sound, just less precise. This is the knob that bounds run time; solve + /// time grows super-linearly in the node count, so raising it is not a + /// proportional trade. + size_t MaxContextualNodes = 20'000; + /// Cap on distinct calling contexts per function; further call sites fall + /// back to the shared root context. A selected function costs one clone of + /// its whole body per context, so without this a single hot function can + /// consume \c MaxContextualNodes on its own. + unsigned MaxContextsPerFunction = 32; + /// \c Mode::Dynamic only: functions with more LLVM instructions than this + /// are never selected. Cloning a large body per context is expensive, and + /// large functions are rarely the point where callers merge. + unsigned MaxContextualFunctionSize = 256; + /// \c Mode::Dynamic only: tighter size limit for the weaker signal where + /// the merged parameters never leave the function body. Off by default: + /// that signal's only payoff is formal-vs-formal aliasing inside the body, + /// which \c buildResult unions back together across contexts anyway. + unsigned MaxLocalMergeFunctionSize = 0; + + [[nodiscard]] constexpr bool isOff() const noexcept { + return SelectionMode == Mode::Off; + } +}; + /// Alias-analysis result for the Andersen-style OTF points-to analysis. /// /// Two values may-alias iff their points-to sets share at least one abstract -/// object. Satisfies \c UnionFindAAResult so it can be wrapped by +/// object. Satisfies \c RawAAResult so it can be wrapped by /// \c LLVMUnionFindAliasIterator. struct AndersenOTFResult { TypedVector> AliasSets; @@ -63,7 +112,7 @@ struct AndersenOTFResult { } }; -static_assert(UnionFindAAResult); +static_assert(RawAAResult); /// Andersen-style inclusion-based points-to analysis that co-refines the call /// graph and points-to sets in a single fixpoint. @@ -72,13 +121,16 @@ static_assert(UnionFindAAResult); /// function-worklist loop: direct calls add callees immediately; indirect /// calls are resolved as \c pts(fp) grows. /// -/// Phase 1: context- and field-insensitive. +/// Context-sensitivity is opt-in via \c ContextSensitivityOptions and off by +/// default. class AndersenOTFSolver { public: - explicit AndersenOTFSolver(const LLVMProjectIRDB &IRDB, - llvm::ArrayRef Entries, - ValueCompressor &VC, - Soundness S = Soundness::Soundy) noexcept; + explicit AndersenOTFSolver(const LLVMProjectIRDB &IRDB PSR_LIFETIMEBOUND, + llvm::ArrayRef Entries + PSR_LIFETIMEBOUND, + ValueCompressor &VC PSR_LIFETIMEBOUND, + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}) noexcept; /// Run the full OTF fixpoint and return the alias-analysis result. [[nodiscard]] AndersenOTFResult solve(); @@ -90,6 +142,7 @@ class AndersenOTFSolver { llvm::ArrayRef Entries; NonNullPtr> VC; Soundness S; + ContextSensitivityOptions CSOpts; }; // ---- Factory functions ------------------------------------------------ @@ -100,14 +153,16 @@ class AndersenOTFSolver { computeAndersenOTFRaw(const LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints, MaybeUniquePtr> VC = nullptr, - Soundness S = Soundness::Soundy); + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}); /// Runs the Andersen OTF fixpoint and returns an \c LLVMUnionFindAliasIterator /// that implements \c IsLLVMAliasIterator. -[[nodiscard]] LLVMUnionFindAliasIterator +[[nodiscard]] LLVMRawAliasIterator computeAndersenOTF(const LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints, MaybeUniquePtr> VC = nullptr, - Soundness S = Soundness::Soundy); + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}); } // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h new file mode 100644 index 0000000000..67070c196d --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h @@ -0,0 +1,170 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/Casting.h" + +#include + +/// LLVM-level questions that LLVMPAGBuilder and AndersenOTFSolver must answer +/// identically. They emit different node and edge kinds, but must not disagree +/// on which IR constructs carry pointers. + +namespace psr { + +/// Whether a value of the pointer-free type \p Ty is laid out such that it +/// could hold pointer bit patterns. +[[nodiscard]] inline bool mayHidePointer(const llvm::DataLayout &DL, + const llvm::Type *Ty) { + if (Ty->isIntegerTy(DL.getPointerSizeInBits())) { + return true; + } + if (const auto *Arr = llvm::dyn_cast(Ty)) { + return mayHidePointer(DL, Arr->getElementType()); + } + const auto *Struct = llvm::dyn_cast(Ty); + return Struct && !Struct->isOpaque() && !Struct->elements().empty() && + llvm::all_of(Struct->elements(), [&DL](const llvm::Type *ElemTy) { + return mayHidePointer(DL, ElemTy); + }); +} + +/// Whether Ptr is a memory-location (alloca or global), accessed as an +/// integer. +/// +/// Useful for handling atomicrmw of pointers, which clang punns to i64, and +/// for ABI-coerced aggregates (see PunnedABICache). +[[nodiscard]] inline bool isPunnedPointerAccess(const llvm::DataLayout &DL, + const llvm::Value *Ptr, + const llvm::Type *AccessedTy) { + if (!mayHidePointer(DL, AccessedTy)) { + return false; + } + const llvm::Value *Base = Ptr->stripPointerCastsAndAliases(); + if (const auto *A = llvm::dyn_cast(Base)) { + return !definitelyContainsNoPointer(A->getAllocatedType()); + } + if (const auto *G = llvm::dyn_cast(Base)) { + return !definitelyContainsNoPointer(G->getValueType()); + } + return false; +} + +/// The memory access to model for a load, store, atomicrmw or cmpxchg. +struct LLVMMemoryAccess { + const llvm::Instruction *Instr{}; + const llvm::Value *Pointer{}; + /// Null if the access only reads. + const llvm::Value *StoredValue{}; + /// Null if the access only writes; \c Instr itself for an atomic. + const llvm::Instruction *LoadedInto{}; + bool Punned{}; + + /// The value whose type decides whether a pointer is transferred. For a + /// cmpxchg that is the new value, not the { ty, i1 } result. + [[nodiscard]] const llvm::Value *transferredValue() const noexcept { + return StoredValue ? StoredValue : LoadedInto; + } + + /// Whether the access has to be modeled at all. Gating on + /// definitelyContainsNoPointer alone would drop punned accesses. + [[nodiscard]] bool mayTransferPointer() const { + return Punned || !definitelyContainsNoPointer(transferredValue()); + } +}; + +/// Decomposes \p I, or returns nullopt if it is not a memory access. Field- +/// insensitively an atomicrmw is a store of the new value plus a load of the +/// old one; cmpxchg likewise, into its { ty, i1 } result. +/// +/// The result still has to pass mayTransferPointer(). +[[nodiscard]] inline std::optional +asMemoryAccess(const llvm::Instruction &I, const llvm::DataLayout &DL) { + const auto Make = [&DL, &I](const llvm::Value *Ptr, + const llvm::Value *StoredValue, + const llvm::Instruction *LoadedInto) { + const auto *Transferred = StoredValue ? StoredValue : LoadedInto; + return LLVMMemoryAccess{ + .Instr = &I, + .Pointer = Ptr, + .StoredValue = StoredValue, + .LoadedInto = LoadedInto, + .Punned = isPunnedPointerAccess(DL, Ptr, Transferred->getType()), + }; + }; + + if (const auto *S = llvm::dyn_cast(&I)) { + return Make(S->getPointerOperand(), S->getValueOperand(), nullptr); + } + if (const auto *L = llvm::dyn_cast(&I)) { + return Make(L->getPointerOperand(), nullptr, L); + } + if (const auto *RMW = llvm::dyn_cast(&I)) { + return Make(RMW->getPointerOperand(), RMW->getValOperand(), RMW); + } + if (const auto *CX = llvm::dyn_cast(&I)) { + return Make(CX->getPointerOperand(), CX->getNewValOperand(), CX); + } + return std::nullopt; +} + +/// Recognizes ABI-coerced boundary values: values that carry a pointer +/// although their type has none. +/// +/// A small pointer-carrying struct is passed and returned in registers: +/// +/// For whatever reason, on AArch64 clang punns such nested pointers as i64 +/// instead of ptr. This here is a best-effort approcach to keep pointer +/// data-flows in such situations. +class PunnedABICache { +public: + explicit PunnedABICache(const llvm::DataLayout *DL) noexcept + : DL(&assertNotNull(DL)) {} + + /// Whether \p V -- an argument, returned value or call result of \p F -- + /// carries a pointer that its type does not reveal. + [[nodiscard]] bool isCoercedPointer(const llvm::Value *V, + const llvm::Function *F) { + return !llvm::isa(V) && + definitelyContainsNoPointer(V->getType()) && + mayHidePointer(*DL, V->getType()) && punsPointers(F); + } + +private: + bool punsPointers(const llvm::Function *F) { + auto [It, Inserted] = Cache.try_emplace(F, false); + if (Inserted) { + It->second = llvm::any_of(llvm::instructions(*F), [this](const auto &I) { + const auto Access = asMemoryAccess(I, *DL); + return Access && Access->Punned; + }); + } + return It->second; + } + + const llvm::DataLayout *DL; + llvm::DenseMap Cache; +}; + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h b/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h new file mode 100644 index 0000000000..4a436ad382 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h @@ -0,0 +1,300 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Pointer/AliasResult.h" +#include "phasar/Pointer/RawAAResult.h" +#include "phasar/Utils/NonNullPtr.h" + +#include "llvm/IR/Instructions.h" + +namespace psr { + +/// Returns a \c ValueId handler suitable for \c RawAliasSet::foreach() that +/// maps each alias \c ValueId back to all of its underlying \c llvm::Value* +/// (via \p VC), forwarding non-null values to \p Callback. +constexpr std::invocable auto +llvmRawAliasHandler(const ValueCompressor &VC, + std::invocable auto Callback) { + return [&VC, Callback{copyOrRef(Callback)}](ValueId Alias) { + for (auto V : VC.id2vars(Alias)) { + if (const auto *LLVMVar = V.valueOrNull()) [[likely]] { + std::invoke(Callback, LLVMVar); + } + } + }; +} + +/// CRTP mixin that adds the LLVM alias-iterator interface to a class that +/// holds a \c RawAAResult and a \c ValueCompressor. +/// +/// Provides \c forallAliasesOf(), \c mayAlias(), and \c alias() overloads +/// accepting both \c llvm::Value* and \c ValueId arguments. Results are +/// reported as \c llvm::Value* via the stored \c ValueCompressor. +/// +/// The derived class must expose a \c VC member (pointer to a +/// \c ValueCompressor). +/// +/// \tparam Derived The CRTP derived class. +/// \tparam AAResT A type satisfying \c RawAAResult. +template + requires RawAAResult> +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +struct LLVMRawAliasIteratorMixin { + [[no_unique_address]] AAResT AARes; + + using v_t = const llvm::Value *; + using n_t = const llvm::Instruction *; + + [[nodiscard]] decltype(auto) getRawAliasSet(ValueId ValId) const { + return AARes.getRawAliasSet(ValId); + } + + [[nodiscard]] const auto &base() const noexcept { return AARes; } + + void + forallAliasesOf(ValueId VId, const auto & /*Inst*/, + std::invocable auto Callback) const { + const auto &RawAliases = AARes.getRawAliasSet(VId); + RawAliases.foreach (llvmRawAliasHandler(*self().VC, copyOrRef(Callback))); + } + + void + forallAliasesOf(const llvm::Value *Ptr, const auto &Inst, + std::invocable auto Callback) const { + if (auto ValId = self().VC->getOrNull(Ptr)) { + forallAliasesOf(*ValId, Inst, copyOrRef(Callback)); + } + } + + [[nodiscard]] bool mayAlias(ValueId Ptr1, ValueId Ptr2) const { + return AARes.mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] bool mayAlias(ValueId Ptr1, ValueId Ptr2, + const auto & /*AtInstruction*/) const { + return AARes.mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] bool mayAlias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + + return ValId1 && ValId2 && mayAlias(*ValId1, *ValId2); + } + + [[nodiscard]] bool mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + const auto & /*AtInstruction*/) const { + return mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] AliasResult alias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2, + const auto &AtInstruction) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + if (!ValId1 || !ValId2) { + return AliasResult::NoAlias; + } + if (*ValId1 == *ValId2) { + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } + return !llvm::isa(Ptr1) && + !llvm::isa(Ptr2) + ? AliasResult::MustAlias + : AliasResult::MayAlias; + } + return mayAlias(*ValId1, *ValId2, AtInstruction) ? AliasResult::MayAlias + : AliasResult::NoAlias; + } + + [[nodiscard]] constexpr const Derived &self() const noexcept { + return *static_cast(this); + } +}; + +template + requires RawAAResult> +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +struct LLVMRawAliasIterator + : public LLVMRawAliasIteratorMixin, AAResT> { + MaybeUniquePtr> VC; + + constexpr LLVMRawAliasIterator( + AAResT &&AARes, + MaybeUniquePtr> VC PSR_LIFETIMEBOUND) + : psr::LLVMRawAliasIteratorMixin, + AAResT>{PSR_FWD(AARes)}, + VC(std::move(VC)) {} +}; + +template +LLVMRawAliasIterator(AAResT, MaybeUniquePtr>) + -> LLVMRawAliasIterator; +template +LLVMRawAliasIterator(AAResT, const ValueCompressor *) + -> LLVMRawAliasIterator; + +namespace detail { +class LLVMLocalRawAliasIteratorBase { +public: + explicit LLVMLocalRawAliasIteratorBase( + const ValueCompressor &VC); + +protected: + llvm::DenseMap> GlobalsOrInFun; +}; +} // namespace detail + +/// CRTP mixin adding a function-local view on top of a global +/// \c RawAAResult. +/// +/// Extends \c LLVMRawAliasIteratorMixin with \c forallAliasesOf() +/// overloads that accept an \c llvm::Function* or \c llvm::Instruction* +/// context. When a non-null context is provided, alias sets are intersected +/// with the set of variables that are visible in that function (globals plus +/// locals defined in that function), giving a function-local result even +/// though the underlying analysis is interprocedural. +template +class LLVMLocalRawAliasIteratorMixin + : public detail::LLVMLocalRawAliasIteratorBase { +public: + LLVMLocalRawAliasIteratorMixin(AAResT &&AARes, + const ValueCompressor &VC) + : detail::LLVMLocalRawAliasIteratorBase(VC), AARes(PSR_FWD(AARes)) {} + + [[nodiscard]] decltype(auto) getRawAliasSet(ValueId ValId) const { + return AARes.getRawAliasSet(ValId); + } + + [[nodiscard]] auto getRawAliasSet(ValueId ValId, + const llvm::Function *Context) const { + auto Vars = AARes.getRawAliasSet(ValId); + if (Context) { + Vars &= getOrDefault(GlobalsOrInFun, Context); + } + return Vars; + } + + [[nodiscard]] const auto &base() const noexcept { return AARes; } + + [[nodiscard]] auto getRawAliasSet(ValueId ValId, + const llvm::Instruction *Context) const { + return getRawAliasSet(ValId, getFunction(Context)); + } + + void forallAliasesOf(ValueId VId, const llvm::Function *Context, + std::invocable auto WithAlias) { + const auto AliasHandler = + llvmRawAliasHandler(*self().VC, copyOrRef(WithAlias)); + + auto &&RawVars = AARes.getRawAliasSet(VId); + if (Context) { + auto Vars = PSR_FWD(RawVars); + Vars &= getOrDefault(GlobalsOrInFun, Context); + Vars.foreach (AliasHandler); + } else { + RawVars.foreach (AliasHandler); + } + } + + void forallAliasesOf(const llvm::Value *Val, const llvm::Function *Context, + std::invocable auto WithAlias) { + if (auto ValId = self().VC->getOrNull(Val)) { + forallAliasesOf(*ValId, Context, copyOrRef(WithAlias)); + } + } + + void forallAliasesOf(ValueId ValId, const llvm::Instruction *AtInstruction, + std::invocable auto WithAlias) { + forallAliasesOf(ValId, psr::getFunction(AtInstruction), + copyOrRef(WithAlias)); + } + + void forallAliasesOf(const llvm::Value *Val, + const llvm::Instruction *AtInstruction, + std::invocable auto WithAlias) { + forallAliasesOf(Val, psr::getFunction(AtInstruction), copyOrRef(WithAlias)); + } + + void forallAliasesOf(const llvm::Value *Val, + std::invocable auto WithAlias) { + forallAliasesOf(Val, psr::getFunction(Val), copyOrRef(WithAlias)); + } + + [[nodiscard]] bool + mayAlias(ValueId ValId1, ValueId ValId2, + const llvm::Instruction * /*AtInstruction*/ = nullptr) const { + // XXX: Should we filter by AtInstruction-context here as well? + return AARes.mayAlias(ValId1, ValId2); + } + + [[nodiscard]] bool + mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + const llvm::Instruction * /*AtInstruction*/ = nullptr) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + + // XXX: Should we filter by AtInstruction-context here as well? + return ValId1 && ValId2 && AARes.mayAlias(*ValId1, *ValId2); + } + + [[nodiscard]] AliasResult alias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2, + const auto &AtInstruction) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + if (!ValId1 || !ValId2) { + return AliasResult::NoAlias; + } + if (*ValId1 == *ValId2) { + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } + return !llvm::isa(Ptr1) && + !llvm::isa(Ptr2) + ? AliasResult::MustAlias + : AliasResult::MayAlias; + } + return mayAlias(*ValId1, *ValId2, AtInstruction) ? AliasResult::MayAlias + : AliasResult::NoAlias; + } + + [[nodiscard]] constexpr const Derived &self() const noexcept { + return *static_cast(this); + } + +private: + AAResT AARes; +}; + +template +class LLVMLocalRawAliasIterator + : public LLVMLocalRawAliasIteratorMixin, + AAResT> { + friend LLVMLocalRawAliasIteratorMixin, + AAResT>; + +public: + LLVMLocalRawAliasIterator(AAResT &&AARes, + NonNullPtr> VC + PSR_LIFETIMEBOUND) + : LLVMLocalRawAliasIteratorMixin, + AAResT>(PSR_FWD(AARes), *VC), + VC(VC) {} + +private: + NonNullPtr> VC; +}; +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h b/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h new file mode 100644 index 0000000000..6760a74768 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h @@ -0,0 +1,350 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" +#include "phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Pointer/AliasAnalysisType.h" +#include "phasar/Pointer/AliasInfoTraits.h" +#include "phasar/Pointer/AliasResult.h" +#include "phasar/Pointer/AliasSetOwner.h" +#include "phasar/Pointer/RawAAResult.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Pointer/UnionFindAliasAnalysisType.h" +#include "phasar/Utils/AnalysisProperties.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/IR/Instructions.h" +#include "llvm/Support/TypeName.h" + +#include +#include + +namespace llvm { +class Value; +class Instruction; +class Function; +} // namespace llvm + +namespace psr { + +class LLVMRawAliasSet; +class LLVMProjectIRDB; + +template <> +struct AliasInfoTraits + : DefaultAATraits {}; + +struct LLVMRawAliasSetBase { + using traits_t = AliasInfoTraits; + using n_t = traits_t::n_t; + using v_t = traits_t::v_t; + using AliasSetTy = traits_t::AliasSetTy; + using AliasSetPtrTy = traits_t::AliasSetPtrTy; + using AllocationSiteSetPtrTy = traits_t::AllocationSiteSetPtrTy; + + /// Whether alias sets are reported globally or filtered to the function + /// containing the query instruction. + enum class AnalysisLocality : uint8_t { + /// All aliases across all functions are reported. + Global, + /// Aliases are intersected with variables visible in the querying function + /// (globals + function-local values). + FunctionLocal, + }; + + struct Config { + /// The specific union-find analysis variant to run (default: + /// \c BotCtxIndSens — bottom-up, context- and indirection-sensitive). + UnionFindAliasAnalysisType AType = + UnionFindAliasAnalysisType::BotCtxIndSens; + /// Controls whether alias sets are scoped to the querying function. + AnalysisLocality ALocality = AnalysisLocality::Global; + }; +}; + +[[nodiscard]] llvm::StringRef +to_string(LLVMRawAliasSetBase::AnalysisLocality ALoc) noexcept; + +/// Concrete \c IsAliasInfo implementation backed by a raw alias +/// analysis. Provides convenience constructors to invoke union-find-based +/// analyses, but can be instantiated with *any* analysis result that conforms +/// to \c RawAAResult . +/// +/// Alias sets are materialized lazily on first query and cached per \c ValueId +/// in \c AliasSets. +/// +/// \note When \c AnalysisLocality::FunctionLocal is selected, alias sets are +/// filtered to variables visible in the function that contains the query +/// instruction. The per-\c ValueId cache does **not** account for the +/// instruction context, so the first caller's function wins — do not mix +/// queries to local pointers from different functions for the same value in +/// local mode. +class LLVMRawAliasSet : public LLVMRawAliasSetBase, + public AnalysisPropertiesMixin { +public: + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG, Config Cfg, + ValueCompressor *VC); + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG, Config Cfg) + : LLVMRawAliasSet(IRDB, BaseCG, Cfg, nullptr) {} + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG) + : LLVMRawAliasSet(IRDB, BaseCG, Config{}, nullptr) {} + + template + explicit LLVMRawAliasSet(AAResT &&AARes, + MaybeUniquePtr> VC, + AnalysisProperties Props = {}) + : Props(Props) { + assert(VC != nullptr); + AliasSets.resize(VC->size()); + // XXX: Support locality + + this->AARes = std::make_unique< + AAResultModel>>( + std::move(VC), PSR_FWD(AARes)); + } + + [[nodiscard]] constexpr std::true_type isInterProcedural() const noexcept { + return {}; + }; + + [[nodiscard]] constexpr std::integral_constant + getAliasAnalysisType() const noexcept { + return {}; + }; + + [[nodiscard]] constexpr AnalysisProperties + getAnalysisProperties() const noexcept { + return Props; + } + + [[nodiscard]] constexpr AliasResult alias(v_t V1, v_t V2, n_t I) const { + assert(isValid()); + return AARes->alias(V1, V2, I); + } + + void foreachAliasOf(v_t V, n_t I, + llvm::function_ref WithAlias) const { + assert(isValid()); + AARes->forallAliasesOf(V, I, WithAlias); + } + + [[nodiscard]] AliasSetPtrTy getAliasSet(v_t V, n_t I) { + assert(isValid()); + auto ValId = AARes->VC->getOrNull(V); + if (!ValId) { + return getEmptyAliasSet(); + } + + assert(AliasSets.inbounds(*ValId)); + if (!AliasSets[*ValId]) [[unlikely]] { + AliasSets[*ValId] = AARes->constructAliasSet(*ValId, I, Owner); + } + + return AliasSets[*ValId]; + } + + [[nodiscard]] AllocationSiteSetPtrTy + getReachableAllocationSites(v_t V, bool IntraProcOnly, n_t I) const { + assert(isValid()); + auto ValId = AARes->VC->getOrNull(V); + if (!ValId) { + return std::make_unique(); + } + + return AARes->constructReachableAllocSites(V, *ValId, IntraProcOnly, I); + } + + [[nodiscard]] bool isInReachableAllocationSites( + const llvm::Value *V, const llvm::Value *PotentialValue, + bool IntraProcOnly, const llvm::Instruction *I) const { + assert(isValid()); + if (!psr::isInterestingPointer(V)) { + return false; + } + + if (!psr::isInReachableAllocationSitesTy(V, PotentialValue, + IntraProcOnly)) { + return false; + } + + return alias(V, PotentialValue, I) != AliasResult::NoAlias; + } + + void print(llvm::raw_ostream &OS) const; + void printAsJson(llvm::raw_ostream &OS) const; + + [[nodiscard]] bool isValid() const noexcept { + return AARes != nullptr && AARes->VC != nullptr && + AARes->VC->size() == AliasSets.size(); + } + +private: + struct AAResultConcept { + MaybeUniquePtr> VC; + std::optional> AllocationSites{}; + + AAResultConcept( + MaybeUniquePtr> VC) noexcept + : VC(std::move(VC)) {} + virtual ~AAResultConcept() = default; + + virtual void forallAliasesOf(v_t Ptr, n_t Inst, + llvm::function_ref Callback) = 0; + + virtual AliasResult alias(v_t Ptr1, v_t Ptr2, n_t AtInstruction) = 0; + + virtual AliasSetPtrTy + constructAliasSet(ValueId ValId, n_t Inst, + AliasSetOwner &Owner) = 0; + + virtual AllocationSiteSetPtrTy + constructReachableAllocSites(v_t V, ValueId ValId, bool IntraProcOnly, + n_t Inst) = 0; + + virtual void print(llvm::raw_ostream &OS, Config Cfg) const = 0; + }; + + static bool isPotentialAllocSite(const llvm::Value *Val) { + if (!Val->getType()->isPointerTy()) { + return false; + } + if (llvm::isa(Val)) { + return true; + } + if (const auto *Call = llvm::dyn_cast(Val)) { + return Call->getCalledFunction() && + psr::isHeapAllocatingFunction(Call->getCalledFunction()); + } + return false; + } + + template