library: tests: Add function to search free space within a range

This is needed to load compressed textures, as it is needed to look for
empty space in two banks at the same time.
This commit is contained in:
Antonio Niño Díaz 2022-10-29 14:10:05 +01:00
parent 8aa6b382d2
commit 6e53a01d83
3 changed files with 210 additions and 3 deletions

View File

@ -37,6 +37,12 @@ typedef struct {
int NE_AllocInit(NEChunk **first_element, void *start, void *end);
int NE_AllocEnd(NEChunk **first_element);
// This function takes a memory range defined by ["start", "end"] and tries to
// look a chunk of free memory that is at least as big as "size". It doesn't
// allocate it, that needs to be done with NE_AllocAddress(). On error, this
// function returns NULL.
void *NE_AllocFindInRange(NEChunk *first_chunk, void *start, void *end, size_t size);
// It returns 0 on success. On error, it returns a negative number.
int NE_AllocAddress(NEChunk *first_chunk, void *address, size_t size);

View File

@ -125,6 +125,56 @@ static NEChunk *ne_search_address(NEChunk *first_chunk, void *address)
return NULL;
}
void *NE_AllocFindInRange(NEChunk *first_chunk, void *start, void *end, size_t size)
{
if ((first_chunk == NULL) || (start == NULL) || (end == NULL) || (size == 0))
return NULL;
// Get the chunk that contains the first address. If the start address of
// the first chunk is after the provided start, get the first chunk.
NEChunk *this;
if (start < first_chunk->start)
this = first_chunk;
else
this = ne_search_address(first_chunk, start);
uintptr_t range_end = (uintptr_t)end;
for ( ; this != NULL; this = this->next)
{
// Is this free?
if (this->state != NE_STATE_FREE)
continue;
// "start" is inside "this", but "this->start" may be before "start". In
// that case, we need to calculate the size actually inside the range
// provided by the user.
uintptr_t real_start;
if (this->start < start)
real_start = (uintptr_t)start;
else
real_start = (uintptr_t)this->start;
uintptr_t this_end = (uintptr_t)this->end;
size_t this_size = this_end - real_start;
// Check if the requested chunk would fit here
if (this_size < size)
continue;
// Check if the expected end of the allocated chunk is within the limits
// provided by the user. If so, we've gone over the limit, there is no
// need to keep searching.
uintptr_t expected_end = real_start + size;
if (expected_end > range_end)
return NULL;
return (void *)real_start;
}
return NULL;
}
// This function searches the list and returns a chunk that contains the
// specified range of memory (address, address + size) if it is free.
static NEChunk *ne_search_free_range_chunk(NEChunk *first_chunk,

View File

@ -360,6 +360,7 @@ void test_statistics(void)
POOL_DEINITIALIZE();
}
// Count the number of chunks present in the linked list.
int count_num_chunks(NEChunk *list)
{
int count = 0;
@ -370,6 +371,7 @@ int count_num_chunks(NEChunk *list)
return count;
}
// Verify that the linked list of chunks has consistent start and end addresses.
int verify_consistency(NEChunk *list, void *start, void *end)
{
if (list == NULL)
@ -778,9 +780,157 @@ void test_alloc_range(void)
POOL_DEINITIALIZE();
}
// Tests for NE_AllocFindInRange()
void test_find_range(void)
{
printf("%s\n", __func__);
POOL_INITIALIZE();
int ret;
void *found;
void *half = (void *)(POOL_START_ADDR + POOL_SIZE / 2);
// Invalid arguments
found = NE_AllocFindInRange(NULL, POOL_START, POOL_END, 1024);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, NULL, POOL_END, 1024);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, POOL_START, NULL, 1024);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, 0);
ASSERT(found == NULL);
// A few tests with a completely empty pool
// ----------------------------------------
// Get memory from the start of a chunk
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, 1024);
ASSERT(found == POOL_START);
// Get memory from the middle of a chunk
found = NE_AllocFindInRange(alloc, half, POOL_END, 1024);
ASSERT(found == half);
// Ask for too much memory with an empty pool
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, POOL_SIZE + 1);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, half, POOL_END, POOL_SIZE);
ASSERT(found == NULL);
// The user requests to search in a range that starts before the memory pool
void *before_start = (void *)(POOL_START_ADDR - 1024);
found = NE_AllocFindInRange(alloc, before_start, POOL_END, POOL_SIZE);
ASSERT(found == POOL_START);
// The range requested by the user ends before the memory pool
found = NE_AllocFindInRange(alloc, before_start, POOL_START, 64);
ASSERT(found == NULL);
// The range requested by the user starts after the memory pool
void *after_end = (void *)(POOL_END_ADDR + 1024);
found = NE_AllocFindInRange(alloc, POOL_END, after_end, 64);
ASSERT(found == NULL);
// Now, fill the memory pool with a few chunks of data and run more tests
// ----------------------------------------------------------------------
void *one_eight = (void *)(POOL_START_ADDR + (POOL_SIZE / 8));
void *one_quarter = (void *)(POOL_START_ADDR + (POOL_SIZE / 4));
void *three_eights = (void *)(POOL_START_ADDR + (3 * POOL_SIZE / 8));
void *three_quarters = (void *)(POOL_START_ADDR + (3 * POOL_SIZE / 4));
void *five_eights = (void *)(POOL_START_ADDR + (5 * POOL_SIZE / 8));
size_t quarter = POOL_SIZE / 4;
// +-----------------+-----------------+-----------------+-----------------+
// | | USED | | USED |
// +-----------------+-----------------+-----------------+-----------------+
ret = NE_AllocAddress(alloc, one_quarter, quarter);
ASSERT(ret == 0);
ret = NE_AllocAddress(alloc, three_quarters, quarter);
ASSERT(ret == 0);
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, quarter);
ASSERT(found == POOL_START);
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, POOL_SIZE / 2);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, one_eight, POOL_END, quarter);
ASSERT(found == half);
found = NE_AllocFindInRange(alloc, three_eights, POOL_END, quarter);
ASSERT(found == half);
found = NE_AllocFindInRange(alloc, three_eights, POOL_END, POOL_SIZE / 2);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, five_eights, POOL_END, quarter);
ASSERT(found == NULL);
ret = NE_Free(alloc, one_quarter);
ASSERT(ret == 0);
ret = NE_Free(alloc, three_quarters);
ASSERT(ret == 0);
// +-----------------+-----------------+-----------------+-----------------+
// | USED | | USED | |
// +-----------------+-----------------+-----------------+-----------------+
ret = NE_AllocAddress(alloc, POOL_START, quarter);
ASSERT(ret == 0);
ret = NE_AllocAddress(alloc, half, quarter);
ASSERT(ret == 0);
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, quarter);
ASSERT(found == one_quarter);
found = NE_AllocFindInRange(alloc, POOL_START, POOL_END, POOL_SIZE / 2);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, one_eight, POOL_END, quarter);
ASSERT(found == one_quarter);
found = NE_AllocFindInRange(alloc, three_eights, POOL_END, quarter);
ASSERT(found == three_quarters);
found = NE_AllocFindInRange(alloc, three_eights, POOL_END, POOL_SIZE / 2);
ASSERT(found == NULL);
found = NE_AllocFindInRange(alloc, five_eights, POOL_END, quarter);
ASSERT(found == three_quarters);
ret = NE_Free(alloc, POOL_START);
ASSERT(ret == 0);
ret = NE_Free(alloc, half);
ASSERT(ret == 0);
POOL_DEINITIALIZE();
}
// Known random number generator to always generate the same sequence of numbers
// and make this test reproducible.
int rand(void)
int my_rand(void)
{
static unsigned long int next = 1;
next = next * 1103515245 + 12345;
@ -804,7 +954,7 @@ void test_stress(void)
for (int i = 0; i < 500000; i++)
{
unsigned int selected = rand() % NUM_PTRS;
unsigned int selected = my_rand() % NUM_PTRS;
if (ptr[selected] == NULL)
{
@ -812,7 +962,7 @@ void test_stress(void)
// small to ever fill the memory with this chunk size, so it should
// always be allocated.
size_t size = (rand() & 0x3FFF) + 1;
size_t size = (my_rand() & 0x3FFF) + 1;
void *p;
@ -862,6 +1012,7 @@ int main(void)
test_internal_list_state();
test_alloc_fill();
test_alloc_range();
test_find_range();
test_stress();
printf("Done!");