From f73b8284bb8c98e273be691e1ffb2fc7db9a940c Mon Sep 17 00:00:00 2001 From: Patedam Date: Tue, 10 Feb 2026 23:01:42 -0500 Subject: [PATCH] Adding base to reallocate + unit test --- Juliet/include/Core/Common/CoreTypes.h | 2 + Juliet/include/Core/Memory/MemoryArena.h | 15 ++- Juliet/src/Core/Memory/MemoryArena.cpp | 100 +++++++++++++++++++- Juliet/src/Core/Memory/MemoryArenaTests.cpp | 44 +++------ 4 files changed, 125 insertions(+), 36 deletions(-) diff --git a/Juliet/include/Core/Common/CoreTypes.h b/Juliet/include/Core/Common/CoreTypes.h index b93bb99..8db89c3 100644 --- a/Juliet/include/Core/Common/CoreTypes.h +++ b/Juliet/include/Core/Common/CoreTypes.h @@ -25,6 +25,8 @@ struct ByteBuffer size_t Size; }; +using ptrdiff_t = decltype(static_cast(nullptr) - static_cast(nullptr)); + using FunctionPtr = auto (*)(void) -> void; // Limits diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 2c6d190..43ac87e 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -13,7 +13,8 @@ namespace Juliet constexpr global uint64 g_Arena_Default_Commit_Size = Kilobytes(64); constexpr global uint64 k_ArenaHeaderSize = 128; - // Refactor + struct ArenaFreeNode; + struct Arena { Arena* Previous; @@ -29,8 +30,13 @@ namespace Juliet uint64 Committed; uint64 Reserved; - Arena* FreeLast; + Arena* FreeBlockLast; + ArenaFreeNode* FreeNodes; + + bool AllowRealloc : 1; + + JULIET_DEBUG_ONLY(uint16 LostNodeCount); JULIET_DEBUG_ONLY(bool CanReserveMore : 1;) }; static_assert(sizeof(Arena) <= k_ArenaHeaderSize); @@ -40,6 +46,9 @@ namespace Juliet uint64 ReserveSize = g_Arena_Default_Reserve_Size; uint64 CommitSize = g_Arena_Default_Commit_Size; + // True: All push will be 32 bytes minimum + bool AllowRealloc = false; + // When false, will assert if a new block is reserved. // Useful for Vectors as they are guaranteed to be linear and i wont need to implement memcopy to increase capacity JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;) @@ -51,6 +60,8 @@ namespace Juliet // Raw Push, can be used but templated helpers exists below [[nodiscard]] void* ArenaPush(NonNullPtr arena, size_t size, size_t align, bool shouldBeZeroed); + [[nodiscard]] void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, + size_t align, bool shouldBeZeroed); void ArenaPopTo(NonNullPtr arena, size_t position); void ArenaPop(NonNullPtr arena, size_t amount); void ArenaClear(NonNullPtr arena); diff --git a/Juliet/src/Core/Memory/MemoryArena.cpp b/Juliet/src/Core/Memory/MemoryArena.cpp index f7ac744..6b8eabe 100644 --- a/Juliet/src/Core/Memory/MemoryArena.cpp +++ b/Juliet/src/Core/Memory/MemoryArena.cpp @@ -17,6 +17,20 @@ namespace Juliet constexpr uint64 k_PageSize = Kilobytes(4); } // namespace + struct ArenaFreeNode + { + ArenaFreeNode* Next; + ArenaFreeNode* Previous; + index_t Position; + size_t Size; + }; + + namespace + { + static_assert(sizeof(ArenaFreeNode) == 32, "ArenaFreeNode should be 32 bytes and not more"); + constexpr size_t k_ArenaFreeNodeSize = sizeof(ArenaFreeNode); + } // namespace + // https://github.com/EpicGamesExt/raddebugger/blob/master/src/base/base_arena.c Arena* ArenaAllocate(const ArenaParams& params, const std::source_location& loc) @@ -44,6 +58,9 @@ namespace Juliet arena->BasePosition = 0; arena->Position = k_ArenaHeaderSize; + arena->FreeNodes = nullptr; + arena->AllowRealloc = params.AllowRealloc; + arena->CanReserveMore = params.CanReserveMore; return arena; @@ -64,6 +81,47 @@ namespace Juliet size_t positionPrePush = AlignPow2(current->Position, align); size_t positionPostPush = positionPrePush + size; + if (arena->AllowRealloc) + { + size = Max(size, k_ArenaFreeNodeSize); + + for (ArenaFreeNode* freeNode = current->FreeNodes; freeNode != nullptr; freeNode = freeNode->Next) + { + if (size <= freeNode->Size) + { + index_t position = freeNode->Position; + size_t remainingSize = freeNode->Size - size; + if (remainingSize < k_ArenaFreeNodeSize) + { + ArenaFreeNode* previous = freeNode->Previous; + ArenaFreeNode* next = freeNode->Next; + if (previous) + { + previous->Next = next; + } + if (next) + { + next->Previous = previous; + } + + JULIET_DEBUG_ONLY(remainingSize > 0 ? ++current->LostNodeCount : current->LostNodeCount); + } + else + { + freeNode->Position += size; + } + + auto* result = reinterpret_cast(current) + position; + if (shouldBeZeroed) + { + MemoryZero(result, size); + } + + return result; + } + } + } + // If allowed and needed, add a new block and chain it to the arena. if (current->Reserved < positionPostPush /* flags : chaining allowed */) { @@ -72,7 +130,7 @@ namespace Juliet Arena* newBlock = nullptr; { Arena* prev_block; - for (newBlock = arena->FreeLast, prev_block = nullptr; newBlock != nullptr; + for (newBlock = arena->FreeBlockLast, prev_block = nullptr; newBlock != nullptr; prev_block = newBlock, newBlock = newBlock->Previous) { if (newBlock->Reserved >= AlignPow2(newBlock->Position, align) + size) @@ -83,7 +141,7 @@ namespace Juliet } else { - arena->FreeLast = newBlock->Previous; + arena->FreeBlockLast = newBlock->Previous; } break; } @@ -153,6 +211,42 @@ namespace Juliet return result; } + void* ArenaReallocate(NonNullPtr arena, void* oldPtr, size_t oldSize, size_t newSize, size_t align, bool shouldBeZeroed) + { + // Find the correct block + Arena* block = nullptr; + for (block = arena->Current; block != nullptr; block = block->Previous) + { + if ((block < oldPtr) && (oldPtr <= (block + block->Reserved))) + { + break; + } + } + Assert(block != nullptr); + + MemoryZero(oldPtr, oldSize); + ArenaFreeNode* freeNode = static_cast(oldPtr); + ptrdiff_t posPtr = static_cast(oldPtr) - reinterpret_cast(block); + index_t position = static_cast(posPtr) - block->BasePosition; + freeNode->Position = position; + freeNode->Size = oldSize; + + if (block->FreeNodes != nullptr) + { + block->FreeNodes = freeNode; + } + else + { + freeNode->Previous = block->FreeNodes; + if (block->FreeNodes != nullptr) + { + block->FreeNodes->Next = freeNode; + } + block->FreeNodes = freeNode; + } + return ArenaPush(arena, newSize, align, shouldBeZeroed); + } + void ArenaPopTo(NonNullPtr arena, size_t position) { size_t clampedPosition = ClampBottom(k_ArenaHeaderSize, position); @@ -162,7 +256,7 @@ namespace Juliet { previous = current->Previous; current->Position = k_ArenaHeaderSize; - SingleLinkedListPushPrevious(arena->FreeLast, current); + SingleLinkedListPushPrevious(arena->FreeBlockLast, current); } // No freelist : diff --git a/Juliet/src/Core/Memory/MemoryArenaTests.cpp b/Juliet/src/Core/Memory/MemoryArenaTests.cpp index cc630e8..015961f 100644 --- a/Juliet/src/Core/Memory/MemoryArenaTests.cpp +++ b/Juliet/src/Core/Memory/MemoryArenaTests.cpp @@ -139,40 +139,22 @@ namespace Juliet::UnitTest pool.FreeList = blk; } - MemoryArena arena; - MemoryArenaCreate(&arena, &pool); + { + // Test reallocate + Arena* arena = ArenaAllocate({ .AllowRealloc = true }); + char* charArray = ArenaPushArray(arena, 128); + char* secondCharArray = ArenaPushArray(arena, 128); + char* thirdCharArray = ArenaPushArray(arena, 128); - // 5. Arena Pop - // Align sizes to 16 to avoid padding issues during Pop - void* pop1 = ArenaPush(&arena, 5008, 16, ConstString("Pop1")); + secondCharArray = static_cast(ArenaReallocate(arena, secondCharArray, 128, 256, alignof(char), true)); - void* pop2 = ArenaPush(&arena, 208, 16, ConstString("Pop2")); - // Pop Middle (Should Fail) - bool res1 = ArenaPop(&arena, pop1, 5008); - Assert(res1 == false); + char* fourthCharArray = ArenaPushArray(arena, 128); - // Pop Top (Should Success) - bool res2 = ArenaPop(&arena, pop2, 208); // 200->208 - Assert(res2 == true); - - // Verify Used space is back to pop1 end - Assert(arena.CurrentBlock->Used == ArenaGetMarker(&arena).Offset); // This usage of GetMarker is valid if marker was implicit? - // Actually we didn't take a marker. - // We can verify by allocating pop3. It should overwrite pop2 location. - void* pop3 = ArenaPush(&arena, 16, 16, ConstString("Pop3")); - Assert(pop3 == pop2); - - // Cleanup popped items from stack logic for reset... - // Pop pop3 - ArenaPop(&arena, pop3, 16); - // Pop pop1 - ArenaPop(&arena, pop1, 5008); - Assert(arena.CurrentBlock->Used == 0); // Should be effectively 0 - - printf("[Success] Arena Pop\n"); - - // Cleanup - SafeFree(testBacking); + Assert(charArray); + Assert(secondCharArray); + Assert(thirdCharArray); + Assert(fourthCharArray); + } printf("All Paged MemoryArena tests passed.\n"); }