Reallocate-Arena #1
@@ -25,6 +25,8 @@ struct ByteBuffer
|
|||||||
size_t Size;
|
size_t Size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ptrdiff_t = decltype(static_cast<int*>(nullptr) - static_cast<int*>(nullptr));
|
||||||
|
|
||||||
using FunctionPtr = auto (*)(void) -> void;
|
using FunctionPtr = auto (*)(void) -> void;
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ namespace Juliet
|
|||||||
constexpr global uint64 g_Arena_Default_Commit_Size = Kilobytes(64);
|
constexpr global uint64 g_Arena_Default_Commit_Size = Kilobytes(64);
|
||||||
constexpr global uint64 k_ArenaHeaderSize = 128;
|
constexpr global uint64 k_ArenaHeaderSize = 128;
|
||||||
|
|
||||||
// Refactor
|
struct ArenaFreeNode;
|
||||||
|
|
||||||
struct Arena
|
struct Arena
|
||||||
{
|
{
|
||||||
Arena* Previous;
|
Arena* Previous;
|
||||||
@@ -29,8 +30,13 @@ namespace Juliet
|
|||||||
uint64 Committed;
|
uint64 Committed;
|
||||||
uint64 Reserved;
|
uint64 Reserved;
|
||||||
|
|
||||||
Arena* FreeLast;
|
Arena* FreeBlockLast;
|
||||||
|
|
||||||
|
ArenaFreeNode* FreeNodes;
|
||||||
|
|
||||||
|
bool AllowRealloc : 1;
|
||||||
|
|
||||||
|
JULIET_DEBUG_ONLY(uint16 LostNodeCount);
|
||||||
JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
|
JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
|
||||||
};
|
};
|
||||||
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);
|
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);
|
||||||
@@ -40,6 +46,9 @@ namespace Juliet
|
|||||||
uint64 ReserveSize = g_Arena_Default_Reserve_Size;
|
uint64 ReserveSize = g_Arena_Default_Reserve_Size;
|
||||||
uint64 CommitSize = g_Arena_Default_Commit_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.
|
// 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
|
// 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;)
|
JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
|
||||||
@@ -51,6 +60,8 @@ namespace Juliet
|
|||||||
|
|
||||||
// Raw Push, can be used but templated helpers exists below
|
// 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* 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 ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
|
||||||
void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
|
void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
|
||||||
void ArenaClear(NonNullPtr<Arena> arena);
|
void ArenaClear(NonNullPtr<Arena> arena);
|
||||||
|
|||||||
@@ -17,6 +17,20 @@ namespace Juliet
|
|||||||
constexpr uint64 k_PageSize = Kilobytes(4);
|
constexpr uint64 k_PageSize = Kilobytes(4);
|
||||||
} // namespace
|
} // 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
|
// https://github.com/EpicGamesExt/raddebugger/blob/master/src/base/base_arena.c
|
||||||
|
|
||||||
Arena* ArenaAllocate(const ArenaParams& params, const std::source_location& loc)
|
Arena* ArenaAllocate(const ArenaParams& params, const std::source_location& loc)
|
||||||
@@ -44,6 +58,9 @@ namespace Juliet
|
|||||||
arena->BasePosition = 0;
|
arena->BasePosition = 0;
|
||||||
arena->Position = k_ArenaHeaderSize;
|
arena->Position = k_ArenaHeaderSize;
|
||||||
|
|
||||||
|
arena->FreeNodes = nullptr;
|
||||||
|
arena->AllowRealloc = params.AllowRealloc;
|
||||||
|
|
||||||
arena->CanReserveMore = params.CanReserveMore;
|
arena->CanReserveMore = params.CanReserveMore;
|
||||||
|
|
||||||
return arena;
|
return arena;
|
||||||
@@ -64,6 +81,47 @@ namespace Juliet
|
|||||||
size_t positionPrePush = AlignPow2(current->Position, align);
|
size_t positionPrePush = AlignPow2(current->Position, align);
|
||||||
size_t positionPostPush = positionPrePush + size;
|
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 allowed and needed, add a new block and chain it to the arena.
|
||||||
if (current->Reserved < positionPostPush /* flags : chaining allowed */)
|
if (current->Reserved < positionPostPush /* flags : chaining allowed */)
|
||||||
{
|
{
|
||||||
@@ -72,7 +130,7 @@ namespace Juliet
|
|||||||
Arena* newBlock = nullptr;
|
Arena* newBlock = nullptr;
|
||||||
{
|
{
|
||||||
Arena* prev_block;
|
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)
|
prev_block = newBlock, newBlock = newBlock->Previous)
|
||||||
{
|
{
|
||||||
if (newBlock->Reserved >= AlignPow2(newBlock->Position, align) + size)
|
if (newBlock->Reserved >= AlignPow2(newBlock->Position, align) + size)
|
||||||
@@ -83,7 +141,7 @@ namespace Juliet
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
arena->FreeLast = newBlock->Previous;
|
arena->FreeBlockLast = newBlock->Previous;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -153,6 +211,42 @@ namespace Juliet
|
|||||||
return result;
|
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)
|
void ArenaPopTo(NonNullPtr<Arena> arena, size_t position)
|
||||||
{
|
{
|
||||||
size_t clampedPosition = ClampBottom(k_ArenaHeaderSize, position);
|
size_t clampedPosition = ClampBottom(k_ArenaHeaderSize, position);
|
||||||
@@ -162,7 +256,7 @@ namespace Juliet
|
|||||||
{
|
{
|
||||||
previous = current->Previous;
|
previous = current->Previous;
|
||||||
current->Position = k_ArenaHeaderSize;
|
current->Position = k_ArenaHeaderSize;
|
||||||
SingleLinkedListPushPrevious(arena->FreeLast, current);
|
SingleLinkedListPushPrevious(arena->FreeBlockLast, current);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No freelist :
|
// No freelist :
|
||||||
|
|||||||
@@ -139,40 +139,22 @@ namespace Juliet::UnitTest
|
|||||||
pool.FreeList = blk;
|
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
|
secondCharArray = static_cast<char*>(ArenaReallocate(arena, secondCharArray, 128, 256, alignof(char), true));
|
||||||
// Align sizes to 16 to avoid padding issues during Pop
|
|
||||||
void* pop1 = ArenaPush(&arena, 5008, 16, ConstString("Pop1"));
|
|
||||||
|
|
||||||
void* pop2 = ArenaPush(&arena, 208, 16, ConstString("Pop2"));
|
char* fourthCharArray = ArenaPushArray<char>(arena, 128);
|
||||||
// Pop Middle (Should Fail)
|
|
||||||
bool res1 = ArenaPop(&arena, pop1, 5008);
|
|
||||||
Assert(res1 == false);
|
|
||||||
|
|
||||||
// Pop Top (Should Success)
|
Assert(charArray);
|
||||||
bool res2 = ArenaPop(&arena, pop2, 208); // 200->208
|
Assert(secondCharArray);
|
||||||
Assert(res2 == true);
|
Assert(thirdCharArray);
|
||||||
|
Assert(fourthCharArray);
|
||||||
// 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);
|
|
||||||
|
|
||||||
printf("All Paged MemoryArena tests passed.\n");
|
printf("All Paged MemoryArena tests passed.\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user