mission_script_arena.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #include <algorithm>
  2. #include <cstddef>
  3. #include <cstring>
  4. #include <limits>
  5. #include <new>
  6. #include "mission_script_vm_internal.h"
  7. namespace sunrise::server::activity::mission::lua_vm::detail {
  8. namespace {
  9. // Every payload Lua allocates must satisfy the widest fundamental alignment, and each block
  10. // carries its header directly in front of that payload.
  11. constexpr std::size_t kAlignment = alignof(std::max_align_t);
  12. constexpr std::size_t kHeaderSize = sizeof(ArenaBlock);
  13. static_assert(kHeaderSize % kAlignment == 0);
  14. [[nodiscard]] bool aligned_size(std::size_t value, std::size_t& output) noexcept {
  15. if (value > (std::numeric_limits<std::size_t>::max)() - (kAlignment - 1)) {
  16. return false;
  17. }
  18. output = (value + kAlignment - 1) & ~(kAlignment - 1);
  19. return true;
  20. }
  21. [[nodiscard]] std::byte* arena_begin(Arena& arena) noexcept {
  22. return arena.bytes.get();
  23. }
  24. [[nodiscard]] const std::byte* arena_end(const Arena& arena) noexcept {
  25. return arena.bytes.get() + arena.capacity;
  26. }
  27. [[nodiscard]] ArenaBlock* first_block(Arena& arena) noexcept {
  28. return reinterpret_cast<ArenaBlock*>(arena_begin(arena));
  29. }
  30. [[nodiscard]] ArenaBlock* next_block(Arena& arena, ArenaBlock& block) noexcept {
  31. std::byte* const next = reinterpret_cast<std::byte*>(&block) + kHeaderSize + block.size;
  32. return next < arena_end(arena) ? reinterpret_cast<ArenaBlock*>(next) : nullptr;
  33. }
  34. [[nodiscard]] bool valid_block(const Arena& arena, const ArenaBlock& block) noexcept {
  35. const std::byte* const begin = reinterpret_cast<const std::byte*>(&block);
  36. return begin >= arena.bytes.get() && begin + kHeaderSize <= arena_end(arena)
  37. && block.size <= static_cast<std::size_t>(arena_end(arena) - begin - kHeaderSize);
  38. }
  39. /** @return The header of the block owning this payload, or null when the arena does not own it. */
  40. [[nodiscard]] ArenaBlock* block_for_pointer(Arena& arena, void* pointer) noexcept {
  41. if (pointer == nullptr) {
  42. return nullptr;
  43. }
  44. std::byte* const payload = static_cast<std::byte*>(pointer);
  45. if (payload < arena_begin(arena) + kHeaderSize || payload >= arena_end(arena)) {
  46. return nullptr;
  47. }
  48. ArenaBlock* const block = reinterpret_cast<ArenaBlock*>(payload - kHeaderSize);
  49. return valid_block(arena, *block) ? block : nullptr;
  50. }
  51. [[nodiscard]] ArenaBlock* previous_block(Arena& arena, ArenaBlock& block) noexcept {
  52. std::byte* const begin = reinterpret_cast<std::byte*>(&block);
  53. if (block.previous == 0 || begin == arena_begin(arena)) {
  54. return nullptr;
  55. }
  56. return reinterpret_cast<ArenaBlock*>(begin - kHeaderSize - block.previous);
  57. }
  58. /** Records a block's payload size on its physical successor, which owns the back link. */
  59. void publish_size(Arena& arena, ArenaBlock& block) noexcept {
  60. ArenaBlock* const next = next_block(arena, block);
  61. if (next != nullptr && valid_block(arena, *next)) {
  62. next->previous = block.size;
  63. }
  64. }
  65. /** Leaves the unused tail of one free block as its own free successor. */
  66. void split(Arena& arena, ArenaBlock& block, std::size_t size) noexcept {
  67. const std::size_t remaining = block.size - size;
  68. if (remaining < kHeaderSize + kAlignment) {
  69. return;
  70. }
  71. std::byte* const address = reinterpret_cast<std::byte*>(&block) + kHeaderSize + size;
  72. auto* const rest = reinterpret_cast<ArenaBlock*>(address);
  73. rest->size = remaining - kHeaderSize;
  74. rest->previous = size;
  75. rest->free = true;
  76. block.size = size;
  77. publish_size(arena, *rest);
  78. }
  79. /** Takes the first free block at or after the rover. A scan from the start would be quadratic. */
  80. [[nodiscard]] void* allocate_new(Arena& arena, std::size_t requested) noexcept {
  81. std::size_t size = 0;
  82. if (requested == 0 || !aligned_size(requested, size)) {
  83. return nullptr;
  84. }
  85. const std::size_t start = arena.rover < arena.capacity ? arena.rover : 0;
  86. std::size_t offset = start;
  87. bool wrapped = false;
  88. while (true) {
  89. auto* const block = reinterpret_cast<ArenaBlock*>(arena_begin(arena) + offset);
  90. if (offset >= arena.capacity || !valid_block(arena, *block)) {
  91. if (wrapped || start == 0) {
  92. return nullptr;
  93. }
  94. wrapped = true;
  95. offset = 0;
  96. continue;
  97. }
  98. if (block->free && block->size >= size) {
  99. split(arena, *block, size);
  100. block->free = false;
  101. arena.used += block->size;
  102. arena.highWater = (std::max)(arena.highWater, arena.used);
  103. arena.rover = offset + kHeaderSize + block->size;
  104. return reinterpret_cast<std::byte*>(block) + kHeaderSize;
  105. }
  106. offset += kHeaderSize + block->size;
  107. if (wrapped && offset > start) {
  108. return nullptr;
  109. }
  110. }
  111. }
  112. /** Merges one freed block with a free neighbour on either side. */
  113. void merge_free(Arena& arena, ArenaBlock& block) noexcept {
  114. ArenaBlock* merged = &block;
  115. ArenaBlock* const next = next_block(arena, block);
  116. if (next != nullptr && valid_block(arena, *next) && next->free) {
  117. block.size += kHeaderSize + next->size;
  118. publish_size(arena, block);
  119. }
  120. ArenaBlock* const previous = previous_block(arena, block);
  121. if (previous != nullptr && valid_block(arena, *previous) && previous->free) {
  122. previous->size += kHeaderSize + block.size;
  123. publish_size(arena, *previous);
  124. merged = previous;
  125. }
  126. // The rover must never point inside a block that no longer starts there.
  127. arena.rover =
  128. static_cast<std::size_t>(reinterpret_cast<std::byte*>(merged) - arena_begin(arena));
  129. }
  130. void release(Arena& arena, void* pointer) noexcept {
  131. ArenaBlock* const block = block_for_pointer(arena, pointer);
  132. if (block == nullptr || block->free) {
  133. return;
  134. }
  135. arena.used -= (std::min)(arena.used, block->size);
  136. block->free = true;
  137. merge_free(arena, *block);
  138. }
  139. } // namespace
  140. /** Takes one block from the heap and lays it out as a single free maximum-aligned span. */
  141. bool arena_initialize(Arena& arena) noexcept {
  142. // operator new[] returns at least this alignment, which is what every payload here needs.
  143. static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >= kAlignment);
  144. arena.used = 0;
  145. arena.highWater = 0;
  146. arena.initialized = false;
  147. if (arena.bytes == nullptr) {
  148. // Call the allocation function directly: Clang's optimized Windows build folds the
  149. // nothrow byte-array new-expression into null, preventing every mission from opening.
  150. // The matching byte-array deleter still releases this storage through operator delete[].
  151. auto* const block =
  152. static_cast<std::byte*>(::operator new[](kArenaByteCapacity, std::nothrow));
  153. if (block == nullptr) {
  154. arena.capacity = 0;
  155. return false;
  156. }
  157. arena.bytes.reset(block);
  158. arena.capacity = kArenaByteCapacity;
  159. }
  160. ArenaBlock* const block = first_block(arena);
  161. block->size = arena.capacity - kHeaderSize;
  162. block->previous = 0;
  163. block->free = true;
  164. arena.rover = 0;
  165. arena.initialized = true;
  166. return true;
  167. }
  168. /** Frees the block. The high-water mark stays, because a caller reads it after the close. */
  169. void arena_release(Arena& arena) noexcept {
  170. arena.initialized = false;
  171. arena.bytes.reset();
  172. arena.capacity = 0;
  173. arena.used = 0;
  174. arena.rover = 0;
  175. }
  176. /** Implements Lua's realloc contract without crossing the fixed arena boundary. */
  177. void* arena_allocate(void* context,
  178. void* pointer,
  179. std::size_t oldSize,
  180. std::size_t newSize) noexcept {
  181. (void)oldSize;
  182. auto* const arena = static_cast<Arena*>(context);
  183. if (arena == nullptr || !arena->initialized) {
  184. return nullptr;
  185. }
  186. if (newSize == 0) {
  187. release(*arena, pointer);
  188. return nullptr;
  189. }
  190. if (pointer == nullptr) {
  191. return allocate_new(*arena, newSize);
  192. }
  193. ArenaBlock* const block = block_for_pointer(*arena, pointer);
  194. if (block == nullptr || block->free) {
  195. return nullptr;
  196. }
  197. std::size_t aligned = 0;
  198. if (!aligned_size(newSize, aligned)) {
  199. return nullptr;
  200. }
  201. if (block->size >= aligned) {
  202. return pointer;
  203. }
  204. void* const replacement = allocate_new(*arena, newSize);
  205. if (replacement == nullptr) {
  206. return nullptr;
  207. }
  208. std::memcpy(replacement, pointer, (std::min)(block->size, newSize));
  209. release(*arena, pointer);
  210. return replacement;
  211. }
  212. } // namespace sunrise::server::activity::mission::lua_vm::detail