Handle.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2024 to 2025 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #ifndef DFPSR_HANDLE
  24. #define DFPSR_HANDLE
  25. #include "heap.h"
  26. #include <utility>
  27. enum class AllocationInitialization {
  28. Uninitialized, // Used when the data will be instantly overwritten.
  29. Zeroed, // Used for trivial data types.
  30. Constructed // Used for a few objects.
  31. };
  32. namespace dsr {
  33. template <typename T>
  34. class Handle {
  35. private:
  36. // The internal pointer that reference counting is added to.
  37. // Must be allocated using heap_allocate, so that it can be freed using heap_free when the use count reaches zero.
  38. T *data = nullptr;
  39. #ifdef SAFE_POINTER_CHECKS
  40. // The identity that should match the allocation header's identity.
  41. uint64_t allocationIdentity = 0;
  42. inline void validate() const {
  43. if (this->data != nullptr) {
  44. // Heap allocations are shared with all threads, so we only need to check the identity.
  45. AllocationHeader *header = heap_getHeader(this->data);
  46. if (header->allocationIdentity != this->allocationIdentity) {
  47. impl_throwIdentityMismatch(header->allocationIdentity, this->allocationIdentity);
  48. }
  49. }
  50. }
  51. #endif
  52. public:
  53. // Default construct an empty handle.
  54. Handle() {}
  55. // Assigns a debug name to the handled heap allocation.
  56. // Returns the handle by reference to allow call chaining:
  57. // return handle_create<MyType>(some, arguments).setName("data for something specific");
  58. // return buffer_create(size).setName("data for something specific");
  59. // Should be trivially optimized away by the compiler in release mode.
  60. inline Handle<T> &setName(const char *name) {
  61. #ifdef SAFE_POINTER_CHECKS
  62. heap_setAllocationName(this->data, name);
  63. #endif
  64. return *this;
  65. }
  66. // Construct from pointer.
  67. // Pre-condition: data is the data allocated with heap_allocate.
  68. #ifdef SAFE_POINTER_CHECKS
  69. Handle(T* data, uint64_t allocationIdentity) noexcept
  70. : data(data) {
  71. this->allocationIdentity = allocationIdentity;
  72. if (this->data != nullptr) {
  73. heap_increaseUseCount(this->data);
  74. }
  75. this->validate();
  76. }
  77. inline uint64_t getAllocationIdentity() const { return this->allocationIdentity; }
  78. #else
  79. Handle(T* data) noexcept
  80. : data(data) {
  81. if (this->data != nullptr) {
  82. heap_increaseUseCount(this->data);
  83. }
  84. }
  85. #endif
  86. // Copy constructor.
  87. Handle(const Handle<T> &other) noexcept
  88. : data(other.getUnsafe()) {
  89. if (this->data != nullptr) {
  90. heap_increaseUseCount(this->data);
  91. }
  92. #ifdef SAFE_POINTER_CHECKS
  93. this->allocationIdentity = other.getAllocationIdentity();
  94. this->validate();
  95. #endif
  96. }
  97. // Copy constructor with static cast.
  98. template <typename V>
  99. Handle(const Handle<V> &other) noexcept
  100. : data(static_cast<T*>(other.getUnsafe())) {
  101. if (this->data != nullptr) {
  102. heap_increaseUseCount(this->data);
  103. }
  104. #ifdef SAFE_POINTER_CHECKS
  105. this->allocationIdentity = other.getAllocationIdentity();
  106. this->validate();
  107. #endif
  108. }
  109. // Move constructor.
  110. Handle(Handle<T> &&other) noexcept
  111. : data(other.takeOwnership()) {
  112. #ifdef SAFE_POINTER_CHECKS
  113. this->allocationIdentity = other.getAllocationIdentity();
  114. this->validate();
  115. #endif
  116. }
  117. // Move constructor with static cast.
  118. template <typename V>
  119. Handle(Handle<V> &&other) noexcept
  120. : data(static_cast<T*>(other.takeOwnership())) {
  121. #ifdef SAFE_POINTER_CHECKS
  122. this->allocationIdentity = other.getAllocationIdentity();
  123. this->validate();
  124. #endif
  125. }
  126. // Assignment.
  127. Handle<T>& operator = (const Handle<T> &other) {
  128. #ifdef SAFE_POINTER_CHECKS
  129. this->validate();
  130. this->allocationIdentity = other.getAllocationIdentity();
  131. #endif
  132. if (this->data != other.getUnsafe()) {
  133. // Decrease any old use count.
  134. if (this->data != nullptr) {
  135. heap_decreaseUseCount(this->data);
  136. }
  137. this->data = other.data;
  138. // Increase any new use count.
  139. if (this->data != nullptr) {
  140. heap_increaseUseCount(this->data);
  141. }
  142. }
  143. return *this;
  144. }
  145. // Assignment with static cast.
  146. template <typename V>
  147. Handle<T>& operator = (const Handle<V> &other) {
  148. #ifdef SAFE_POINTER_CHECKS
  149. this->validate();
  150. this->allocationIdentity = other.getAllocationIdentity();
  151. #endif
  152. if (this->data != other.getUnsafe()) {
  153. // Decrease any old use count.
  154. if (this->data != nullptr) {
  155. heap_decreaseUseCount(this->data);
  156. }
  157. this->data = static_cast<T*>(other.data);
  158. // Increase any new use count.
  159. if (this->data != nullptr) {
  160. heap_increaseUseCount(this->data);
  161. }
  162. }
  163. return *this;
  164. }
  165. // Move assignment.
  166. Handle<T>& operator = (Handle<T> &&other) {
  167. T* inherited = other.takeOwnership();
  168. #ifdef SAFE_POINTER_CHECKS
  169. this->validate();
  170. this->allocationIdentity = other.getAllocationIdentity();
  171. #endif
  172. if (this->data != inherited) {
  173. // Decrease any old use count.
  174. if (this->data != nullptr) {
  175. heap_decreaseUseCount(this->data);
  176. }
  177. this->data = inherited;
  178. }
  179. return *this;
  180. }
  181. // Move assignment with static cast.
  182. template <typename V>
  183. Handle<T>& operator = (Handle<V> &&other) {
  184. T* inherited = static_cast<T*>(other.takeOwnership());
  185. #ifdef SAFE_POINTER_CHECKS
  186. this->validate();
  187. this->allocationIdentity = other.getAllocationIdentity();
  188. #endif
  189. if (this->data != inherited) {
  190. // Decrease any old use count.
  191. if (this->data != nullptr) {
  192. heap_decreaseUseCount(this->data);
  193. }
  194. this->data = inherited;
  195. }
  196. return *this;
  197. }
  198. // Destructor.
  199. ~Handle() {
  200. if (this->data != nullptr) {
  201. #ifdef SAFE_POINTER_CHECKS
  202. this->validate();
  203. #endif
  204. heap_decreaseUseCount(this->data);
  205. }
  206. }
  207. // Take ownership of the returned pointer from this handle.
  208. inline T* takeOwnership() {
  209. T* result = this->data;
  210. this->data = nullptr;
  211. return result;
  212. }
  213. // Check if the handle is null, using explicit syntax to explain the code.
  214. inline bool isNull() const { return this->data == nullptr; }
  215. // Check if the handle points to anything, using explicit syntax to explain the code.
  216. inline bool isNotNull() const { return this->data != nullptr; }
  217. // Access content through the handle using the -> operator.
  218. inline T* operator ->() const {
  219. #ifdef SAFE_POINTER_CHECKS
  220. if (this->data == nullptr) { impl_throwNullException(); }
  221. this->validate();
  222. #endif
  223. return this->data;
  224. }
  225. // Returns the allocation's used size in bytes.
  226. inline uintptr_t getUsedSize() const {
  227. if (this->data == nullptr) {
  228. return 0;
  229. } else {
  230. return heap_getUsedSize(this->data);
  231. }
  232. }
  233. // Get the number of elements by dividing the total size with the element size.
  234. inline uintptr_t getElementCount() const {
  235. if (this->data == nullptr) {
  236. return 0;
  237. } else {
  238. // When sizeof(T) is a power of two, this unsigned integer division will be optimized into a bit shift by the compiler.
  239. return heap_getUsedSize(this->data) / sizeof(T);
  240. }
  241. }
  242. // Get a SafePointer to the data, which is used temporarity to iterate over the content with bound checks in debug mode but no overhead in release mode.
  243. // Alignment decides how many additional bytes of padding that should be possible to access for SIMD operations.
  244. template <typename V = T>
  245. SafePointer<V> getSafe(const char * name) const {
  246. if (this->data == nullptr) {
  247. // A null handle returns a null pointer.
  248. return SafePointer<V>();
  249. } else {
  250. #ifdef SAFE_POINTER_CHECKS
  251. AllocationHeader *header = heap_getHeader(this->data);
  252. return SafePointer<V>(header, this->allocationIdentity, name, (V*)this->data, heap_getPaddedSize(this->data));
  253. #else
  254. return SafePointer<V>(name, (V*)this->data);
  255. #endif
  256. }
  257. }
  258. // Get an unsafe pointer.
  259. inline T* getUnsafe() const {
  260. #ifdef SAFE_POINTER_CHECKS
  261. this->validate();
  262. #endif
  263. return this->data;
  264. }
  265. // Get a reference.
  266. inline T& getReference() const {
  267. #ifdef SAFE_POINTER_CHECKS
  268. if (this->data == nullptr) { impl_throwNullException(); }
  269. this->validate();
  270. #endif
  271. return *(this->data);
  272. }
  273. // Get the use count.
  274. inline uintptr_t getUseCount() const {
  275. #ifdef SAFE_POINTER_CHECKS
  276. this->validate();
  277. #endif
  278. return this->data ? heap_getUseCount(this->data) : 0;
  279. }
  280. };
  281. // Construct a new Handle<T> using the heap allocator and begin reference counting.
  282. // The object is aligned by DSR_MAXIMUM_ALIGNMENT.
  283. template<typename T, typename... ARGS>
  284. static Handle<T> handle_create(ARGS&&...args) {
  285. // Reset the memory to zero before construction, in case that something was forgotten.
  286. // TODO: Should debug mode set the memory to a deterministic pattern to simplify detection of uninitialized variables?
  287. UnsafeAllocation allocation = heap_allocate(sizeof(T), true);
  288. // Construction from pointer increases the allocation's use count to 1.
  289. #ifdef SAFE_POINTER_CHECKS
  290. Handle<T> result((T*)(allocation.data), allocation.header->allocationIdentity);
  291. #else
  292. Handle<T> result((T*)(allocation.data));
  293. #endif
  294. if (result.isNull()) {
  295. impl_throwAllocationFailure();
  296. } else {
  297. new (result.getUnsafe()) T(std::forward<ARGS>(args)...);
  298. if (!std::is_trivially_destructible<T>::value) {
  299. heap_setAllocationDestructor(result.getUnsafe(), HeapDestructor([](void *toDestroy, void *externalResource) {
  300. // Destroy one object.
  301. ((T*)toDestroy)->~T();
  302. }));
  303. }
  304. }
  305. return std::move(result.setName("Nameless handle object"));
  306. }
  307. // Construct an array of objects with a shared handle pointing to the first element.
  308. // The first element is aligned by DSR_MAXIMUM_ALIGNMENT and the rest are following directly according to sizeof(T).
  309. // This allow tight packing of data for SIMD vectorization, because aligning with a SIMD vector would be pointless if each vector only contained one useful lane.
  310. // Pre-condition:
  311. // sizeof(T) % alignof(T) == 0
  312. template<typename T, typename... ARGS>
  313. static Handle<T> handle_createArray(AllocationInitialization initialization, uintptr_t elementCount, ARGS&&...args) {
  314. UnsafeAllocation allocation = heap_allocate(sizeof(T) * elementCount, initialization == AllocationInitialization::Zeroed);
  315. // Construction from pointer increases the allocation's use count to 1.
  316. #ifdef SAFE_POINTER_CHECKS
  317. Handle<T> result((T*)(allocation.data), allocation.header->allocationIdentity);
  318. #else
  319. Handle<T> result((T*)(allocation.data));
  320. #endif
  321. if (result.isNull()) {
  322. impl_throwAllocationFailure();
  323. } else {
  324. if (initialization == AllocationInitialization::Constructed) {
  325. for (uintptr_t i = 0; i < elementCount; i++) {
  326. new (result.getUnsafe() + i) T(std::forward<ARGS>(args)...);
  327. }
  328. }
  329. if (!std::is_trivially_destructible<T>::value) {
  330. heap_setAllocationDestructor(result.getUnsafe(), HeapDestructor([](void *toDestroy, void *externalResource) {
  331. // Calculate the number of elements from the size.
  332. uintptr_t elementCount = heap_getUsedSize(toDestroy) / sizeof(T);
  333. // Destroy each element.
  334. for (uintptr_t i = 0; i < elementCount; i++) {
  335. ((T*)toDestroy)[i].~T();
  336. }
  337. }));
  338. }
  339. }
  340. return std::move(result.setName("Nameless handle array"));
  341. }
  342. // Dynamic casting of handles.
  343. // Attempts to cast from a base class to a specific class inheriting from the old type.
  344. // OLD_TYPE does not have to be stated explicitly in the call, because it is provided by oldHandle.
  345. // Example:
  346. // Handle<TypeB> = handle_dynamicCast<TypeB>(handle_create<TypeA>(1, 2, 3));
  347. // Pre-condition:
  348. // The old handle must refer to a single element or nullptr, no arrays allowed.
  349. // Post-condition:
  350. // Returns oldHandle dynamically casted to NEW_TYPE.
  351. // Returns an empty handle if the conversion failed.
  352. template <typename NEW_TYPE, typename OLD_TYPE>
  353. Handle<NEW_TYPE> handle_dynamicCast(const Handle<OLD_TYPE> &oldHandle) {
  354. #ifdef SAFE_POINTER_CHECKS
  355. return Handle<NEW_TYPE>(dynamic_cast<NEW_TYPE*>(oldHandle.getUnsafe()), oldHandle.getAllocationIdentity());
  356. #else
  357. return Handle<NEW_TYPE>(dynamic_cast<NEW_TYPE*>(oldHandle.getUnsafe()));
  358. #endif
  359. }
  360. }
  361. #endif