Adding base to reallocate + unit test
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user