virtualStack.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2024 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. #include "virtualStack.h"
  24. #include <thread>
  25. #include "../api/stringAPI.h"
  26. namespace dsr {
  27. // A structure that is placed in front of each stack allocation while allocating backwards along decreasing addresses to allow aligning memory quickly using bit masking.
  28. struct StackAllocationHeader {
  29. uint32_t totalSize; // Size of both header and payload.
  30. #ifdef SAFE_POINTER_CHECKS
  31. uint32_t identity; // A unique identifier that can be used to reduce the risk of using a block of memory after it has been freed.
  32. #endif
  33. StackAllocationHeader(uint32_t totalSize);
  34. };
  35. // How many bytes that are allocated directly in thread local memory.
  36. static const size_t VIRTUAL_STACK_SIZE = 262144;
  37. // How many bytes are reserved for the head.
  38. static const size_t ALLOCATION_HEAD_SIZE = memory_getPaddedSize<StackAllocationHeader>();
  39. static const uintptr_t stackHeaderPaddedSize = memory_getPaddedSize<StackAllocationHeader>();
  40. static const uintptr_t stackHeaderAlignmentAndMask = memory_createAlignmentAndMask((uintptr_t)alignof(StackAllocationHeader));
  41. #ifdef SAFE_POINTER_CHECKS
  42. // In debug mode, each new thread creates a hash from its own identity to catch most of the memory errors in debug mode.
  43. std::hash<std::thread::id> hasher;
  44. thread_local uint32_t threadIdentity = hasher(std::this_thread::get_id());
  45. // To check the allocation identity, subtract the padded size of the header from the base pointer, cast to the head type and compare to the pointer's identity.
  46. thread_local uint32_t nextIdentity = threadIdentity;
  47. #endif
  48. StackAllocationHeader::StackAllocationHeader(uint32_t totalSize) : totalSize(totalSize) {
  49. #ifdef SAFE_POINTER_CHECKS
  50. // No identity may be zero, because identity zero is no identity.
  51. if (nextIdentity == 0) nextIdentity++;
  52. this->identity = nextIdentity;
  53. nextIdentity++;
  54. #endif
  55. }
  56. struct StackMemory {
  57. uint8_t data[VIRTUAL_STACK_SIZE];
  58. uint8_t *top = nullptr;
  59. StackMemory() {
  60. this->top = this->data + VIRTUAL_STACK_SIZE;
  61. }
  62. };
  63. thread_local StackMemory virtualStack;
  64. // Returns the size of the allocation including alignment.
  65. inline uint64_t increaseTop(uint64_t paddedSize, uintptr_t alignmentAndMask) {
  66. // Add the padded payload and align.
  67. uintptr_t oldAddress = (uintptr_t)virtualStack.top;
  68. uintptr_t newAddress = (oldAddress - paddedSize) & alignmentAndMask;
  69. virtualStack.top = (uint8_t*)newAddress;
  70. return oldAddress - newAddress;
  71. }
  72. inline void decreaseTop(uint64_t totalSize) {
  73. // Remove the data and alignment.
  74. virtualStack.top += totalSize;
  75. }
  76. uint8_t *virtualStack_push(uint64_t paddedSize, uintptr_t alignmentAndMask) {
  77. uint8_t *oldTop = virtualStack.top;
  78. // Allocate memory for payload.
  79. uint64_t payloadTotalSize = increaseTop(paddedSize, alignmentAndMask);
  80. // Get a pointer to the payload.
  81. uint8_t *result = virtualStack.top;
  82. // Allocate memory for header.
  83. uint64_t headerTotalSize = increaseTop(stackHeaderPaddedSize, stackHeaderAlignmentAndMask);
  84. // Check that we did not run out of memory.
  85. if (virtualStack.top < virtualStack.data) {
  86. // TODO: Expand automatically using heap memory instead of crashing.
  87. throwError(U"Ran out of virtual stack memory to allocate when trying to allocate ", paddedSize, U" bytes!\n");
  88. virtualStack.top = oldTop;
  89. return nullptr;
  90. } else {
  91. // Write the header to memory.
  92. *((StackAllocationHeader*)virtualStack.top) = StackAllocationHeader(payloadTotalSize + headerTotalSize);
  93. // Clear the new allocation for determinism.
  94. std::memset((char*)result, 0, payloadTotalSize);
  95. // Return a pointer to the payload.
  96. return result;
  97. }
  98. }
  99. void virtualStack_pop() {
  100. if (virtualStack.top + stackHeaderPaddedSize > virtualStack.data + VIRTUAL_STACK_SIZE) {
  101. throwError(U"No more stack memory to pop!\n");
  102. } else {
  103. // Read the header.
  104. StackAllocationHeader header = *((StackAllocationHeader*)virtualStack.top);
  105. // Deallocate both header and payload using the stored total size.
  106. decreaseTop(header.totalSize);
  107. }
  108. }
  109. }