heap.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  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. // TODO: Apply thread safety to more memory operations.
  24. // heap_getUsedSize and heap_setUsedSize are often used together, forming a transaction without any mutex.
  25. // But because of potential false sharing of cache lines, writing should be considered a transaction even if it is atomic.
  26. #include "../settings.h"
  27. #if defined(USE_MICROSOFT_WINDOWS)
  28. #include <Windows.h>
  29. #elif defined(USE_MACOS)
  30. #include <sys/sysctl.h>
  31. #elif defined(USE_LINUX)
  32. #include <stdio.h>
  33. #include <stdint.h>
  34. #include <stdlib.h>
  35. #endif
  36. #ifndef DISABLE_MULTI_THREADING
  37. // Requires -pthread for linking
  38. #include <thread>
  39. #include <mutex>
  40. #endif
  41. #include "heap.h"
  42. #include "../api/stringAPI.h"
  43. #include "../api/timeAPI.h"
  44. #include <stdio.h>
  45. #include <new>
  46. #include "simd.h"
  47. #ifdef SAFE_POINTER_CHECKS
  48. #define DSR_PRINT_CACHE_LINE_SIZE
  49. #define DSR_PRINT_NO_MEMORY_LEAK
  50. #endif
  51. namespace dsr {
  52. // The default cache line size is used when not known from asking the system.
  53. static const uintptr_t defaultCacheLineSize = 128;
  54. // There is no point in using a heap alignment smaller than the allocation heads, so we align to at least 64 bytes.
  55. static const uintptr_t minimumHeapAlignment = 64;
  56. #if defined(USE_LINUX)
  57. static uintptr_t getCacheLineSizeFromIndices(uintptr_t cpuIndex, uintptr_t cacheLevel) {
  58. char path[256];
  59. snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%i/cache/index%i/coherency_line_size", (int)cpuIndex, (int)cacheLevel);
  60. FILE *file = fopen(path, "r");
  61. if (file == nullptr) {
  62. return 0;
  63. }
  64. int cacheLineSize;
  65. if (fscanf(file, "%i", &cacheLineSize) != 1) {
  66. cacheLineSize = 0;
  67. }
  68. fclose(file);
  69. return uintptr_t(cacheLineSize);
  70. }
  71. static uintptr_t getCacheLineSize() {
  72. uintptr_t result = 0;
  73. uintptr_t cpuIndex = 0;
  74. uintptr_t cacheLevel = 0;
  75. while (true) {
  76. uintptr_t newSize = getCacheLineSizeFromIndices(cpuIndex, cacheLevel);
  77. if (newSize == 0) {
  78. if (cacheLevel == 0) {
  79. // CPU does not exist, so we are done.
  80. break;
  81. } else {
  82. // Cache level does not exist. Go to the next CPU.
  83. cpuIndex++;
  84. cacheLevel = 0;
  85. }
  86. } else {
  87. // We have found the cache line size for cpuIndex and cacheLevel, so we include it in a maximum.
  88. //printf("CPU %i cache level %i is %i.\n", (int)cpuIndex, (int)cacheLevel, (int)newSize);
  89. result = max(result, newSize);
  90. cacheLevel++;
  91. }
  92. }
  93. if (result <= 0) {
  94. result = defaultCacheLineSize;
  95. printf("WARNING! Failed to read cache line size from Linux system folders. The application might not be thread-safe.\n");
  96. }
  97. #ifdef DSR_PRINT_CACHE_LINE_SIZE
  98. printf("Detected a cache line width of %i bytes from reading Linux system folders.\n", (int)result);
  99. #endif
  100. return result;
  101. }
  102. #elif defined(USE_MACOS)
  103. static uintptr_t getCacheLineSize() {
  104. uintptr_t result = 0;
  105. int cacheLine;
  106. size_t size = sizeof(cacheLine);
  107. int mib[2] = {CTL_HW, HW_CACHELINE};
  108. int error = sysctl(mib, 2, &cacheLine, &size, NULL, 0);
  109. if(error == 0) {
  110. result = cacheLine;
  111. #ifdef DSR_PRINT_CACHE_LINE_SIZE
  112. printf("Detected a cache line width of %i bytes on MacOS by asking for HW_CACHELINE with sysctl.\n", (int)result);
  113. #endif
  114. } else {
  115. result = defaultCacheLineSize;
  116. printf("WARNING! Failed to read HW_CACHELINE on MacOS. The application might not be thread-safe.\n");
  117. }
  118. return result;
  119. }
  120. #elif defined(USE_MICROSOFT_WINDOWS)
  121. static uintptr_t getCacheLineSize() {
  122. DWORD bufferSize = 0;
  123. GetLogicalProcessorInformation(nullptr, &bufferSize);
  124. SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)malloc(bufferSize);
  125. uintptr_t result = 0;
  126. if (buffer == nullptr) {
  127. result = defaultCacheLineSize;
  128. printf("WARNING! Failed to allocate buffer for the call to GetLogicalProcessorInformation on MS-Windows. The application might not be thread-safe.\n");
  129. } else if (GetLogicalProcessorInformation(buffer, &bufferSize)) {
  130. SYSTEM_LOGICAL_PROCESSOR_INFORMATION *entry = buffer;
  131. SYSTEM_LOGICAL_PROCESSOR_INFORMATION *lastEntry = buffer + (bufferSize / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) - 1;
  132. while (entry <= lastEntry) {
  133. if (entry->Relationship == RelationCache) {
  134. uintptr_t currentLineSize = entry->Cache.LineSize;
  135. if (currentLineSize > result) {
  136. result = currentLineSize;
  137. }
  138. }
  139. entry++;
  140. }
  141. #ifdef DSR_PRINT_CACHE_LINE_SIZE
  142. printf("Detected a cache line width of %i bytes on MS-Windows by checking each RelationCache with GetLogicalProcessorInformation.\n", (int)result);
  143. #endif
  144. } else {
  145. result = defaultCacheLineSize;
  146. printf("WARNING! The call to GetLogicalProcessorInformation failed to get the cache line size on MS-Windows. The application might not be thread-safe.\n");
  147. }
  148. free(buffer);
  149. return result;
  150. }
  151. #else
  152. static uintptr_t getCacheLineSize() {
  153. printf("WARNING! The target platform does not have a method for detecting cache line width in DFPSR/base/heap.cpp.\n");
  154. return defaultCacheLineSize;
  155. }
  156. #endif
  157. static uintptr_t heapAlignmentAndMask = 0u;
  158. uintptr_t heap_getHeapAlignment() {
  159. static uintptr_t heapAlignment = 0u;
  160. if (heapAlignment == 0) {
  161. heapAlignment = getCacheLineSize();
  162. if (heapAlignment < DSR_FLOAT_VECTOR_SIZE) { heapAlignment = DSR_FLOAT_VECTOR_SIZE; }
  163. if (heapAlignment < minimumHeapAlignment) { heapAlignment = minimumHeapAlignment; }
  164. heapAlignmentAndMask = memory_createAlignmentAndMask(heapAlignment);
  165. }
  166. return heapAlignment;
  167. }
  168. static uintptr_t heap_getHeapAlignmentAndMask() {
  169. heap_getHeapAlignment();
  170. return heapAlignmentAndMask;
  171. }
  172. // Keep track of the program's state.
  173. enum class ProgramState {
  174. Starting, // A single thread does global construction without using any mutex.
  175. Running, // Any number of threads allocate and free memory.
  176. Terminating // A single thread does global destruction without using any mutex.
  177. };
  178. ProgramState programState = ProgramState::Starting;
  179. // Count threads.
  180. #ifndef DISABLE_MULTI_THREADING
  181. static uint64_t threadCount = 0u;
  182. static std::mutex threadLock;
  183. struct ThreadCounter {
  184. ThreadCounter() {
  185. threadLock.lock();
  186. threadCount++;
  187. if (threadCount > 1 && programState != ProgramState::Running) {
  188. if (programState == ProgramState::Starting) {
  189. printf("Tried to create another thread before construction of global variables was complete!\n");
  190. } else if (programState == ProgramState::Terminating) {
  191. printf("Tried to create another thread after destruction of global variables had begun!\n");
  192. }
  193. }
  194. threadLock.unlock();
  195. };
  196. ~ThreadCounter() {
  197. threadLock.lock();
  198. threadCount--;
  199. threadLock.unlock();
  200. };
  201. };
  202. thread_local ThreadCounter threadCounter;
  203. static uint64_t getThreadCount() {
  204. uint64_t result = 0;
  205. threadLock.lock();
  206. result = threadCount;
  207. threadLock.unlock();
  208. return result;
  209. }
  210. #endif
  211. // Because locking is recursive, it is safest to just have one global mutex for allocating, freeing and manipulating use counters.
  212. // Otherwise each use counter would need to store the thread identity and recursive depth for each heap allocation.
  213. #ifndef DISABLE_MULTI_THREADING
  214. static thread_local intptr_t lockDepth = 0;
  215. static std::mutex memoryLock;
  216. #endif
  217. inline void lockMemory() {
  218. #ifndef DISABLE_MULTI_THREADING
  219. // Only call the mutex within main.
  220. if (programState == ProgramState::Running) {
  221. if (lockDepth == 0) {
  222. memoryLock.lock();
  223. }
  224. lockDepth++;
  225. }
  226. #endif
  227. }
  228. inline void unlockMemory() {
  229. #ifndef DISABLE_MULTI_THREADING
  230. // Only call the mutex within main.
  231. if (programState == ProgramState::Running) {
  232. lockDepth--;
  233. assert(lockDepth >= 0);
  234. if (lockDepth == 0) {
  235. memoryLock.unlock();
  236. }
  237. }
  238. #endif
  239. }
  240. // Called before main, after global initiation completes.
  241. void heap_startingApplication() {
  242. // Once global initiation has completed, mutexes can be used for multi-threading.
  243. programState = ProgramState::Running;
  244. }
  245. // Called after main, before global termination begins.
  246. void heap_terminatingApplication() {
  247. #ifndef DISABLE_MULTI_THREADING
  248. // Wait for all other threads to terminate before closing the program.
  249. while (getThreadCount() > 1) {
  250. // Sleep for 10 milliseconds before trying again.
  251. time_sleepSeconds(0.01);
  252. }
  253. #endif
  254. // Once global termination begins, we can no longer use the mutex.
  255. // The memory system will still get calls to free resources, which must be handled with a single thread.
  256. programState = ProgramState::Terminating;
  257. }
  258. // The total number of used heap allocations, excluding recycled memory.
  259. // Only accessed when defaultHeap.poolLock is locked.
  260. static intptr_t allocationCount = 0;
  261. using HeapFlag = uint16_t;
  262. using BinIndex = uint16_t;
  263. // The free function is hidden, because all allocations are forced to use reference counting,
  264. // so that a hard exit can disable recursive calls to heap_free by incrementing all reference counters.
  265. static void heap_free(void * const allocation);
  266. // The bin size equals two to the power of binIndex multiplied by minimumHeapAlignment.
  267. // This allow knowing the number of bins in compile time by leaving a number of unused bins specified by MIN_BIN_COUNT.
  268. inline constexpr uintptr_t getBinSize(BinIndex binIndex) {
  269. // Index 0 starts at the minimum alignment to know the number of bins in compile time, so some bins will be unused when the alignment is bigger.
  270. return (uintptr_t(1) << uintptr_t(binIndex)) * minimumHeapAlignment;
  271. }
  272. // Calculates the largest power of two allocation size that does not overflow a pointer on the target platform.
  273. constexpr int calculateBinCount() {
  274. intptr_t p = 0;
  275. while (true) {
  276. uintptr_t result = getBinSize(p);
  277. // Once the value overflows in uintptr_t we have found the index that can not be used, which is also the bin count when including index zero.
  278. if (result == 0) {
  279. return p;
  280. }
  281. p++;
  282. }
  283. }
  284. // The index of the last used bin.
  285. static const int MAX_BIN_COUNT = calculateBinCount();
  286. static BinIndex getBinIndex(uintptr_t minimumSize, intptr_t minimumBin) {
  287. for (intptr_t p = minimumBin; p < MAX_BIN_COUNT; p++) {
  288. uintptr_t result = getBinSize(p);
  289. if (result >= minimumSize) {
  290. return p;
  291. }
  292. }
  293. // Failed to match any bin.
  294. return -1;
  295. }
  296. // The index of the first used bin, which is also the number of unused bins.
  297. static const int MIN_BIN_COUNT = getBinIndex(heap_getHeapAlignment(), 0);
  298. static const HeapFlag heapFlag_recycled = 1 << 0;
  299. struct HeapHeader : public AllocationHeader {
  300. // Because nextRecycled and usedSize have mutually exclusive lifetimes, they can share memory location.
  301. union {
  302. // When allocated
  303. uintptr_t usedSize; // The actual size requested.
  304. // When recycled
  305. HeapHeader *nextRecycled = nullptr;
  306. };
  307. HeapDestructor destructor;
  308. uintptr_t useCount = 0; // How many handles that point to the data.
  309. uint32_t customFlags = 0; // Application defined allocation flags.
  310. HeapFlag flags = 0; // Flags use the heapFlag_ prefix.
  311. BinIndex binIndex = 0; // Recycling bin index to use when freeing the allocation.
  312. HeapHeader(uintptr_t totalSize)
  313. : AllocationHeader(totalSize, false, "Nameless heap allocation") {}
  314. inline uintptr_t getAllocationSize() {
  315. return getBinSize(this->binIndex);
  316. }
  317. inline uintptr_t getUsedSize() {
  318. if (this->isRecycled()) {
  319. return 0;
  320. } else {
  321. return this->usedSize;
  322. }
  323. }
  324. inline uintptr_t setUsedSize(uintptr_t size) {
  325. if (!(this->isRecycled())) {
  326. uintptr_t allocationSize = getAllocationSize();
  327. if (size > allocationSize) {
  328. // Failed to assign the size, so it is important to check the result.
  329. size = allocationSize;
  330. }
  331. this->usedSize = size;
  332. return size;
  333. } else {
  334. return 0;
  335. }
  336. }
  337. inline bool isRecycled() const {
  338. return (this->flags & heapFlag_recycled) != 0;
  339. }
  340. inline void makeRecycled() {
  341. this->flags |= heapFlag_recycled;
  342. }
  343. inline void makeUsed() {
  344. this->flags &= ~heapFlag_recycled;
  345. }
  346. };
  347. // TODO: Allow using the header directly for manipulation in the API, now that the offset is not known in compile time.
  348. inline uintptr_t heap_getHeapHeaderPaddedSize() {
  349. static uintptr_t heapHeaderPaddedSize = 0;
  350. if (heapHeaderPaddedSize == 0) {
  351. heapHeaderPaddedSize = memory_getPaddedSize(sizeof(HeapHeader), heap_getHeapAlignment());
  352. }
  353. return heapHeaderPaddedSize;
  354. }
  355. AllocationHeader *heap_getHeader(void * const allocation) {
  356. return (AllocationHeader*)((uint8_t*)allocation - heap_getHeapHeaderPaddedSize());
  357. }
  358. inline HeapHeader *headerFromAllocation(void const * const allocation) {
  359. return (HeapHeader *)((uint8_t*)allocation - heap_getHeapHeaderPaddedSize());
  360. }
  361. inline void *allocationFromHeader(HeapHeader * const header) {
  362. return ((uint8_t*)header) + heap_getHeapHeaderPaddedSize();
  363. }
  364. inline void const *allocationFromHeader(HeapHeader const * const header) {
  365. return ((uint8_t const *)header) + heap_getHeapHeaderPaddedSize();
  366. }
  367. #ifdef SAFE_POINTER_CHECKS
  368. void heap_setAllocationName(void const * const allocation, const char *name) {
  369. if (allocation != nullptr) {
  370. HeapHeader *header = headerFromAllocation(allocation);
  371. header->name = name;
  372. }
  373. }
  374. const char * heap_getAllocationName(void const * const allocation) {
  375. if (allocation == nullptr) {
  376. return "none";
  377. } else {
  378. HeapHeader *header = headerFromAllocation(allocation);
  379. return header->name;
  380. }
  381. }
  382. uintptr_t heap_getPaddedSize(void const * const allocation) {
  383. if (allocation == nullptr) {
  384. return 0;
  385. } else {
  386. HeapHeader *header = headerFromAllocation(allocation);
  387. return memory_getPaddedSize_usingAndMask(header->getUsedSize(), heap_getHeapAlignmentAndMask());
  388. }
  389. }
  390. void setAllocationSerialization(void const * const allocation, AllocationSerialization method) {
  391. if (allocation != nullptr) {
  392. HeapHeader *header = headerFromAllocation(allocation);
  393. header->serializationMethod = method;
  394. }
  395. }
  396. static void serializeUnknownData(PrintCharacter target, void const * const allocation, uintptr_t maxLength) {
  397. uintptr_t byteCount = heap_getUsedSize(allocation);
  398. target(U'{');
  399. for (uintptr_t c = 0; c < byteCount; c++) {
  400. uint8_t byteValue = ((uint8_t *)allocation)[c];
  401. uint8_t firstHexValue = (byteValue >> 4) & 15;
  402. uint8_t secondHexValue = byteValue & 15;
  403. if (c == maxLength) {
  404. target(U'}');
  405. target(U'.');
  406. target(U'.');
  407. target(U'.');
  408. return;
  409. }
  410. if (c > 0) {
  411. target(U' ');
  412. }
  413. if (firstHexValue < 10u) {
  414. target(U'0' + (char32_t)firstHexValue);
  415. } else {
  416. target((U'A' - 10) + (char32_t)firstHexValue);
  417. }
  418. if (secondHexValue < 10u) {
  419. target(U'0' + (char32_t)secondHexValue);
  420. } else {
  421. target((U'A' - 10) + (char32_t)secondHexValue);
  422. }
  423. }
  424. target(U'}');
  425. }
  426. AllocationSerialization getAllocationSerialization(void const * const allocation) {
  427. if (allocation == nullptr) {
  428. return &serializeUnknownData;
  429. } else {
  430. HeapHeader *header = headerFromAllocation(allocation);
  431. return header->serializationMethod != nullptr ? header->serializationMethod : &serializeUnknownData;
  432. }
  433. }
  434. #endif
  435. uint32_t heap_getAllocationCustomFlags(void const * const allocation) {
  436. uint32_t result = 0;
  437. if (allocation != nullptr) {
  438. HeapHeader *header = headerFromAllocation(allocation);
  439. result = ((HeapHeader *)header)->customFlags;
  440. }
  441. return result;
  442. }
  443. uint32_t heap_getAllocationCustomFlags(AllocationHeader * header) {
  444. uint32_t result = 0;
  445. if (header != nullptr) {
  446. result = ((HeapHeader *)header)->customFlags;
  447. }
  448. return result;
  449. }
  450. void heap_setAllocationCustomFlags(void const * const allocation, uint32_t customFlags) {
  451. if (allocation != nullptr) {
  452. HeapHeader *header = headerFromAllocation(allocation);
  453. ((HeapHeader *)header)->customFlags = customFlags;
  454. }
  455. }
  456. void heap_setAllocationCustomFlags(AllocationHeader * header, uint32_t customFlags) {
  457. if (header != nullptr) {
  458. ((HeapHeader *)header)->customFlags = customFlags;
  459. }
  460. }
  461. uintptr_t heap_getAllocationSize(AllocationHeader const * const header) {
  462. uintptr_t result = 0;
  463. if (header != nullptr) {
  464. result = getBinSize(((HeapHeader *)header)->binIndex);
  465. }
  466. return result;
  467. }
  468. uintptr_t heap_getAllocationSize(void const * const allocation) {
  469. uintptr_t result = 0;
  470. if (allocation != nullptr) {
  471. HeapHeader *header = headerFromAllocation(allocation);
  472. result = getBinSize(header->binIndex);
  473. }
  474. return result;
  475. }
  476. uintptr_t heap_getUsedSize(AllocationHeader const * const header) {
  477. uintptr_t result = 0;
  478. if (header != nullptr) {
  479. result = ((HeapHeader *)header)->getUsedSize();
  480. }
  481. return result;
  482. }
  483. uintptr_t heap_getUsedSize(void const * const allocation) {
  484. uintptr_t result = 0;
  485. if (allocation != nullptr) {
  486. HeapHeader *header = headerFromAllocation(allocation);
  487. result = header->getUsedSize();
  488. }
  489. return result;
  490. }
  491. uintptr_t heap_setUsedSize(AllocationHeader * const header, uintptr_t size) {
  492. uintptr_t result = 0;
  493. if (header != nullptr) {
  494. result = ((HeapHeader *)header)->setUsedSize(size);
  495. }
  496. return result;
  497. }
  498. uintptr_t heap_setUsedSize(void * const allocation, uintptr_t size) {
  499. uintptr_t result = 0;
  500. if (allocation != nullptr) {
  501. HeapHeader *header = headerFromAllocation(allocation);
  502. result = header->setUsedSize(size);
  503. }
  504. return result;
  505. }
  506. void heap_increaseUseCount(AllocationHeader const * const header) {
  507. if (header != nullptr) {
  508. lockMemory();
  509. ((HeapHeader *)header)->useCount++;
  510. unlockMemory();
  511. }
  512. }
  513. void heap_increaseUseCount(void const * const allocation) {
  514. if (allocation != nullptr) {
  515. HeapHeader *header = headerFromAllocation(allocation);
  516. lockMemory();
  517. header->useCount++;
  518. unlockMemory();
  519. }
  520. }
  521. void heap_decreaseUseCount(AllocationHeader const * const header) {
  522. if (header != nullptr) {
  523. lockMemory();
  524. if (((HeapHeader *)header)->useCount == 0) {
  525. printf("Heap error: Decreasing a count that is already zero!\n");
  526. } else {
  527. ((HeapHeader *)header)->useCount--;
  528. if (((HeapHeader *)header)->useCount == 0) {
  529. heap_free(allocationFromHeader((HeapHeader *)header));
  530. }
  531. }
  532. unlockMemory();
  533. }
  534. }
  535. void heap_decreaseUseCount(void const * const allocation) {
  536. if (allocation != nullptr) {
  537. HeapHeader *header = headerFromAllocation(allocation);
  538. lockMemory();
  539. if (((HeapHeader *)header)->useCount == 0) {
  540. printf("Heap error: Decreasing a count that is already zero!\n");
  541. } else {
  542. ((HeapHeader *)header)->useCount--;
  543. if (((HeapHeader *)header)->useCount == 0) {
  544. heap_free((void*)allocation);
  545. }
  546. }
  547. unlockMemory();
  548. }
  549. }
  550. uintptr_t heap_getUseCount(AllocationHeader * const header) {
  551. if (header == nullptr) {
  552. return 0;
  553. } else {
  554. return ((HeapHeader *)header)->useCount;
  555. }
  556. }
  557. uintptr_t heap_getUseCount(void const * const allocation) {
  558. if (allocation == nullptr) {
  559. return 0;
  560. } else {
  561. return headerFromAllocation(allocation)->useCount;
  562. }
  563. }
  564. // A block of memory where heap data can be allocated.
  565. struct HeapMemory {
  566. HeapMemory *prevHeap = nullptr;
  567. uint8_t *top = nullptr; // The start of the arena, where the allocation pointer is when full.
  568. uint8_t *allocationPointer = nullptr; // The allocation pointer that moves from bottom to top when filling the arena.
  569. uint8_t *bottom = nullptr; // The end of the arena, where the allocation pointer is when empty.
  570. HeapMemory(uintptr_t size) {
  571. this->top = (uint8_t*)(operator new (size));
  572. this->bottom = this->top + size;
  573. this->allocationPointer = this->bottom;
  574. }
  575. ~HeapMemory() {
  576. if (this->top != nullptr) {
  577. delete this->top;
  578. this->top = nullptr;
  579. }
  580. this->allocationPointer = nullptr;
  581. this->bottom = nullptr;
  582. }
  583. };
  584. // The heap can have memory freed after its own destruction by telling the remaining allocations to clean up after themselves.
  585. struct HeapPool {
  586. HeapMemory *lastHeap = nullptr;
  587. HeapHeader *recyclingBin[MAX_BIN_COUNT] = {};
  588. void cleanUp(bool noLeaks) {
  589. // If memory safety checks are enabled, then we should indicate that everything is fine with the memory once cleaning up.
  590. // There is however no way to distinguish between leaking memory and not yet having terminated everything, so there is no leak warning to print.
  591. #ifdef DSR_PRINT_NO_MEMORY_LEAK
  592. // Can't allocate more memory after freeing all memory.
  593. if (noLeaks) {
  594. printf("All heap memory was freed without leaks.\n");
  595. }
  596. #endif
  597. HeapMemory *nextHeap = this->lastHeap;
  598. while (nextHeap != nullptr) {
  599. HeapMemory *currentHeap = nextHeap;
  600. nextHeap = currentHeap->prevHeap;
  601. operator delete(currentHeap);
  602. }
  603. this->lastHeap = nullptr;
  604. }
  605. HeapPool() {}
  606. ~HeapPool() {
  607. // The destruction should be ignored to manually terminate once all memory allocations have been freed.
  608. if (programState != ProgramState::Terminating) {
  609. printf("Heap error: Terminated the application without first calling heap_terminatingApplication or heap_hardExitCleaning!\n");
  610. }
  611. }
  612. };
  613. static UnsafeAllocation tryToAllocate(HeapMemory &heap, uintptr_t paddedSize, uintptr_t alignmentAndMask) {
  614. UnsafeAllocation result(nullptr, nullptr);
  615. uint8_t *dataPointer = (uint8_t*)(((uintptr_t)(heap.allocationPointer) - paddedSize) & alignmentAndMask);
  616. AllocationHeader *headerPointer = (AllocationHeader*)(dataPointer - heap_getHeapHeaderPaddedSize());
  617. if ((uint8_t*)headerPointer >= heap.top) {
  618. // There is enough space, so confirm the allocation.
  619. result = UnsafeAllocation(dataPointer, headerPointer);
  620. // Write data to the header.
  621. *headerPointer = HeapHeader((uintptr_t)heap.allocationPointer - (uintptr_t)headerPointer);
  622. // Reserve the data in the heap by moving the allocation pointer.
  623. heap.allocationPointer = (uint8_t*)headerPointer;
  624. }
  625. return result;
  626. }
  627. static UnsafeAllocation tryToAllocate(HeapPool &pool, uintptr_t paddedSize, uintptr_t alignmentAndMask) {
  628. // Start with the most recent heap, which is most likely to have enough space.
  629. HeapMemory *currentHeap = pool.lastHeap;
  630. while (currentHeap != nullptr) {
  631. UnsafeAllocation result = tryToAllocate(*currentHeap, paddedSize, alignmentAndMask);
  632. if (result.data != nullptr) {
  633. return result;
  634. }
  635. currentHeap = currentHeap->prevHeap;
  636. }
  637. // If no free space could be found, allocate a new memory block.
  638. uintptr_t allocationSize = 16777216;
  639. uintptr_t usefulSize = paddedSize << 4; // TODO: Handle integer overflow.
  640. if (usefulSize > allocationSize) allocationSize = usefulSize;
  641. // Append to the linked list of memory.
  642. HeapMemory *previousHeap = pool.lastHeap;
  643. pool.lastHeap = new HeapMemory(allocationSize);
  644. pool.lastHeap->prevHeap = previousHeap;
  645. // Make one last attempt at allocating the memory.
  646. return tryToAllocate(*(pool.lastHeap), paddedSize, alignmentAndMask);
  647. }
  648. static HeapPool defaultHeap;
  649. UnsafeAllocation heap_allocate(uintptr_t minimumSize, bool zeroed) {
  650. UnsafeAllocation result(nullptr, nullptr);
  651. int32_t binIndex = getBinIndex(minimumSize, MIN_BIN_COUNT);
  652. if (binIndex == -1) {
  653. // If the requested allocation is so big that there is no power of two that can contain it without overflowing the address space, then it can not be allocated.
  654. printf("Heap error: Exceeded the maximum size when trying to allocate!\n");
  655. } else {
  656. uintptr_t paddedSize = getBinSize(binIndex);
  657. lockMemory();
  658. allocationCount++;
  659. // Look for pre-existing allocations in the recycling bins.
  660. HeapHeader *binHeader = defaultHeap.recyclingBin[binIndex];
  661. if (binHeader != nullptr) {
  662. // Make the recycled allocation's tail into the new head.
  663. defaultHeap.recyclingBin[binIndex] = binHeader->nextRecycled;
  664. // Clear the pointer to make room for the allocation's size in the union.
  665. binHeader->nextRecycled = nullptr;
  666. // Mark the allocation as not recycled. (assume that it was recycled when found in the bin)
  667. binHeader->makeUsed();
  668. binHeader->reuse(false, "Nameless reused allocation");
  669. result = UnsafeAllocation((uint8_t*)allocationFromHeader(binHeader), binHeader);
  670. } else {
  671. // Look for a heap with enough space for a new allocation.
  672. result = tryToAllocate(defaultHeap, paddedSize, heap_getHeapAlignmentAndMask());
  673. if (result.data == nullptr) {
  674. printf("Heap error: Failed to allocate more memory!\n");
  675. }
  676. }
  677. unlockMemory();
  678. if (zeroed && result.data != nullptr) {
  679. memset(result.data, 0, paddedSize);
  680. }
  681. }
  682. if (result.data != nullptr) {
  683. // Get the header.
  684. HeapHeader *head = (HeapHeader*)(result.header);
  685. // Tell the allocation where it should be recycled when freed.
  686. head->binIndex = binIndex;
  687. // Tell the allocation how many of the bytes that are used.
  688. head->setUsedSize(minimumSize);
  689. }
  690. return result;
  691. }
  692. void heap_setAllocationDestructor(void * const allocation, const HeapDestructor &destructor) {
  693. HeapHeader *header = headerFromAllocation(allocation);
  694. header->destructor = destructor;
  695. }
  696. static void heap_free(void * const allocation) {
  697. lockMemory();
  698. // Get the recycled allocation's header.
  699. HeapHeader *header = headerFromAllocation(allocation);
  700. if (header->isRecycled()) {
  701. printf("Heap error: A heap allocation was freed twice!\n");
  702. } else {
  703. // Call the destructor provided with any external resource that also needs to be freed.
  704. if (header->destructor.destructor) {
  705. header->destructor.destructor(allocation, header->destructor.externalResource);
  706. }
  707. // Remove the destructor so that it is not called again for the next allocation.
  708. header->destructor = HeapDestructor();
  709. int binIndex = header->binIndex;
  710. if (binIndex >= MAX_BIN_COUNT) {
  711. printf("Heap error: Out of bound recycling bin index in corrupted head of freed allocation!\n");
  712. } else {
  713. // Make any previous head from the bin into the new tail.
  714. HeapHeader *oldHeader = defaultHeap.recyclingBin[binIndex];
  715. header->nextRecycled = oldHeader;
  716. // Mark the allocation as recycled.
  717. header->makeRecycled();
  718. #ifdef SAFE_POINTER_CHECKS
  719. // Remove the allocation identity, so that use of freed memory can be detected in SafePointer and Handle.
  720. header->allocationIdentity = 0;
  721. header->threadHash = 0;
  722. #endif
  723. // Store the newly recycled allocation in the bin.
  724. defaultHeap.recyclingBin[binIndex] = header;
  725. header->nextRecycled = oldHeader;
  726. }
  727. }
  728. // By decreasing the count after recursive calls to destructors, we can make sure that the arena is freed last.
  729. // If a destructor allocates new memory, it will have to allocate a new arena and then clean it up again.
  730. allocationCount--;
  731. // If the heap has been told to terminate and we reached zero allocations, we can tell it to clean up.
  732. if (programState == ProgramState::Terminating && allocationCount == 0) {
  733. defaultHeap.cleanUp(true);
  734. }
  735. unlockMemory();
  736. }
  737. static void forAllHeapAllocations(HeapMemory &heap, std::function<void(AllocationHeader * header, void * allocation)> callback) {
  738. uint8_t * current = heap.allocationPointer;
  739. while (current < heap.bottom) {
  740. HeapHeader *header = (HeapHeader*)current;
  741. void *payload = allocationFromHeader(header);
  742. if (!(header->isRecycled())) {
  743. callback(header, payload);
  744. }
  745. current += header->totalSize;
  746. }
  747. }
  748. void heap_forAllHeapAllocations(std::function<void(AllocationHeader * header, void * allocation)> callback) {
  749. HeapMemory *currentHeap = defaultHeap.lastHeap;
  750. while (currentHeap != nullptr) {
  751. forAllHeapAllocations(*currentHeap, callback);
  752. currentHeap = currentHeap->prevHeap;
  753. }
  754. }
  755. void heap_hardExitCleaning() {
  756. // TODO:
  757. // * Increment the use count for each allocation, to prevent recursive freeing of resources.
  758. // * Call the destructor on each allocation without freeing any memory, while all memory is still available.
  759. // Then the arenas can safely be deallocated without looking at individual allocations again.
  760. allocationCount = 0;
  761. programState = ProgramState::Terminating;
  762. defaultHeap.cleanUp(false);
  763. }
  764. static void printCharacter(char32_t character) {
  765. if (character < 32) {
  766. putchar(' ');
  767. } else if (character > 127) {
  768. putchar('?');
  769. } else {
  770. putchar((char)character);
  771. }
  772. }
  773. // TODO: Can whole pointers be displayed using printf?
  774. void heap_debugPrintAllocation(void const * const allocation, uintptr_t maxLength) {
  775. uintptr_t size = (int)heap_getUsedSize(allocation);
  776. #ifdef SAFE_POINTER_CHECKS
  777. printf("* %s of %i bytes of use count %i at %p\n", heap_getAllocationName(allocation), (int)size, (int)heap_getUseCount(allocation), allocation);
  778. printf("\t");
  779. getAllocationSerialization(allocation)(printCharacter, allocation, maxLength);
  780. printf("\n");
  781. #else
  782. printf("* Allocation of %i bytes of use count %i at %p\n", (int)size, (int)heap_getUseCount(allocation), allocation);
  783. #endif
  784. }
  785. void heap_debugPrintAllocations(uintptr_t maxLength) {
  786. printf("\nAllocations:\n");
  787. heap_forAllHeapAllocations([maxLength](AllocationHeader * header, void * allocation) {
  788. heap_debugPrintAllocation(allocation, maxLength);
  789. });
  790. }
  791. intptr_t heap_getAllocationCount() {
  792. return allocationCount;
  793. }
  794. void impl_throwAllocationFailure() {
  795. string_sendMessage(U"Failed to allocate memory for a new object!\n", MessageType::Error);
  796. }
  797. void impl_throwNullException() {
  798. string_sendMessage(U"Null handle exception!\n", MessageType::Error);
  799. }
  800. void impl_throwIdentityMismatch(uint64_t allocationIdentity, uint64_t pointerIdentity) {
  801. throwError(U"Identity mismatch! The allocation pointed to had identity ", allocationIdentity, U" but ", pointerIdentity, U" was expected by the pointer from when it was allocated.\n");
  802. }
  803. }