mirror of
https://github.com/AntonioND/nitro-engine.git
synced 2025-06-18 16:45:33 -04:00
library: tests: Add function to allocate from end of pool
Add tests for it.
This commit is contained in:
parent
4ec6488068
commit
3b09697a91
@ -37,8 +37,14 @@ typedef struct {
|
||||
int NE_AllocInit(NEChunk **first_element, void *start, void *end);
|
||||
int NE_AllocEnd(NEChunk **first_element);
|
||||
|
||||
// Returns NULL on error, or a valid pointer on success.
|
||||
// Allocates data at the first available space starting from the start of the
|
||||
// memory pool. Returns NULL on error, or a valid pointer on success.
|
||||
void *NE_Alloc(NEChunk *first_element, size_t size);
|
||||
|
||||
// Allocates data at the first available space starting from the end of the
|
||||
// memory pool. Returns NULL on error, or a valid pointer on success.
|
||||
void *NE_AllocFromEnd(NEChunk *first_element, size_t size);
|
||||
|
||||
// Returns 0 on success. On error, it returns a negative number.
|
||||
int NE_Free(NEChunk *first_element, void *pointer);
|
||||
|
||||
|
@ -138,6 +138,100 @@ void *NE_Alloc(NEChunk *first_chunk, size_t size)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *NE_AllocFromEnd(NEChunk *first_chunk, size_t size)
|
||||
{
|
||||
if ((first_chunk == NULL) || (size == 0))
|
||||
return NULL;
|
||||
|
||||
// Force sizes multiple of NE_ALLOC_MIN_SIZE
|
||||
const size_t mask = NE_ALLOC_MIN_SIZE - 1;
|
||||
if ((size & mask) != 0)
|
||||
size += NE_ALLOC_MIN_SIZE - (size & mask);
|
||||
|
||||
// Find last chunk
|
||||
NEChunk *this = first_chunk;
|
||||
while (this->next != NULL)
|
||||
this = this->next;
|
||||
|
||||
// Traverse list from end to beginning
|
||||
for ( ; this != NULL; this = this->previous)
|
||||
{
|
||||
// Skip non-free chunks
|
||||
if (this->state != NE_STATE_FREE)
|
||||
continue;
|
||||
|
||||
uintptr_t this_start = (uintptr_t)this->start;
|
||||
uintptr_t this_end = (uintptr_t)this->end;
|
||||
|
||||
size_t this_size = this_end - this_start;
|
||||
|
||||
// If this chunk doesn't have enough space, simply skip it.
|
||||
if (this_size < size)
|
||||
continue;
|
||||
|
||||
// If we have exactly the space requested, we're done.
|
||||
if (this_size == size)
|
||||
{
|
||||
this->state = NE_STATE_USED;
|
||||
return this->start;
|
||||
}
|
||||
|
||||
// If we have more space than requested, split this chunk:
|
||||
//
|
||||
// | THIS | NEXT |
|
||||
// +------------------+------+ Before
|
||||
// | NOT USED | USED |
|
||||
//
|
||||
// | THIS | NEW | NEXT |
|
||||
// +-----------+------+------+ After
|
||||
// | NOT USED | USED | USED |
|
||||
|
||||
// Get next chunk and create a new one.
|
||||
|
||||
NEChunk *new = malloc(sizeof(NEChunk));
|
||||
NE_AssertPointer(new, "Couldn't allocate chunk metadata.");
|
||||
|
||||
NEChunk *next = this->next;
|
||||
|
||||
// Flag the new chunk as used
|
||||
|
||||
new->state = NE_STATE_USED;
|
||||
|
||||
// Update pointers in the linked list
|
||||
// ----------------------------------
|
||||
|
||||
this->next = new;
|
||||
new->previous = this;
|
||||
|
||||
if (next == NULL)
|
||||
{
|
||||
new->next = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
new->next = next;
|
||||
next->previous = new;
|
||||
|
||||
// It shouldn't be free because deallocating a chunk should merge it
|
||||
// with any free chunk next to it.
|
||||
NE_Assert(next->state != NE_STATE_FREE,
|
||||
"Possible list corruption");
|
||||
}
|
||||
|
||||
// Update pointers to start and end of this chunk and the new chunk
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
new->end = this->end;
|
||||
new->start = (void *)(this_end - size);
|
||||
this->end = new->start;
|
||||
|
||||
return new->start;
|
||||
}
|
||||
|
||||
// No more chunks... Not enough free space.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int NE_Free(NEChunk *first_chunk, void *pointer)
|
||||
{
|
||||
if (first_chunk == NULL)
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define ASSERT(cond) \
|
||||
if (!(cond)) { \
|
||||
printf("Line %d\n", __LINE__); \
|
||||
while (1); \
|
||||
}
|
||||
|
||||
#define ASSERT_MSG(cond, msg) \
|
||||
@ -78,6 +79,42 @@ void test_alloc_align(void)
|
||||
POOL_DEINITIALIZE();
|
||||
}
|
||||
|
||||
// Test that the pointer advances as expected when allocating memory from the
|
||||
// end of the pool, and that chunks that are too small are aligned to 16 bytes.
|
||||
void test_alloc_from_end_align(void)
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
|
||||
POOL_INITIALIZE();
|
||||
|
||||
uintptr_t addr = POOL_END_ADDR;
|
||||
void *ptr;
|
||||
|
||||
addr -= 64;
|
||||
ptr = NE_AllocFromEnd(alloc, 64);
|
||||
ASSERT(A(ptr) == addr);
|
||||
|
||||
addr -= 32;
|
||||
ptr = NE_AllocFromEnd(alloc, 32);
|
||||
ASSERT(A(ptr) == addr);
|
||||
|
||||
addr -= 16;
|
||||
ptr = NE_AllocFromEnd(alloc, 16);
|
||||
ASSERT(A(ptr) == addr);
|
||||
|
||||
ASSERT_MSG(16 == NE_ALLOC_MIN_SIZE, "Unexpected NE_ALLOC_MIN_SIZE");
|
||||
|
||||
addr -= NE_ALLOC_MIN_SIZE;
|
||||
ptr = NE_AllocFromEnd(alloc, 8);
|
||||
ASSERT(A(ptr) == addr);
|
||||
|
||||
addr -= NE_ALLOC_MIN_SIZE;
|
||||
ptr = NE_AllocFromEnd(alloc, 4);
|
||||
ASSERT(A(ptr) == addr);
|
||||
|
||||
POOL_DEINITIALIZE();
|
||||
}
|
||||
|
||||
// Test that a freed chunk can be reused
|
||||
void test_free(void)
|
||||
{
|
||||
@ -104,6 +141,33 @@ void test_free(void)
|
||||
POOL_DEINITIALIZE();
|
||||
}
|
||||
|
||||
// Test that a freed chunk can be reused
|
||||
void test_free_from_end(void)
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
|
||||
POOL_INITIALIZE();
|
||||
|
||||
uintptr_t addr = POOL_END_ADDR;
|
||||
|
||||
addr -= 256;
|
||||
void *ptr1 = NE_AllocFromEnd(alloc, 256);
|
||||
ASSERT(A(ptr1) == addr);
|
||||
|
||||
addr -= 256;
|
||||
void *ptr2 = NE_AllocFromEnd(alloc, 256);
|
||||
ASSERT(A(ptr2) == addr);
|
||||
|
||||
// Free the first chunk
|
||||
int ret = NE_Free(alloc, ptr1);
|
||||
ASSERT(ret == 0);
|
||||
|
||||
void *ptr3 = NE_AllocFromEnd(alloc, 256);
|
||||
ASSERT(A(ptr3) == A(ptr1));
|
||||
|
||||
POOL_DEINITIALIZE();
|
||||
}
|
||||
|
||||
// Several tests to lock and unlock chunks
|
||||
void test_lock_unlock(void)
|
||||
{
|
||||
@ -187,6 +251,9 @@ void test_alloc_fail(void)
|
||||
ptr = NE_Alloc(alloc, 0);
|
||||
ASSERT(ptr == NULL);
|
||||
|
||||
ptr = NE_AllocFromEnd(alloc, 0);
|
||||
ASSERT(ptr == NULL);
|
||||
|
||||
// Try to allocate the maximum size
|
||||
|
||||
ptr = NE_Alloc(alloc, POOL_SIZE);
|
||||
@ -195,11 +262,20 @@ void test_alloc_fail(void)
|
||||
ret = NE_Free(alloc, ptr);
|
||||
ASSERT(ret == 0);
|
||||
|
||||
ptr = NE_AllocFromEnd(alloc, POOL_SIZE);
|
||||
ASSERT(A(ptr) == addr);
|
||||
|
||||
ret = NE_Free(alloc, ptr);
|
||||
ASSERT(ret == 0);
|
||||
|
||||
// Try to allocate more than the limit
|
||||
|
||||
void *fail = NE_Alloc(alloc, POOL_SIZE + 1);
|
||||
ASSERT(fail == NULL);
|
||||
|
||||
fail = NE_AllocFromEnd(alloc, POOL_SIZE + 1);
|
||||
ASSERT(fail == NULL);
|
||||
|
||||
// Fragment the memory pool and try to allocate the remaining space, which
|
||||
// should fail.
|
||||
|
||||
@ -221,6 +297,9 @@ void test_alloc_fail(void)
|
||||
fail = NE_Alloc(alloc, info.free);
|
||||
ASSERT(fail == NULL);
|
||||
|
||||
fail = NE_AllocFromEnd(alloc, info.free);
|
||||
ASSERT(fail == NULL);
|
||||
|
||||
POOL_DEINITIALIZE();
|
||||
}
|
||||
|
||||
@ -250,8 +329,8 @@ void test_statistics(void)
|
||||
ASSERT(A(ptr3) == addr);
|
||||
addr += size;
|
||||
|
||||
void *ptr4 = NE_Alloc(alloc, size);
|
||||
ASSERT(A(ptr4) == addr);
|
||||
void *ptr4 = NE_AllocFromEnd(alloc, size);
|
||||
ASSERT(A(ptr4) == POOL_END_ADDR - size);
|
||||
|
||||
// Free one of them
|
||||
|
||||
@ -317,6 +396,21 @@ int verify_consistency(NEChunk *list, void *start, void *end)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void print_list(NEChunk *list)
|
||||
{
|
||||
if (list == NULL)
|
||||
{
|
||||
printf("NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for ( ; list != NULL; list = list->next)
|
||||
{
|
||||
printf("%p-%p (%zu)\n", list->start, list->end,
|
||||
(size_t)(A(list->end) - A(list->start)));
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the internal linked list is in the expected state
|
||||
void test_internal_list_state(void)
|
||||
{
|
||||
@ -446,7 +540,7 @@ void test_stress(void)
|
||||
for (int i = 0; i < NUM_PTRS; i++)
|
||||
ptr[i] = NULL;
|
||||
|
||||
for (int i = 0; i < 300000; i++)
|
||||
for (int i = 0; i < 500000; i++)
|
||||
{
|
||||
unsigned int selected = rand() % NUM_PTRS;
|
||||
|
||||
@ -458,7 +552,13 @@ void test_stress(void)
|
||||
|
||||
size_t size = (rand() & 0x3FFF) + 1;
|
||||
|
||||
void *p = NE_Alloc(alloc, size);
|
||||
void *p;
|
||||
|
||||
if (size & 1)
|
||||
p = NE_Alloc(alloc, size);
|
||||
else
|
||||
p = NE_AllocFromEnd(alloc, size);
|
||||
|
||||
ASSERT(p != NULL);
|
||||
|
||||
ptr[selected] = p;
|
||||
@ -471,11 +571,13 @@ void test_stress(void)
|
||||
ptr[selected] = NULL;
|
||||
}
|
||||
|
||||
// Verify list every now and then
|
||||
if ((i % 64) == 0)
|
||||
// Verify consistency of the list
|
||||
ret = verify_consistency(alloc, POOL_START, POOL_END);
|
||||
if (ret != 0)
|
||||
{
|
||||
ret = verify_consistency(alloc, POOL_START, POOL_END);
|
||||
ASSERT(ret == 0);
|
||||
printf("ret = %d", ret);
|
||||
print_list(alloc);
|
||||
while (1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,7 +591,9 @@ int main(void)
|
||||
consoleDemoInit();
|
||||
|
||||
test_alloc_align();
|
||||
test_alloc_from_end_align();
|
||||
test_free();
|
||||
test_free_from_end();
|
||||
test_lock_unlock();
|
||||
test_alloc_fail();
|
||||
test_statistics();
|
||||
|
Loading…
Reference in New Issue
Block a user