Add ArenaAllocator::realloc

This commit is contained in:
2025-08-15 12:30:05 -04:00
parent 6c506a2ba2
commit 52f0eeee1f
3 changed files with 286 additions and 1 deletions

View File

@@ -356,3 +356,179 @@ TEST_CASE("ArenaAllocator alignment edge cases") {
CHECK(reinterpret_cast<uintptr_t>(ptr) % 128 == 0);
}
}
TEST_CASE("ArenaAllocator realloc functionality") {
ArenaAllocator arena;
SUBCASE("realloc edge cases") {
// realloc with new_size == 0 returns nullptr and reclaims memory if it's
// the last allocation
ArenaAllocator fresh_arena(256);
void *ptr = fresh_arena.allocate(100);
size_t used_before = fresh_arena.used_bytes();
CHECK(used_before == 100);
void *result = fresh_arena.realloc(ptr, 100, 0);
CHECK(result == nullptr);
CHECK(fresh_arena.used_bytes() == 0); // Memory should be reclaimed
// Test case where it's NOT the last allocation - memory cannot be reclaimed
ArenaAllocator arena2(256);
void *ptr1 = arena2.allocate(50);
void *ptr2 = arena2.allocate(50);
size_t used_before2 = arena2.used_bytes();
CHECK(used_before2 >= 100); // At least 100 bytes due to potential alignment
void *result2 =
arena2.realloc(ptr1, 50, 0); // ptr1 is not the last allocation
CHECK(result2 == nullptr);
CHECK(arena2.used_bytes() ==
used_before2); // Memory should NOT be reclaimed
// realloc with nullptr behaves like allocate
void *new_ptr = arena.realloc(nullptr, 0, 150);
CHECK(new_ptr != nullptr);
CHECK(arena.used_bytes() >= 150);
// realloc with same size returns same pointer
void *same_ptr = arena.realloc(new_ptr, 150, 150);
CHECK(same_ptr == new_ptr);
}
SUBCASE("in-place extension - growing") {
ArenaAllocator fresh_arena(1024);
void *ptr = fresh_arena.allocate(100);
CHECK(ptr != nullptr);
// Fill the allocation with test data
std::memset(ptr, 0xAB, 100);
// Should extend in place since it's the last allocation
void *extended_ptr = fresh_arena.realloc(ptr, 100, 200);
CHECK(extended_ptr == ptr); // Should be same pointer (in-place)
// Check that original data is preserved
char *data = static_cast<char *>(extended_ptr);
for (size_t i = 0; i < 100; ++i) {
CHECK(data[i] == static_cast<char>(0xAB));
}
CHECK(fresh_arena.used_bytes() == 200);
}
SUBCASE("in-place shrinking") {
ArenaAllocator fresh_arena(1024);
void *ptr = fresh_arena.allocate(200);
std::memset(ptr, 0xCD, 200);
// Should shrink in place
void *shrunk_ptr = fresh_arena.realloc(ptr, 200, 100);
CHECK(shrunk_ptr == ptr); // Same pointer
CHECK(fresh_arena.used_bytes() == 100);
// Check that remaining data is preserved
char *data = static_cast<char *>(shrunk_ptr);
for (size_t i = 0; i < 100; ++i) {
CHECK(data[i] == static_cast<char>(0xCD));
}
}
SUBCASE("copy when can't extend in place") {
ArenaAllocator fresh_arena(256); // Larger block to avoid edge cases
// Allocate first chunk
void *ptr1 = fresh_arena.allocate(60);
std::memset(ptr1, 0x11, 60);
// Allocate second chunk (this prevents in-place extension of ptr1)
void *ptr2 = fresh_arena.allocate(30);
std::memset(ptr2, 0x22, 30);
// Try to reallocate ptr1 - should copy since ptr2 is now the last
// allocation
void *realloc_ptr1 = fresh_arena.realloc(ptr1, 60, 120);
CHECK(realloc_ptr1 != ptr1); // Should be different pointer (copy occurred)
// Check that data was copied correctly
char *data = static_cast<char *>(realloc_ptr1);
for (size_t i = 0; i < 60; ++i) {
CHECK(data[i] == static_cast<char>(0x11));
}
// Try to reallocate ptr2 (last allocation) - should extend in place if
// space allows
void *realloc_ptr2 = fresh_arena.realloc(ptr2, 30, 50);
// Note: this might not be in-place if the realloc of ptr1 used up space in
// the block Let's just check the result is valid and data is preserved
CHECK(realloc_ptr2 != nullptr);
char *data2 = static_cast<char *>(realloc_ptr2);
for (size_t i = 0; i < 30; ++i) {
CHECK(data2[i] == static_cast<char>(0x22));
}
}
SUBCASE("copy when insufficient space for extension") {
ArenaAllocator fresh_arena(100);
// Allocate almost all space
void *ptr = fresh_arena.allocate(90);
std::memset(ptr, 0x33, 90);
// Try to extend beyond block size - should copy to new block
void *extended_ptr = fresh_arena.realloc(ptr, 90, 150);
CHECK(extended_ptr != ptr); // Should be different (new block)
CHECK(fresh_arena.num_blocks() == 2); // Should have created new block
// Check data preservation
char *data = static_cast<char *>(extended_ptr);
for (size_t i = 0; i < 90; ++i) {
CHECK(data[i] == static_cast<char>(0x33));
}
}
SUBCASE("realloc with custom alignment") {
ArenaAllocator fresh_arena(1024);
// Allocate with specific alignment
void *ptr = fresh_arena.allocate(50, 16);
CHECK(reinterpret_cast<uintptr_t>(ptr) % 16 == 0);
std::memset(ptr, 0x44, 50);
// Realloc with same alignment - should extend in place
void *extended_ptr = fresh_arena.realloc(ptr, 50, 100, 16);
CHECK(extended_ptr == ptr); // In place
CHECK(reinterpret_cast<uintptr_t>(extended_ptr) % 16 == 0);
// Check data preservation
char *data = static_cast<char *>(extended_ptr);
for (size_t i = 0; i < 50; ++i) {
CHECK(data[i] == static_cast<char>(0x44));
}
}
SUBCASE("realloc stress test") {
ArenaAllocator fresh_arena(512);
void *ptr = fresh_arena.allocate(50);
size_t current_size = 50;
// Fill with pattern
for (size_t i = 0; i < 50; ++i) {
static_cast<char *>(ptr)[i] = static_cast<char>(i & 0xFF);
}
// Grow in multiple steps
for (size_t new_size = 100; new_size <= 500; new_size += 100) {
void *new_ptr = fresh_arena.realloc(ptr, current_size, new_size);
CHECK(new_ptr != nullptr);
// Verify original data is still there
for (size_t i = 0; i < 50; ++i) {
CHECK(static_cast<char *>(new_ptr)[i] == static_cast<char>(i & 0xFF));
}
ptr = new_ptr;
current_size = new_size;
}
}
}