Adding base to reallocate + unit test

This commit is contained in:
2026-02-10 23:01:42 -05:00
parent 679edf48ed
commit f73b8284bb
4 changed files with 125 additions and 36 deletions

View File

@@ -25,6 +25,8 @@ struct ByteBuffer
size_t Size;
};
using ptrdiff_t = decltype(static_cast<int*>(nullptr) - static_cast<int*>(nullptr));
using FunctionPtr = auto (*)(void) -> void;
// Limits

View File

@@ -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> arena, size_t size, size_t align, bool shouldBeZeroed);
[[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
size_t align, bool shouldBeZeroed);
void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
void ArenaClear(NonNullPtr<Arena> arena);

View File

@@ -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<Byte*>(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> 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<ArenaFreeNode*>(oldPtr);
ptrdiff_t posPtr = static_cast<Byte*>(oldPtr) - reinterpret_cast<Byte*>(block);
index_t position = static_cast<index_t>(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> 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 :

View File

@@ -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<char>(arena, 128);
char* secondCharArray = ArenaPushArray<char>(arena, 128);
char* thirdCharArray = ArenaPushArray<char>(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<char*>(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<char>(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");
}