Skip to content

Commit 6a94e07

Browse files
authored
Make Compose and Join optional in EdgeFunction (#736)
* Make the compose and join functions in the EdgeFunction implementations optional, if the IDETabulkationProblem impl provides extend and combine * Remove obsolete compose/join dummies from IDEFeatureTaintAnalysis * Use explicit vtable thunks in EdgeFunction to allow function merging + minor * Remove unnecessary inline
1 parent 4f502b1 commit 6a94e07

8 files changed

Lines changed: 236 additions & 215 deletions

File tree

include/phasar/DataFlow/IfdsIde/EdgeFunction.h

Lines changed: 150 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "phasar/DataFlow/IfdsIde/EdgeFunctionSingletonCache.h"
1414
#include "phasar/Utils/ByRef.h"
1515
#include "phasar/Utils/EmptyBaseOptimizationUtils.h"
16+
#include "phasar/Utils/ErrorFwd.h"
1617
#include "phasar/Utils/Macros.h"
1718
#include "phasar/Utils/TypeTraits.h"
1819

@@ -37,14 +38,23 @@ template <typename L> class EdgeFunction;
3738
template <typename EF> class EdgeFunctionRef;
3839

3940
template <typename T>
40-
concept IsEdgeFunction =
41-
requires(const T &EF, const EdgeFunction<typename T::l_t> &TEEF,
42-
EdgeFunctionRef<T> CEF, typename T::l_t Src) {
43-
typename T::l_t;
44-
{ EF.computeTarget(Src) } -> std::convertible_to<typename T::l_t>;
45-
{ T::compose(CEF, TEEF) } -> std::same_as<EdgeFunction<typename T::l_t>>;
46-
{ T::join(CEF, TEEF) } -> std::same_as<EdgeFunction<typename T::l_t>>;
47-
};
41+
concept IsEdgeFunction = requires(const T &EF, typename T::l_t Src) {
42+
typename T::l_t;
43+
{ EF.computeTarget(Src) } -> std::convertible_to<typename T::l_t>;
44+
};
45+
46+
template <typename T>
47+
concept HasEFCompose = requires(EdgeFunctionRef<T> EFRef,
48+
const EdgeFunction<typename T::l_t> &EF) {
49+
{
50+
T::compose(EFRef, EF)
51+
} -> std::convertible_to<EdgeFunction<typename T::l_t>>;
52+
};
53+
template <typename T>
54+
concept HasEFJoin = requires(EdgeFunctionRef<T> EFRef,
55+
const EdgeFunction<typename T::l_t> &EF) {
56+
{ T::join(EFRef, EF) } -> std::convertible_to<EdgeFunction<typename T::l_t>>;
57+
};
4858

4959
enum class EdgeFunctionAllocationPolicy {
5060
SmallObjectOptimized,
@@ -100,29 +110,31 @@ class EdgeFunctionBase {
100110
/// \brief Non-null reference to an edge function that is guarenteed to be
101111
/// managed by an EdgeFunction object.
102112
template <typename EF>
103-
class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase {
113+
class [[gsl::Pointer(EF)]] EdgeFunctionRef final : EdgeFunctionBase {
104114
template <typename L> friend class EdgeFunction;
105115

106116
public:
107117
using l_t = typename EF::l_t;
108118

109-
EdgeFunctionRef(const EdgeFunctionRef &) noexcept = default;
110-
EdgeFunctionRef &operator=(const EdgeFunctionRef &) noexcept = default;
111-
~EdgeFunctionRef() = default;
112-
113-
const EF *operator->() const noexcept { return getPtr<EF>(Instance); }
114-
const EF *get() const noexcept { return getPtr<EF>(Instance); }
115-
const EF &operator*() const noexcept { return *getPtr<EF>(Instance); }
119+
[[nodiscard]] constexpr const EF *operator->() const noexcept {
120+
return getPtr<EF>(Instance);
121+
}
122+
[[nodiscard]] constexpr const EF *get() const noexcept {
123+
return getPtr<EF>(Instance);
124+
}
125+
[[nodiscard]] constexpr const EF &operator*() const noexcept {
126+
return *getPtr<EF>(Instance);
127+
}
116128

117-
[[nodiscard]] bool isCached() const noexcept {
129+
[[nodiscard]] constexpr bool isCached() const noexcept {
118130
if constexpr (IsSOOCandidate<EF>) {
119131
return false;
120132
} else {
121133
return IsCached;
122134
}
123135
}
124136

125-
[[nodiscard]] EdgeFunctionSingletonCache<EF> *
137+
[[nodiscard]] constexpr EdgeFunctionSingletonCache<EF> *
126138
getCacheOrNull() const noexcept {
127139
if (isCached()) {
128140
return static_cast<const CachedRefCounted<EF> *>(Instance)->Cache;
@@ -148,46 +160,46 @@ class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase {
148160
template <typename L>
149161
// -- combined copy and move assignment
150162
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
151-
class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase {
163+
class [[clang::trivial_abi, gsl::Owner]] EdgeFunction final : EdgeFunctionBase {
152164
public:
153165
using l_t = L;
154166

155167
// --- Constructors
156168

157169
/// Default-initializes the edge-function with nullptr
158-
EdgeFunction() noexcept = default;
170+
constexpr EdgeFunction() noexcept = default;
159171
/// Default-initializes the edge-function with nullptr
160-
EdgeFunction(std::nullptr_t) noexcept : EdgeFunction() {}
172+
constexpr EdgeFunction(std::nullptr_t) noexcept : EdgeFunction() {}
161173
/// Copy constructor. Increments the ref-count, if not small-object-optimized.
162174
EdgeFunction(const EdgeFunction &Other) noexcept
163175
: EdgeFunction(Other.EF, Other.VTAndHeapAlloc) {}
164176
/// Move constructor. Does not increment the ref-count, but instead leaves the
165177
/// moved-from edge function in the nullptr state.
166-
EdgeFunction(EdgeFunction &&Other) noexcept
178+
constexpr EdgeFunction(EdgeFunction &&Other) noexcept
167179
: EF(std::exchange(Other.EF, nullptr)),
168180
VTAndHeapAlloc(
169181
std::exchange(Other.VTAndHeapAlloc, decltype(VTAndHeapAlloc){})) {}
170182

171183
/// Standard swap; does not affect ref-counts
172-
void swap(EdgeFunction &Other) noexcept {
184+
constexpr void swap(EdgeFunction &Other) noexcept {
173185
std::swap(EF, Other.EF);
174186
std::swap(VTAndHeapAlloc, Other.VTAndHeapAlloc);
175187
}
176188
/// Standard swap; does not affect ref-counts
177-
friend void swap(EdgeFunction &LHS, EdgeFunction &RHS) noexcept {
189+
constexpr friend void swap(EdgeFunction &LHS, EdgeFunction &RHS) noexcept {
178190
LHS.swap(RHS);
179191
}
180192

181193
/// Combined copy- and move assignment. If the assigned-to edge function is
182194
/// not null, invokes the destructor on it, before overwriting its content.
183-
EdgeFunction &operator=(EdgeFunction Other) noexcept {
195+
constexpr EdgeFunction &operator=(EdgeFunction Other) noexcept {
184196
std::destroy_at(this);
185197
return *new (this) EdgeFunction(std::move(Other)); // NOLINT
186198
}
187199
/// Null-assignment operator. Decrements the ref-count if not
188200
/// small-object-optimized or already null. Leaves the assigned-to edge
189201
/// function in the nullptr state.
190-
EdgeFunction &operator=(std::nullptr_t) noexcept {
202+
constexpr EdgeFunction &operator=(std::nullptr_t) noexcept {
191203
std::destroy_at(this);
192204
return *new (this) EdgeFunction(); // NOLINT
193205
}
@@ -581,7 +593,9 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase {
581593

582594
/// Gets an opaque identifier for this edge function. Only meant for
583595
/// comparisons of object-identity. Do not dereference!
584-
[[nodiscard]] const void *getOpaqueValue() const noexcept { return EF; }
596+
[[nodiscard]] constexpr const void *getOpaqueValue() const noexcept {
597+
return EF;
598+
}
585599

586600
/// Gets the cache where this edge function is being cached in. If this edge
587601
/// function is not cached (i.e., isCached() returns false), returns nullptr.
@@ -646,83 +660,117 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase {
646660
// NOLINTEND(readability-identifier-naming)
647661
};
648662

663+
template <typename ConcreteEF>
664+
static l_t computeTargetThunk(const void *EF, ByConstRef<l_t> Source) {
665+
return getPtr<ConcreteEF>(EF)->computeTarget(Source);
666+
}
667+
668+
template <typename ConcreteEF>
669+
static EdgeFunction composeThunk(const void *EF, const EdgeFunction &SecondEF,
670+
AllocationPolicy Policy) {
671+
if constexpr (HasEFCompose<ConcreteEF>) {
672+
return ConcreteEF::compose(
673+
EdgeFunctionRef<ConcreteEF>(
674+
EF, Policy == AllocationPolicy::CustomHeapAllocated),
675+
SecondEF);
676+
} else {
677+
composeEFPureVirtualError(llvm::getTypeName<ConcreteEF>(),
678+
llvm::getTypeName<l_t>());
679+
}
680+
}
681+
682+
template <typename ConcreteEF>
683+
static EdgeFunction joinThunk(const void *EF, const EdgeFunction &OtherEF,
684+
AllocationPolicy Policy) {
685+
if constexpr (HasEFJoin<ConcreteEF>) {
686+
return ConcreteEF::join(
687+
EdgeFunctionRef<ConcreteEF>(
688+
EF, Policy == AllocationPolicy::CustomHeapAllocated),
689+
OtherEF);
690+
} else {
691+
joinEFPureVirtualError(llvm::getTypeName<ConcreteEF>(),
692+
llvm::getTypeName<l_t>());
693+
}
694+
}
695+
696+
template <typename ConcreteEF>
697+
static bool equalsThunk(const void *EF1, const void *EF2) noexcept {
698+
static_assert(IsEqualityComparable<ConcreteEF> ||
699+
std::is_empty_v<ConcreteEF>,
700+
"An EdgeFunction must be equality comparable with "
701+
"operator==. Only if the type is empty, i.e. has no "
702+
"members, the comparison can be inferred.");
703+
if constexpr (IsEqualityComparable<ConcreteEF>) {
704+
return *getPtr<ConcreteEF>(EF1) == *getPtr<ConcreteEF>(EF2);
705+
} else {
706+
return true;
707+
}
708+
}
709+
710+
template <typename ConcreteEF>
711+
static void printThunk(const void *EF, llvm::raw_ostream &OS) {
712+
if constexpr (is_llvm_printable_v<ConcreteEF>) {
713+
OS << *getPtr<ConcreteEF>(EF);
714+
} else {
715+
OS << llvm::getTypeName<ConcreteEF>();
716+
}
717+
}
718+
719+
template <typename ConcreteEF>
720+
static bool isConstantThunk(const void *EF) noexcept {
721+
if constexpr (HasIsConstant<ConcreteEF>) {
722+
static_assert(
723+
std::is_nothrow_invocable_v<decltype(&ConcreteEF::isConstant),
724+
const ConcreteEF &>,
725+
"The function isConstant() must be noexcept!");
726+
return getPtr<ConcreteEF>(EF)->isConstant();
727+
} else {
728+
return false;
729+
}
730+
}
731+
732+
template <typename ConcreteEF>
733+
static void destroyThunk(const void *EF, AllocationPolicy Policy) noexcept {
734+
if constexpr (!IsSOOCandidate<ConcreteEF>) {
735+
if (Policy != AllocationPolicy::CustomHeapAllocated) {
736+
assert(Policy == AllocationPolicy::DefaultHeapAllocated);
737+
delete static_cast<const RefCounted<ConcreteEF> *>(EF);
738+
} else {
739+
auto CEF = static_cast<const CachedRefCounted<ConcreteEF> *>(EF);
740+
CEF->Cache->erase(CEF->Value);
741+
delete CEF;
742+
}
743+
}
744+
}
745+
746+
template <typename ConcreteEF>
747+
static size_t getHashCodeThunk(const void *EF, const void *VT) noexcept {
748+
if constexpr (is_std_hashable_v<ConcreteEF>) {
749+
return std::hash<ConcreteEF>{}(*getPtr<ConcreteEF>(EF));
750+
} else if constexpr (is_llvm_hashable_v<ConcreteEF>) {
751+
using llvm::hash_value;
752+
return hash_value(*getPtr<ConcreteEF>(EF));
753+
} else {
754+
return llvm::hash_combine(EF, VT);
755+
}
756+
}
757+
758+
template <typename ConcreteEF>
759+
static size_t depthThunk(const void *EF) noexcept {
760+
if constexpr (HasDepth<ConcreteEF>) {
761+
return getPtr<ConcreteEF>(EF)->depth();
762+
} else {
763+
return 1;
764+
}
765+
}
766+
649767
template <typename ConcreteEF>
650768
static constexpr VTable VTableFor = {
651-
[](const void *EF, ByConstRef<l_t> Source) {
652-
return getPtr<ConcreteEF>(EF)->computeTarget(Source);
653-
},
654-
[](const void *EF, const EdgeFunction &SecondEF,
655-
AllocationPolicy Policy) {
656-
return ConcreteEF::compose(
657-
EdgeFunctionRef<ConcreteEF>(
658-
EF, Policy == AllocationPolicy::CustomHeapAllocated),
659-
SecondEF);
660-
},
661-
[](const void *EF, const EdgeFunction &OtherEF, AllocationPolicy Policy) {
662-
return ConcreteEF::join(
663-
EdgeFunctionRef<ConcreteEF>(
664-
EF, Policy == AllocationPolicy::CustomHeapAllocated),
665-
OtherEF);
666-
},
667-
[](const void *EF1, const void *EF2) noexcept {
668-
static_assert(IsEqualityComparable<ConcreteEF> ||
669-
std::is_empty_v<ConcreteEF>,
670-
"An EdgeFunction must be equality comparable with "
671-
"operator==. Only if the type is empty, i.e. has no "
672-
"members, the comparison can be inferred.");
673-
if constexpr (IsEqualityComparable<ConcreteEF>) {
674-
return *getPtr<ConcreteEF>(EF1) == *getPtr<ConcreteEF>(EF2);
675-
} else {
676-
return true;
677-
}
678-
},
679-
[](const void *EF, llvm::raw_ostream &OS) {
680-
if constexpr (is_llvm_printable_v<ConcreteEF>) {
681-
OS << *getPtr<ConcreteEF>(EF);
682-
} else {
683-
OS << llvm::getTypeName<ConcreteEF>();
684-
}
685-
},
686-
[](const void *EF) noexcept {
687-
if constexpr (HasIsConstant<ConcreteEF>) {
688-
static_assert(
689-
std::is_nothrow_invocable_v<decltype(&ConcreteEF::isConstant),
690-
const ConcreteEF &>,
691-
"The function isConstant() must be noexcept!");
692-
return getPtr<ConcreteEF>(EF)->isConstant();
693-
} else {
694-
return false;
695-
}
696-
},
697-
[](const void *EF, AllocationPolicy Policy) noexcept {
698-
if constexpr (!IsSOOCandidate<ConcreteEF>) {
699-
if (Policy != AllocationPolicy::CustomHeapAllocated) {
700-
assert(Policy == AllocationPolicy::DefaultHeapAllocated);
701-
delete static_cast<const RefCounted<ConcreteEF> *>(EF);
702-
} else {
703-
auto CEF = static_cast<const CachedRefCounted<ConcreteEF> *>(EF);
704-
CEF->Cache->erase(CEF->Value);
705-
delete CEF;
706-
}
707-
}
708-
},
709-
[](const void *EF, const void *VT) noexcept -> size_t {
710-
if constexpr (is_std_hashable_v<ConcreteEF>) {
711-
return std::hash<ConcreteEF>{}(*getPtr<ConcreteEF>(EF));
712-
} else if constexpr (is_llvm_hashable_v<ConcreteEF>) {
713-
using llvm::hash_value;
714-
return hash_value(*getPtr<ConcreteEF>(EF));
715-
} else {
716-
return llvm::hash_combine(EF, VT);
717-
}
718-
},
719-
[](const void *EF) noexcept -> size_t {
720-
if constexpr (HasDepth<ConcreteEF>) {
721-
return getPtr<ConcreteEF>(EF)->depth();
722-
} else {
723-
return 1;
724-
}
725-
},
769+
&computeTargetThunk<ConcreteEF>, &composeThunk<ConcreteEF>,
770+
&joinThunk<ConcreteEF>, &equalsThunk<ConcreteEF>,
771+
&printThunk<ConcreteEF>, &isConstantThunk<ConcreteEF>,
772+
&destroyThunk<ConcreteEF>, &getHashCodeThunk<ConcreteEF>,
773+
&depthThunk<ConcreteEF>,
726774
};
727775

728776
// Utility ctor for (copy) construction. Increments the ref-count if

0 commit comments

Comments
 (0)