Jlm
PointerObjectSet.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023, 2024 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
6 #ifndef JLM_LLVM_OPT_ALIAS_ANALYSES_POINTEROBJECTSET_HPP
7 #define JLM_LLVM_OPT_ALIAS_ANALYSES_POINTEROBJECTSET_HPP
8 
14 #include <jlm/util/common.hpp>
15 #include <jlm/util/GraphWriter.hpp>
16 #include <jlm/util/HashSet.hpp>
17 #include <jlm/util/Math.hpp>
18 
19 #include <cstdint>
20 #include <optional>
21 #include <unordered_map>
22 #include <variant>
23 #include <vector>
24 
25 namespace jlm::llvm::aa
26 {
27 
28 enum class PointerObjectKind : uint8_t
29 {
30  // Registers can not be pointed to, only point.
31  Register = 0,
32  // All other pointer objects represent storage instances in memory
36  // Represents functions, they can not point to any memory objects.
38  // Represents functions and global variables imported from other modules.
40 
41  COUNT
42 };
43 
44 using PointerObjectIndex = uint32_t;
45 
51 class PointerObjectSet final
52 {
58  struct PointerObject final
59  {
60  // The kind of pointer object
62 
63  // If set, this pointer object may point to other pointer objects.
64  // If unset, the analysis should make no attempt at tracking what this PointerObject may target.
65  // The final PointsToGraph will not have any outgoing edges for this object.
66  const uint8_t CanPointFlag : 1;
67 
68  // This memory object's address is known outside the module.
69  // Can only be true on memory objects.
70  uint8_t HasEscaped : 1;
71 
72  // Any pointee of this PointerObject has its address escaped.
73  // The unification root is the source of truth for this flag!
74  // This flag is implied by HasEscaped
75  uint8_t PointeesEscaping : 1;
76 
77  // If set, this pointer object is pointing to external.
78  // The unification root is the source of truth for this flag!
79  // This flag is implied by HasEscaped
80  uint8_t PointsToExternal : 1;
81 
82  // If set, any pointee of this object should point to external.
83  // The unification root is the source of truth for this flag!
84  uint8_t StoredAsScalar : 1;
85 
86  // If set, any pointee of this object should mark its pointees as escaping.
87  // The unification root is the source of truth for this flag!
88  uint8_t LoadedAsScalar : 1;
89 
90  explicit PointerObject(PointerObjectKind kind, bool canPoint)
91  : Kind(kind),
92  CanPointFlag(canPoint),
93  HasEscaped(0),
96  StoredAsScalar(0),
98  {
100 
101  // Ensure that certain kinds of PointerObject always CanPoint or never CanPoint
103  JLM_ASSERT(!CanPoint());
104  else if (kind == PointerObjectKind::Register)
105  JLM_ASSERT(CanPoint());
106 
107  if (!CanPoint())
108  {
109  // No attempt is made at tracking pointees, so use these flags to inform others
110  PointeesEscaping = 1;
111  PointsToExternal = 1;
112  }
113  }
114 
122  [[nodiscard]] bool
123  CanTrackPointeesImplicitly() const noexcept
124  {
126  }
127 
133  [[nodiscard]] bool
134  CanPoint() const noexcept
135  {
136  return CanPointFlag;
137  }
138 
143  [[nodiscard]] bool
144  IsRegister() const noexcept
145  {
147  }
148  };
149 
150  // All PointerObjects in the set
151  std::vector<PointerObject> PointerObjects_;
152 
153  // The parent of each PointerObject in the disjoint-set forest. Roots are their own parent.
154  // Marked as mutable to allow path compression in const qualified methods.
155  mutable std::vector<PointerObjectIndex> PointerObjectParents_;
156 
157  // Metadata enabling union by rank, where rank is an upper bound for tree height
158  // Size of a disjoint set is at least 2^rank, making a uint8_t plenty big enough.
159  std::vector<uint8_t> PointerObjectRank_;
160 
161  // For each PointerObject, a set of the other PointerObjects it points to
162  // Only unification roots may have a non-empty set,
163  // other PointerObjects refer to their root's set.
164  std::vector<util::HashSet<PointerObjectIndex>> PointsToSets_;
165 
166  // Mapping from register to PointerObject
167  // Unlike the other maps, several rvsdg::output* can share register PointerObject
168  std::unordered_map<const rvsdg::Output *, PointerObjectIndex> RegisterMap_;
169 
170  std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> AllocaMap_;
171 
172  std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> MallocMap_;
173 
174  std::unordered_map<const rvsdg::DeltaNode *, PointerObjectIndex> GlobalMap_;
175 
177 
178  std::unordered_map<const LlvmGraphImport *, PointerObjectIndex> ImportMap_;
179 
180  // How many items have been attempted added to explicit points-to sets
182 
183  // How many pointees have been removed from points-to sets.
184  // Explicit pointees can only be removed through unification, and the remove method
186 
190  [[nodiscard]] PointerObjectIndex
191  AddPointerObject(PointerObjectKind kind, bool canPoint);
192 
197  template<typename NewPointeeFunctor>
198  bool
200  PointerObjectIndex superset,
201  PointerObjectIndex subset,
202  NewPointeeFunctor & onNewPointee);
203 
204 public:
205  PointerObjectSet() = default;
206 
207  [[nodiscard]] size_t
208  NumPointerObjects() const noexcept;
209 
213  [[nodiscard]] size_t
214  NumPointerObjectsOfKind(PointerObjectKind kind) const noexcept;
215 
219  [[nodiscard]] size_t
220  NumRegisterPointerObjects() const noexcept;
221 
222  [[nodiscard]] size_t
223  NumMemoryPointerObjects() const noexcept;
224 
225  [[nodiscard]] size_t
226  NumMemoryPointerObjectsCanPoint() const noexcept;
227 
234  [[nodiscard]] PointerObjectIndex
235  CreateRegisterPointerObject(const rvsdg::Output & rvsdgOutput);
236 
242  [[nodiscard]] PointerObjectIndex
243  GetRegisterPointerObject(const rvsdg::Output & rvsdgOutput) const;
244 
251  [[nodiscard]] std::optional<PointerObjectIndex>
252  TryGetRegisterPointerObject(const rvsdg::Output & rvsdgOutput) const;
253 
260  void
262  const rvsdg::Output & rvsdgOutput,
263  PointerObjectIndex pointerObject);
264 
272  [[nodiscard]] PointerObjectIndex
274 
275  [[nodiscard]] PointerObjectIndex
276  CreateAllocaMemoryObject(const rvsdg::SimpleNode & allocaNode, bool canPoint);
277 
278  [[nodiscard]] PointerObjectIndex
279  CreateMallocMemoryObject(const rvsdg::SimpleNode & mallocNode, bool canPoint);
280 
281  [[nodiscard]] PointerObjectIndex
282  CreateGlobalMemoryObject(const rvsdg::DeltaNode & deltaNode, bool canPoint);
283 
290  [[nodiscard]] PointerObjectIndex
291  CreateFunctionMemoryObject(const rvsdg::LambdaNode & lambdaNode);
292 
298  [[nodiscard]] PointerObjectIndex
299  GetFunctionMemoryObject(const rvsdg::LambdaNode & lambdaNode) const;
300 
306  [[nodiscard]] const rvsdg::LambdaNode &
308 
309  [[nodiscard]] PointerObjectIndex
310  CreateImportMemoryObject(const LlvmGraphImport & importNode, bool canPoint);
311 
312  const std::unordered_map<const rvsdg::Output *, PointerObjectIndex> &
313  GetRegisterMap() const noexcept;
314 
315  const std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> &
316  GetAllocaMap() const noexcept;
317 
318  const std::unordered_map<const rvsdg::SimpleNode *, PointerObjectIndex> &
319  GetMallocMap() const noexcept;
320 
321  const std::unordered_map<const rvsdg::DeltaNode *, PointerObjectIndex> &
322  GetGlobalMap() const noexcept;
323 
324  const util::BijectiveMap<const rvsdg::LambdaNode *, PointerObjectIndex> &
325  GetFunctionMap() const noexcept;
326 
327  const std::unordered_map<const LlvmGraphImport *, PointerObjectIndex> &
328  GetImportMap() const noexcept;
329 
333  [[nodiscard]] PointerObjectKind
334  GetPointerObjectKind(PointerObjectIndex index) const noexcept;
335 
339  [[nodiscard]] bool
340  CanPoint(PointerObjectIndex index) const noexcept;
341 
345  [[nodiscard]] bool
346  IsPointerObjectRegister(PointerObjectIndex index) const noexcept;
347 
351  [[nodiscard]] bool
352  HasEscaped(PointerObjectIndex index) const noexcept;
353 
360  bool
362 
366  [[nodiscard]] bool
367  HasPointeesEscaping(PointerObjectIndex index) const noexcept;
368 
374  bool
376 
380  [[nodiscard]] bool
381  IsPointingToExternal(PointerObjectIndex index) const noexcept;
382 
388  bool
390 
396  [[nodiscard]] bool
397  CanTrackPointeesImplicitly(PointerObjectIndex index) const noexcept;
398 
403  bool
405 
410  [[nodiscard]] bool
411  IsStoredAsScalar(PointerObjectIndex index) const noexcept;
412 
417  bool
419 
424  [[nodiscard]] bool
425  IsLoadedAsScalar(PointerObjectIndex index) const noexcept;
426 
431  [[nodiscard]] PointerObjectIndex
432  GetUnificationRoot(PointerObjectIndex index) const noexcept;
433 
437  [[nodiscard]] bool
438  IsUnificationRoot(PointerObjectIndex index) const noexcept;
439 
451 
457  [[nodiscard]] const util::HashSet<PointerObjectIndex> &
458  GetPointsToSet(PointerObjectIndex index) const;
459 
469  bool
471 
481  bool
483 
488  bool
490  PointerObjectIndex superset,
491  PointerObjectIndex subset,
492  util::HashSet<PointerObjectIndex> & newPointees);
493 
498  void
500 
506  bool
507  IsPointingTo(PointerObjectIndex pointer, PointerObjectIndex pointee) const;
508 
514  [[nodiscard]] std::unique_ptr<PointerObjectSet>
515  Clone() const;
516 
525  [[nodiscard]] bool
526  HasIdenticalSolAs(const PointerObjectSet & other) const;
527 
533  [[nodiscard]] size_t
534  GetNumSetInsertionAttempts() const noexcept;
535 
540  [[nodiscard]] size_t
541  GetNumExplicitPointeesRemoved() const noexcept;
542 };
543 
550 {
553 
554 public:
556  : Superset_(superset),
557  Subset_(subset)
558  {}
559 
563  [[nodiscard]] PointerObjectIndex
564  GetSuperset() const noexcept
565  {
566  return Superset_;
567  }
568 
572  void
574  {
575  Superset_ = superset;
576  }
577 
581  [[nodiscard]] PointerObjectIndex
582  GetSubset() const noexcept
583  {
584  return Subset_;
585  }
586 
590  void
592  {
593  Subset_ = subset;
594  }
595 
600  bool
601  ApplyDirectly(PointerObjectSet & set);
602 };
603 
611 class StoreConstraint final
612 {
615 
616 public:
618  : Pointer_(pointer),
619  Value_(value)
620  {}
621 
625  [[nodiscard]] PointerObjectIndex
626  GetValue() const noexcept
627  {
628  return Value_;
629  }
630 
634  void
636  {
637  Value_ = value;
638  }
639 
643  [[nodiscard]] PointerObjectIndex
644  GetPointer() const noexcept
645  {
646  return Pointer_;
647  }
648 
652  void
654  {
655  Pointer_ = pointer;
656  }
657 
662  bool
663  ApplyDirectly(PointerObjectSet & set);
664 };
665 
673 class LoadConstraint final
674 {
677 
678 public:
680  : Value_(value),
681  Pointer_(pointer)
682  {}
683 
687  [[nodiscard]] PointerObjectIndex
688  GetValue() const noexcept
689  {
690  return Value_;
691  }
692 
696  void
698  {
699  Value_ = value;
700  }
701 
705  [[nodiscard]] PointerObjectIndex
706  GetPointer() const noexcept
707  {
708  return Pointer_;
709  }
710 
714  void
716  {
717  Pointer_ = pointer;
718  }
719 
724  bool
725  ApplyDirectly(PointerObjectSet & set);
726 };
727 
745 {
750 
755 
756 public:
758  : Pointer_(pointer),
759  CallNode_(callNode)
760  {
761  JLM_ASSERT(is<CallOperation>(callNode.GetOperation()));
762  }
763 
767  [[nodiscard]] PointerObjectIndex
768  GetPointer() const noexcept
769  {
770  return Pointer_;
771  }
772 
776  void
778  {
779  Pointer_ = pointer;
780  }
781 
785  [[nodiscard]] const rvsdg::SimpleNode &
786  GetCallNode() const noexcept
787  {
788  return CallNode_;
789  }
790 
795  bool
796  ApplyDirectly(PointerObjectSet & set);
797 };
798 
804 {
805 public:
807 
813  static bool
814  PropagateEscapedFlagsDirectly(PointerObjectSet & set);
815 };
816 
823 {
824 public:
826 
832  static bool
833  PropagateEscapedFunctionsDirectly(PointerObjectSet & set);
834 };
835 
844 {
845 public:
847  std::variant<SupersetConstraint, StoreConstraint, LoadConstraint, FunctionCallConstraint>;
848 
850  {
856  LeastRecentlyFired,
857 
865  TwoPhaseLeastRecentlyFired,
866 
875  TopologicalSort,
876 
881  FirstInFirstOut,
882 
887  LastInFirstOut
888  };
889 
890  [[nodiscard]] static const char *
891  WorklistSolverPolicyToString(WorklistSolverPolicy policy);
892 
897  {
899  : Policy(policy)
900  {}
901 
906 
910  size_t NumWorkItemsPopped{};
911 
916  size_t NumWorkItemNewPointees{};
917 
922  std::optional<size_t> NumTopologicalWorklistSweeps;
923 
929  std::optional<size_t> NumOnlineCyclesDetected;
930 
934  std::optional<size_t> NumOnlineCycleUnifications;
935 
939  std::optional<size_t> NumHybridCycleUnifications;
940 
947  std::optional<size_t> NumLazyCyclesDetectionAttempts;
948  std::optional<size_t> NumLazyCyclesDetected;
949  std::optional<size_t> NumLazyCycleUnifications;
950 
955  std::optional<size_t> NumPipExplicitPointeesRemoved;
956  };
957 
959  : Set_(set),
960  Constraints_(),
961  ConstraintSetFrozen_(false)
962  {}
963 
965 
967 
969  operator=(const PointerObjectConstraintSet & other) = delete;
970 
973 
980  [[nodiscard]] bool
981  IsFrozen() const noexcept;
982 
988  void
989  AddPointerPointeeConstraint(PointerObjectIndex pointer, PointerObjectIndex pointee);
990 
995  void
996  AddPointsToExternalConstraint(PointerObjectIndex pointer);
997 
1003  void
1004  AddRegisterContentEscapedConstraint(PointerObjectIndex registerIndex);
1005 
1010  void
1011  AddConstraint(ConstraintVariant c);
1012 
1016  [[nodiscard]] const std::vector<ConstraintVariant> &
1017  GetConstraints() const noexcept;
1018 
1022  [[nodiscard]] size_t
1023  NumBaseConstraints() const noexcept;
1024 
1031  [[nodiscard]] std::pair<size_t, size_t>
1032  NumFlagConstraints() const noexcept;
1033 
1038  util::graph::Graph &
1039  DrawSubsetGraph(util::graph::Writer & writer) const;
1040 
1066  size_t
1067  PerformOfflineVariableSubstitution(bool storeRefCycleUnificationRoot);
1068 
1077  size_t
1078  NormalizeConstraints();
1079 
1099  SolveUsingWorklist(
1100  WorklistSolverPolicy policy,
1101  bool enableOnlineCycleDetection,
1102  bool enableHybridCycleDetection,
1103  bool enableLazyCycleDetection,
1104  bool enableDifferencePropagation,
1105  bool enablePreferImplicitPropation);
1106 
1113  size_t
1114  SolveNaively();
1115 
1121  std::pair<std::unique_ptr<PointerObjectSet>, std::unique_ptr<PointerObjectConstraintSet>>
1122  Clone() const;
1123 
1124 private:
1140  std::tuple<size_t, std::vector<util::HashSet<PointerObjectIndex>>, std::vector<bool>>
1141  CreateOvsSubsetGraph();
1142 
1154  template<
1155  typename Worklist,
1156  bool EnableOnlineCycleDetection,
1157  bool EnableHybridCycleDetection,
1158  bool EnableLazyCycleDetection,
1159  bool EnableDifferencePropagation,
1160  bool EnablePreferImplicitPointees>
1161  void
1162  RunWorklistSolver(WorklistStatistics & statistics);
1163 
1164  // The PointerObjectSet being built upon
1166 
1167  // Lists of all constraints, of all different types
1168  std::vector<ConstraintVariant> Constraints_;
1169 
1170  // When true, no new constraints can be added.
1171  // Only offline processing is allowed to modify the constraint set.
1172  bool ConstraintSetFrozen_;
1173 
1174  // Offline Variable Substitution can determine that all pointees of a node p,
1175  // should be unified together, possibly with some other PointerObjects.
1176  // This happens when *p is in a cycle with regular nodes
1177  std::unordered_map<PointerObjectIndex, PointerObjectIndex> RefNodeUnificationRoot_;
1178 };
1179 
1180 } // namespace jlm::llvm::aa
1181 
1182 #endif
void SetPointer(PointerObjectIndex pointer)
FunctionCallConstraint(PointerObjectIndex pointer, const rvsdg::SimpleNode &callNode)
PointerObjectIndex GetPointer() const noexcept
const rvsdg::SimpleNode & GetCallNode() const noexcept
PointerObjectIndex GetPointer() const noexcept
void SetValue(PointerObjectIndex value)
PointerObjectIndex GetValue() const noexcept
void SetPointer(PointerObjectIndex pointer)
LoadConstraint(PointerObjectIndex value, PointerObjectIndex pointer)
PointerObjectConstraintSet(PointerObjectConstraintSet &&other)=delete
PointerObjectConstraintSet & operator=(const PointerObjectConstraintSet &other)=delete
PointerObjectConstraintSet & operator=(PointerObjectConstraintSet &&other)=delete
PointerObjectConstraintSet(const PointerObjectConstraintSet &other)=delete
std::variant< SupersetConstraint, StoreConstraint, LoadConstraint, FunctionCallConstraint > ConstraintVariant
const util::BijectiveMap< const rvsdg::LambdaNode *, PointerObjectIndex > & GetFunctionMap() const noexcept
PointerObjectIndex CreateMallocMemoryObject(const rvsdg::SimpleNode &mallocNode, bool canPoint)
PointerObjectIndex UnifyPointerObjects(PointerObjectIndex object1, PointerObjectIndex object2)
PointerObjectIndex AddPointerObject(PointerObjectKind kind, bool canPoint)
size_t NumMemoryPointerObjects() const noexcept
bool IsPointingTo(PointerObjectIndex pointer, PointerObjectIndex pointee) const
PointerObjectIndex CreateDummyRegisterPointerObject()
PointerObjectKind GetPointerObjectKind(PointerObjectIndex index) const noexcept
size_t GetNumSetInsertionAttempts() const noexcept
std::vector< PointerObjectIndex > PointerObjectParents_
const std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > & GetMallocMap() const noexcept
bool IsPointerObjectRegister(PointerObjectIndex index) const noexcept
size_t GetNumExplicitPointeesRemoved() const noexcept
std::unordered_map< const rvsdg::Output *, PointerObjectIndex > RegisterMap_
PointerObjectIndex CreateGlobalMemoryObject(const rvsdg::DeltaNode &deltaNode, bool canPoint)
void MapRegisterToExistingPointerObject(const rvsdg::Output &rvsdgOutput, PointerObjectIndex pointerObject)
size_t NumRegisterPointerObjects() const noexcept
const util::HashSet< PointerObjectIndex > & GetPointsToSet(PointerObjectIndex index) const
bool HasPointeesEscaping(PointerObjectIndex index) const noexcept
bool PropagateNewPointees(PointerObjectIndex superset, PointerObjectIndex subset, NewPointeeFunctor &onNewPointee)
bool IsLoadedAsScalar(PointerObjectIndex index) const noexcept
void RemoveAllPointees(PointerObjectIndex index)
size_t NumMemoryPointerObjectsCanPoint() const noexcept
std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > AllocaMap_
PointerObjectIndex CreateRegisterPointerObject(const rvsdg::Output &rvsdgOutput)
bool CanPoint(PointerObjectIndex index) const noexcept
bool AddToPointsToSet(PointerObjectIndex pointer, PointerObjectIndex pointee)
PointerObjectIndex GetRegisterPointerObject(const rvsdg::Output &rvsdgOutput) const
std::vector< uint8_t > PointerObjectRank_
bool MarkAsLoadingAsScalar(PointerObjectIndex index)
const std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > & GetAllocaMap() const noexcept
std::unordered_map< const rvsdg::DeltaNode *, PointerObjectIndex > GlobalMap_
std::unique_ptr< PointerObjectSet > Clone() const
bool IsPointingToExternal(PointerObjectIndex index) const noexcept
std::unordered_map< const rvsdg::SimpleNode *, PointerObjectIndex > MallocMap_
bool HasIdenticalSolAs(const PointerObjectSet &other) const
bool IsStoredAsScalar(PointerObjectIndex index) const noexcept
bool HasEscaped(PointerObjectIndex index) const noexcept
bool MarkAsPointeesEscaping(PointerObjectIndex index)
const std::unordered_map< const LlvmGraphImport *, PointerObjectIndex > & GetImportMap() const noexcept
const std::unordered_map< const rvsdg::DeltaNode *, PointerObjectIndex > & GetGlobalMap() const noexcept
std::vector< util::HashSet< PointerObjectIndex > > PointsToSets_
PointerObjectIndex CreateAllocaMemoryObject(const rvsdg::SimpleNode &allocaNode, bool canPoint)
bool CanTrackPointeesImplicitly(PointerObjectIndex index) const noexcept
bool MarkAsStoringAsScalar(PointerObjectIndex index)
bool MarkAsPointingToExternal(PointerObjectIndex index)
PointerObjectIndex GetUnificationRoot(PointerObjectIndex index) const noexcept
bool IsUnificationRoot(PointerObjectIndex index) const noexcept
PointerObjectIndex CreateFunctionMemoryObject(const rvsdg::LambdaNode &lambdaNode)
std::vector< PointerObject > PointerObjects_
size_t NumPointerObjects() const noexcept
const rvsdg::LambdaNode & GetLambdaNodeFromFunctionMemoryObject(PointerObjectIndex index) const
const std::unordered_map< const rvsdg::Output *, PointerObjectIndex > & GetRegisterMap() const noexcept
PointerObjectIndex CreateImportMemoryObject(const LlvmGraphImport &importNode, bool canPoint)
bool MarkAsEscaped(PointerObjectIndex index)
util::BijectiveMap< const rvsdg::LambdaNode *, PointerObjectIndex > FunctionMap_
PointerObjectIndex GetFunctionMemoryObject(const rvsdg::LambdaNode &lambdaNode) const
bool MakePointsToSetSuperset(PointerObjectIndex superset, PointerObjectIndex subset)
std::optional< PointerObjectIndex > TryGetRegisterPointerObject(const rvsdg::Output &rvsdgOutput) const
std::unordered_map< const LlvmGraphImport *, PointerObjectIndex > ImportMap_
size_t NumPointerObjectsOfKind(PointerObjectKind kind) const noexcept
PointerObjectIndex GetPointer() const noexcept
PointerObjectIndex GetValue() const noexcept
StoreConstraint(PointerObjectIndex pointer, PointerObjectIndex value)
void SetValue(PointerObjectIndex value)
void SetPointer(PointerObjectIndex pointer)
PointerObjectIndex GetSubset() const noexcept
PointerObjectIndex GetSuperset() const noexcept
void SetSubset(PointerObjectIndex subset)
SupersetConstraint(PointerObjectIndex superset, PointerObjectIndex subset)
void SetSuperset(PointerObjectIndex superset)
const SimpleOperation & GetOperation() const noexcept override
Definition: simple-node.cpp:48
#define JLM_ASSERT(x)
Definition: common.hpp:16
uint32_t PointerObjectIndex
static constexpr int BitWidthOfEnum(T endValue)
Definition: Math.hpp:107
PointerObject(PointerObjectKind kind, bool canPoint)