Last active
September 20, 2024 17:29
-
-
Save ineedbots/ccd66cf076e67b6033bea8fb8d965bc8 to your computer and use it in GitHub Desktop.
Array indexed buddy allocator + hash table array indexed stringlist in C99
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <assert.h> | |
#include <string.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <time.h> | |
// mt | |
typedef struct block_s block_t; | |
struct block_s | |
{ | |
size_t prev; | |
size_t next; | |
size_t padding[6]; // padding for more memory space and alignment | |
}; | |
#define MAX_DEPTH 16 | |
static block_t mem_pool[(1 << MAX_DEPTH) + 1]; | |
static size_t heads[MAX_DEPTH + 1]; // ordered array indexed linked lists | |
#ifdef _DEBUG | |
typedef struct debug_glob_s debug_glob_t; | |
struct debug_glob_s | |
{ | |
size_t sizes[(1 << MAX_DEPTH) + 1]; | |
size_t depths[(1 << MAX_DEPTH) + 1]; | |
}; | |
static debug_glob_t debug_glob; | |
#endif | |
static void init_debug() | |
{ | |
#ifdef _DEBUG | |
memset(&debug_glob, 0, sizeof(debug_glob)); | |
#endif | |
} | |
static void insert_node_debug(size_t node_num, size_t num_bytes, size_t depth) | |
{ | |
assert(depth <= MAX_DEPTH); | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
#ifdef _DEBUG | |
assert(debug_glob.sizes[node_num] == 0); | |
assert(debug_glob.depths[node_num] == 0); | |
debug_glob.sizes[node_num] = num_bytes; | |
debug_glob.depths[node_num] = depth; | |
#endif | |
} | |
static void remove_node_debug(size_t node_num, size_t num_bytes, size_t depth) | |
{ | |
assert(depth <= MAX_DEPTH); | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
#ifdef _DEBUG | |
assert(debug_glob.sizes[node_num] == num_bytes); | |
assert(debug_glob.depths[node_num] == depth); | |
debug_glob.sizes[node_num] = 0; | |
debug_glob.depths[node_num] = 0; | |
#endif | |
} | |
static size_t get_buddy(size_t depth, size_t node_num) | |
{ | |
assert(depth < MAX_DEPTH); // there is no buddy at the top | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
return ((node_num - 1) ^ ((size_t)1 << depth)) + 1; // buddy index is the XOR of the current index and 2^depth | |
} | |
static size_t get_depth(size_t num_bytes) | |
{ | |
size_t cur_num = sizeof(block_t); | |
size_t depth = 0; | |
while (cur_num != 0 && cur_num < num_bytes) | |
{ | |
cur_num <<= 1; | |
depth++; | |
} | |
return depth; | |
} | |
static bool node_in_head(size_t depth, size_t node_num) | |
{ | |
assert(depth <= MAX_DEPTH); | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
for (size_t i = heads[depth]; i != 0; i = mem_pool[i].next) | |
{ | |
if (i == node_num) | |
{ | |
return true; | |
} | |
// since list is sorted, we can stop early if i exceeds what we are searching for | |
if (i > node_num) | |
{ | |
break; | |
} | |
} | |
return false; | |
} | |
static void remove_from_head(size_t depth, size_t node_num) | |
{ | |
assert(depth <= MAX_DEPTH); | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
assert(node_in_head(depth, node_num)); | |
for (size_t i = heads[depth]; i != 0; i = mem_pool[i].next) | |
{ | |
// since list is sorted, if i exceeds what we are looking for, then we can exit | |
if (i > node_num) | |
{ | |
break; | |
} | |
if (i != node_num) | |
{ | |
continue; | |
} | |
if (i == heads[depth]) | |
{ | |
heads[depth] = mem_pool[heads[depth]].next; | |
} | |
mem_pool[mem_pool[i].prev].next = mem_pool[i].prev ? mem_pool[i].next : 0; | |
mem_pool[mem_pool[i].next].prev = mem_pool[i].next ? mem_pool[i].prev : 0; | |
return; | |
} | |
assert(false); | |
} | |
static void add_to_head(size_t depth, size_t node_num) | |
{ | |
assert(depth <= MAX_DEPTH); | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
size_t i = heads[depth]; | |
size_t prev = 0; | |
while (i != 0) | |
{ | |
assert(i != node_num); | |
// sort in accending order | |
if (i > node_num) | |
{ | |
break; | |
} | |
prev = i; | |
i = mem_pool[i].next; | |
} | |
// n is largest num in list | |
if (i == 0 && heads[depth] != 0) | |
{ | |
// append to prev | |
assert(prev != 0); | |
mem_pool[node_num].prev = prev; | |
mem_pool[node_num].next = i; | |
mem_pool[prev].next = node_num; | |
return; | |
} | |
// nothing in list, or i is greater than us | |
if (heads[depth] == i) | |
{ | |
heads[depth] = node_num; | |
} | |
// prepend to i | |
mem_pool[node_num].next = i; | |
mem_pool[node_num].prev = mem_pool[i].prev; | |
mem_pool[mem_pool[i].prev].next = i && mem_pool[i].prev ? node_num : 0; | |
mem_pool[i].prev = i ? node_num : 0; | |
} | |
void mt_print_heads() | |
{ | |
for (size_t d = 0; d < MAX_DEPTH + 1; d++) | |
{ | |
size_t i = heads[d]; | |
printf("0x%02zX 0x%05zX", d, i); | |
while (i != 0) | |
{ | |
i = mem_pool[i].next; | |
printf(" 0x%05zX", i); | |
} | |
printf("\n"); | |
} | |
} | |
void* mt_get_for_index(size_t index) | |
{ | |
assert(index > 0 && index < (1 << MAX_DEPTH) + 1); | |
#ifdef _DEBUG | |
assert(debug_glob.sizes[index] != 0); | |
#endif | |
return (void*)&mem_pool[index]; | |
} | |
size_t mt_get_for_pointer(const void* p) | |
{ | |
size_t node_num = (block_t*)p - mem_pool; | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
#ifdef _DEBUG | |
assert(debug_glob.sizes[node_num] != 0); | |
#endif | |
return node_num; | |
} | |
void* mt_alloc(size_t num_bytes) | |
{ | |
if (num_bytes == 0) | |
{ | |
printf("zero alloc!\n"); | |
return NULL; | |
} | |
size_t depth_for_size = get_depth(num_bytes); | |
size_t depth = depth_for_size; | |
if (depth > MAX_DEPTH) | |
{ | |
printf("depth too deep for size %zu\n", num_bytes); | |
return NULL; | |
} | |
// find a free list that isnt empty | |
while (heads[depth] == 0) | |
{ | |
depth++; | |
if (depth >= MAX_DEPTH) | |
{ | |
break; | |
} | |
} | |
if (depth > MAX_DEPTH || heads[depth] == 0) | |
{ | |
printf("no free list for size %zu\n", num_bytes); | |
return NULL; | |
} | |
size_t old_head = 0; | |
while (depth >= depth_for_size) | |
{ | |
old_head = heads[depth]; | |
// remove head node from list | |
remove_from_head(depth, old_head); | |
if (depth != depth_for_size) | |
{ | |
// split the current node into two and add to below list | |
add_to_head(depth - 1, old_head); | |
add_to_head(depth - 1, get_buddy(depth - 1, old_head)); | |
} | |
else if (depth == 0) | |
{ | |
break; // prevent underflow and infinite loop | |
} | |
depth--; | |
} | |
insert_node_debug(old_head, num_bytes, depth_for_size); | |
#ifdef _DEBUG | |
memset(&mem_pool[old_head], 0xCC, num_bytes); | |
#endif | |
return mt_get_for_index(old_head); | |
} | |
void mt_free(void* p, size_t num_bytes) | |
{ | |
if (p == NULL || num_bytes == 0) | |
{ | |
printf("Invalid free\n"); | |
return; | |
} | |
// get the index of the block | |
size_t node_num = mt_get_for_pointer(p); | |
size_t depth = get_depth(num_bytes); | |
assert(depth <= MAX_DEPTH); | |
assert(node_num > 0 && node_num < (1 << MAX_DEPTH) + 1); | |
#ifdef _DEBUG | |
memset(p, 0, num_bytes); | |
#endif | |
remove_node_debug(node_num, num_bytes, depth); | |
// try to coalesce the block with its buddy | |
while (depth < MAX_DEPTH) | |
{ | |
size_t buddy = get_buddy(depth, node_num); | |
if (!node_in_head(depth, buddy)) | |
{ | |
break; | |
} | |
// remove buddy from its free list | |
remove_from_head(depth, buddy); | |
// coalesce the current block with its buddy | |
if (buddy < node_num) | |
{ | |
node_num = buddy; | |
} | |
depth++; | |
} | |
// add the coalesced block to the appropriate free list | |
add_to_head(depth, node_num); | |
} | |
void mt_init() | |
{ | |
#ifdef _DEBUG | |
memset(mem_pool, 0, sizeof(mem_pool)); | |
memset(heads, 0, sizeof(heads)); | |
#endif | |
// all heads are empty | |
for (int i = 0; i < MAX_DEPTH; i++) | |
{ | |
heads[i] = 0; | |
} | |
// we have only 1 free node, and its the largest one | |
heads[MAX_DEPTH] = 1; | |
mem_pool[1].prev = 0; | |
mem_pool[1].next = 0; | |
// zero node is placeholder/marker for end of list | |
mem_pool[0].prev = 0; | |
mem_pool[0].next = 0; | |
init_debug(); | |
} | |
// sl | |
#define HASH_TABLE_SIZE_BITS 14 | |
enum | |
{ | |
STRING_LIST_HASH_STATUS_FREE, | |
STRING_LIST_HASH_STATUS_MOVABLE, | |
STRING_LIST_HASH_STATUS_HEAD, | |
}; | |
typedef struct string_list_node_s string_list_node_t; | |
struct string_list_node_s | |
{ | |
struct | |
{ | |
size_t next : HASH_TABLE_SIZE_BITS; | |
size_t status : 2; | |
}; | |
union | |
{ | |
size_t prev; | |
size_t astr; | |
}; | |
}; | |
static string_list_node_t hash_table[1 << HASH_TABLE_SIZE_BITS]; | |
#define STRING_REF_STR_PADDING 4 | |
typedef struct string_ref_s string_ref_t; | |
struct string_ref_s | |
{ | |
struct | |
{ | |
size_t len : 8; | |
size_t ref_count : 24; | |
}; | |
char str[STRING_REF_STR_PADDING]; | |
}; | |
static string_ref_t* get_ref_string(size_t astr) | |
{ | |
return (string_ref_t*)mt_get_for_index(astr); | |
} | |
static size_t hash_code(const char* str, size_t len) | |
{ | |
// len contains nullchar | |
assert(str[len - 1] == '\0'); | |
assert(str != NULL); | |
if (len >= 0xFF) | |
{ | |
return ((len >> 2) % ((1 << HASH_TABLE_SIZE_BITS) - 1)) + 1; | |
} | |
size_t hash; | |
for (hash = 0; len != 0; --len) | |
{ | |
hash = 31 * hash + *str++; | |
} | |
return (hash % ((1 << HASH_TABLE_SIZE_BITS) - 1)) + 1; | |
} | |
static bool is_str_ref(const string_ref_t* ref, const char* str, size_t len) | |
{ | |
return (ref->len == len && !memcmp(ref->str, str, len)); | |
} | |
static void free_for_len(size_t astr, size_t len) | |
{ | |
string_ref_t* ref = get_ref_string(astr); | |
assert(ref != NULL); | |
size_t head = hash_code(ref->str, len); | |
assert(head != 0); | |
assert(hash_table[head].status == STRING_LIST_HASH_STATUS_HEAD); | |
mt_free(ref, len + (sizeof(string_ref_t) - STRING_REF_STR_PADDING)); | |
size_t remove = 0; | |
size_t next = hash_table[head].next; | |
assert(next != 0); | |
assert(hash_table[next].status != STRING_LIST_HASH_STATUS_FREE); | |
// check if this is the head | |
if (hash_table[head].astr == astr) | |
{ | |
// now check if this is the only thing in the list | |
if (next == head) | |
{ | |
// we will be removing the head | |
remove = head; | |
} | |
else | |
{ | |
assert(hash_table[next].status == STRING_LIST_HASH_STATUS_MOVABLE); | |
// swap astrs | |
hash_table[head].astr = hash_table[next].astr; | |
// make the next in-line to be head | |
hash_table[head].next = hash_table[next].next; | |
remove = next; | |
} | |
} | |
else | |
{ | |
assert(hash_table[next].status == STRING_LIST_HASH_STATUS_MOVABLE); | |
// search in the list for our guy | |
size_t prev = head; | |
while (hash_table[next].astr != astr) | |
{ | |
prev = next; | |
next = hash_table[next].next; | |
assert(next != 0); | |
assert(next != head); // freeing something that doesnt exist in the list | |
assert(hash_table[next].status == STRING_LIST_HASH_STATUS_MOVABLE); | |
} | |
// remove what we found | |
hash_table[prev].next = hash_table[next].next; | |
remove = next; | |
} | |
assert(remove != 0); | |
assert(hash_table[remove].status != STRING_LIST_HASH_STATUS_FREE); | |
hash_table[remove].status = STRING_LIST_HASH_STATUS_FREE; | |
// add to free list | |
next = hash_table[0].next; | |
hash_table[remove].next = next; | |
hash_table[remove].prev = 0; | |
hash_table[next].prev = remove; | |
hash_table[0].next = remove; | |
} | |
static size_t alloc_string_ref(const char* str, size_t len) | |
{ | |
string_ref_t* ref = (string_ref_t*)mt_alloc(len + (sizeof(string_ref_t) - STRING_REF_STR_PADDING)); | |
if (ref == NULL) | |
{ | |
printf("no memory avail\n"); | |
return 0; | |
} | |
memcpy(ref->str, str, len); | |
ref->ref_count = 1; | |
ref->len = len; | |
size_t astr = mt_get_for_pointer(ref); | |
assert(astr != 0); | |
return astr; | |
} | |
static size_t find_str_in_list(size_t head, const char* str, size_t len) | |
{ | |
assert(hash_table[head].status == STRING_LIST_HASH_STATUS_HEAD); | |
string_ref_t* ref = get_ref_string(hash_table[head].astr); | |
assert(ref != NULL); | |
// check for hash collision | |
if (is_str_ref(ref, str, len)) | |
{ | |
return hash_table[head].astr; | |
} | |
// hash collision, check list for entries | |
size_t prev = head; | |
for (size_t next = hash_table[head].next; next != head; next = hash_table[next].next) | |
{ | |
assert(next != 0); | |
assert(hash_table[next].status == STRING_LIST_HASH_STATUS_MOVABLE); | |
ref = get_ref_string(hash_table[next].astr); | |
assert(ref != NULL); | |
// check if its it | |
if (is_str_ref(ref, str, len)) | |
{ | |
// move this to head of list as an optimization | |
// make it so old head is second in list | |
hash_table[prev].next = hash_table[next].next; | |
hash_table[next].next = hash_table[head].next; | |
hash_table[head].next = next; | |
// swap vals with head | |
size_t astr = hash_table[next].astr; | |
hash_table[next].astr = hash_table[head].astr; | |
hash_table[head].astr = astr; | |
return astr; | |
} | |
prev = next; | |
} | |
return 0; | |
} | |
static void add_ref_to_string(string_ref_t* ref) | |
{ | |
assert(ref != NULL); | |
ref->ref_count++; | |
assert(ref->ref_count != 0); | |
} | |
static size_t get_ref_len(const string_ref_t* ref) | |
{ | |
assert(ref != NULL); | |
size_t len = ref->len == 0 ? 0xFF : ref->len - 1; | |
while (ref->str[len] != '\0') | |
{ | |
len += 256; | |
} | |
return len; | |
} | |
static size_t find_for_len(const char* str, size_t len) | |
{ | |
size_t head = hash_code(str, len); | |
assert(head != 0); | |
// no entry for hash, not in list | |
if (hash_table[head].status != STRING_LIST_HASH_STATUS_HEAD) | |
{ | |
return 0; | |
} | |
// find it | |
return find_str_in_list(head, str, len); | |
} | |
static size_t get_for_len(const char* str, size_t len) | |
{ | |
size_t hash = hash_code(str, len); | |
assert(hash != 0); | |
// check if a list exists | |
if (hash_table[hash].status == STRING_LIST_HASH_STATUS_HEAD) | |
{ | |
size_t astr = find_str_in_list(hash, str, len); | |
if (astr != 0) | |
{ | |
add_ref_to_string(get_ref_string(astr)); | |
return astr; | |
} | |
// not in list, add it. | |
size_t next_free = hash_table[0].next; | |
if (next_free == 0) | |
{ | |
printf("no free nodes in free list\n"); | |
return 0; | |
} | |
// alloc our stuff | |
astr = alloc_string_ref(str, len); | |
if (astr == 0) | |
{ | |
return 0; | |
} | |
// now remove from free list | |
hash_table[0].next = hash_table[next_free].next; | |
hash_table[hash_table[next_free].next].prev = 0; | |
hash_table[next_free].status = STRING_LIST_HASH_STATUS_MOVABLE; | |
// make this astr the head | |
hash_table[next_free].astr = hash_table[hash].astr; | |
hash_table[hash].astr = astr; | |
// add node to head list | |
hash_table[next_free].next = hash_table[hash].next; | |
hash_table[hash].next = next_free; | |
return astr; | |
} | |
// its movable, which means we need to move the current node somewhere else so we can use the spot | |
if (hash_table[hash].status == STRING_LIST_HASH_STATUS_MOVABLE) | |
{ | |
size_t next_free = hash_table[0].next; | |
if (next_free == 0) | |
{ | |
printf("no free nodes in free list\n"); | |
return 0; | |
} | |
size_t next = hash_table[hash].next; | |
size_t prev = next; | |
while (hash_table[prev].next != hash) | |
{ | |
prev = hash_table[prev].next; | |
} | |
assert(prev != 0); | |
// alloc our stuff | |
size_t astr = alloc_string_ref(str, len); | |
if (astr == 0) | |
{ | |
return 0; | |
} | |
// remove from free list | |
hash_table[0].next = hash_table[next_free].next; | |
hash_table[hash_table[next_free].next].prev = 0; | |
hash_table[prev].next = next_free; | |
hash_table[next_free].status = STRING_LIST_HASH_STATUS_MOVABLE; | |
hash_table[next_free].next = next; | |
hash_table[next_free].astr = hash_table[hash].astr; | |
// now make a head out of this | |
hash_table[hash].status = STRING_LIST_HASH_STATUS_HEAD; | |
hash_table[hash].next = hash; | |
hash_table[hash].astr = astr; | |
return astr; | |
} | |
assert(hash_table[hash].status == STRING_LIST_HASH_STATUS_FREE); | |
// its free! lets use it | |
// alloc our stuff | |
size_t astr = alloc_string_ref(str, len); | |
if (astr == 0) | |
{ | |
return 0; | |
} | |
// remove this node from free list | |
hash_table[hash_table[hash].prev].next = hash_table[hash].next; | |
hash_table[hash_table[hash].next].prev = hash_table[hash].prev; | |
// create a new list out of head | |
hash_table[hash].status = STRING_LIST_HASH_STATUS_HEAD; | |
hash_table[hash].next = hash; | |
hash_table[hash].astr = astr; | |
return astr; | |
} | |
void sl_remove_ref(size_t astr) | |
{ | |
string_ref_t* ref = get_ref_string(astr); | |
assert(ref != NULL); | |
size_t len = get_ref_len(ref) + 1; | |
assert(len != 0); | |
assert(ref->ref_count != 0); | |
--ref->ref_count; | |
if (ref->ref_count == 0) | |
{ | |
free_for_len(astr, len); | |
} | |
} | |
void sl_add_ref(size_t astr) | |
{ | |
string_ref_t* ref = get_ref_string(astr); | |
assert(ref != NULL); | |
add_ref_to_string(ref); | |
} | |
size_t sl_find(const char* str) | |
{ | |
return find_for_len(str, strlen(str) + 1); | |
} | |
size_t sl_get(const char* str) | |
{ | |
return get_for_len(str, strlen(str) + 1); | |
} | |
const char* sl_convert(size_t astr) | |
{ | |
if (astr == 0) | |
{ | |
return NULL; | |
} | |
return get_ref_string(astr)->str; | |
} | |
void sl_init() | |
{ | |
// init free list, 0 is head | |
hash_table[0].status = STRING_LIST_HASH_STATUS_FREE; | |
size_t prev = 0; | |
for (size_t hash = 1; hash < (1 << HASH_TABLE_SIZE_BITS); hash++) | |
{ | |
hash_table[hash].status = STRING_LIST_HASH_STATUS_FREE; | |
hash_table[hash].prev = prev; | |
hash_table[prev].next = hash; | |
prev = hash; | |
} | |
hash_table[0].prev = prev; | |
} | |
// main/tests | |
void test_case_1() | |
{ | |
printf("Test Case 1: Basic Allocation and Freeing\n"); | |
void* ptr1 = mt_alloc(sizeof(block_t)); | |
assert(ptr1 != NULL); | |
mt_free(ptr1, sizeof(block_t)); | |
} | |
void test_case_2() | |
{ | |
printf("Test Case 2: Allocate two blocks, then free them in reverse order\n"); | |
void* ptr1 = mt_alloc(32); | |
void* ptr2 = mt_alloc(32); | |
assert(ptr1 != NULL && ptr2 != NULL); | |
mt_free(ptr2, 32); | |
mt_free(ptr1, 32); | |
} | |
void test_case_3() | |
{ | |
printf("Test Case 3: Minimum allocation size\n"); | |
void* ptr1 = mt_alloc(1); | |
assert(ptr1 != NULL); | |
mt_free(ptr1, 1); | |
} | |
void test_case_4() | |
{ | |
printf("Test Case 4: Maximum allocation size within the pool\n"); | |
void* ptr1 = mt_alloc((1 << MAX_DEPTH) * sizeof(block_t)); | |
assert(ptr1 != NULL); | |
mt_free(ptr1, (1 << MAX_DEPTH) * sizeof(block_t)); | |
} | |
void test_case_5() | |
{ | |
printf("Test Case 5: Allocate two adjacent blocks and free them to test coalescing\n"); | |
void* ptr1 = mt_alloc(64); | |
void* ptr2 = mt_alloc(64); | |
assert(ptr1 != NULL && ptr2 != NULL); | |
mt_free(ptr1, 64); | |
mt_free(ptr2, 64); | |
} | |
void test_case_6() | |
{ | |
printf("Test Case 6: Random order of allocation and freeing\n"); | |
void* ptr1 = mt_alloc(16); | |
void* ptr2 = mt_alloc(64); | |
void* ptr3 = mt_alloc(128); | |
void* ptr4 = mt_alloc(32); | |
assert(ptr1 != NULL && ptr2 != NULL && ptr3 != NULL && ptr4 != NULL); | |
mt_free(ptr3, 128); | |
mt_free(ptr1, 16); | |
mt_free(ptr4, 32); | |
mt_free(ptr2, 64); | |
} | |
void test_case_7() | |
{ | |
printf("Test Case 7: Fragmentation handling\n"); | |
void* ptr1 = mt_alloc(16); | |
void* ptr2 = mt_alloc(32); | |
void* ptr3 = mt_alloc(64); | |
void* ptr4 = mt_alloc(128); | |
assert(ptr1 != NULL && ptr2 != NULL && ptr3 != NULL && ptr4 != NULL); | |
mt_free(ptr2, 32); | |
mt_free(ptr4, 128); | |
void* ptr5 = mt_alloc(64); | |
assert(ptr5 != NULL); | |
mt_free(ptr1, 16); | |
mt_free(ptr3, 64); | |
mt_free(ptr5, 64); | |
} | |
void test_case_8() | |
{ | |
printf("Test Case 8: Request a block larger than the available memory\n"); | |
void* ptr1 = mt_alloc(((1 << MAX_DEPTH) * sizeof(block_t)) + 1); | |
assert(ptr1 == NULL); | |
} | |
void test_case_10() | |
{ | |
printf("Test Case 10: Multiple coalescing levels\n"); | |
void* ptr1 = mt_alloc(64); | |
void* ptr2 = mt_alloc(64); | |
void* ptr3 = mt_alloc(64); | |
void* ptr4 = mt_alloc(64); | |
assert(ptr1 != NULL && ptr2 != NULL && ptr3 != NULL && ptr4 != NULL); | |
mt_free(ptr1, 64); | |
mt_free(ptr2, 64); | |
mt_free(ptr3, 64); | |
mt_free(ptr4, 64); | |
} | |
void test_case_11() | |
{ | |
printf("Test Case 11: Stress Test\n"); | |
for (int i = 0; i < 1000; i++) | |
{ | |
void* ptr = mt_alloc((i % 256) + 1); | |
assert(ptr != NULL); | |
mt_free(ptr, (i % 256) + 1); | |
} | |
} | |
void test_case_12() | |
{ | |
printf("Test Case 12: Boundary alignment test\n"); | |
void* ptr1 = mt_alloc(128); | |
assert(ptr1 != NULL); | |
mt_free(ptr1, 128); | |
} | |
void test_case_13() | |
{ | |
printf("Test Case: Random Allocation and Freeing\n"); | |
#define num_ops 1024 | |
void* pointers[num_ops] = { 0 }; | |
size_t sizes[num_ops] = { 0 }; | |
size_t num_allocated = 0; | |
srand((unsigned int)time(NULL)); | |
for (int i = 0; i < num_ops; i++) | |
{ | |
if (rand() % 2 == 0 || num_allocated == 0) | |
{ | |
// randomly decide to allocate memory | |
size_t size = (rand() % (1 << MAX_DEPTH)) + 1; | |
void* ptr = mt_alloc(size); | |
if (ptr != NULL) | |
{ | |
pointers[num_allocated] = ptr; | |
sizes[num_allocated] = size; | |
num_allocated++; | |
} | |
} | |
else | |
{ | |
// randomly decide to free memory | |
size_t index = rand() % num_allocated; | |
void* ptr = pointers[index]; | |
size_t size = sizes[index]; | |
mt_free(ptr, size); | |
// move the last allocated pointer to the freed position to keep array compact | |
pointers[index] = pointers[num_allocated - 1]; | |
sizes[index] = sizes[num_allocated - 1]; | |
num_allocated--; | |
} | |
} | |
// free any remaining allocations | |
for (size_t i = 0; i < num_allocated; i++) | |
{ | |
mt_free(pointers[i], sizes[i]); | |
} | |
printf("Random allocation and freeing test completed.\n"); | |
} | |
void test_stringlist_basic() | |
{ | |
// Test basic string allocation | |
size_t ref1 = get_for_len("hello", 6); | |
assert(ref1 != 0); | |
string_ref_t* str_ref1 = get_ref_string(ref1); | |
assert(str_ref1 != NULL); | |
assert(str_ref1->ref_count == 1); | |
assert(str_ref1->len == 6); | |
assert(memcmp(str_ref1->str, "hello", 6) == 0); | |
// Test same string lookup returns the same reference | |
size_t ref2 = get_for_len("hello", 6); | |
assert(ref2 == ref1); | |
assert(str_ref1->ref_count == 2); // ref count should increment | |
// Test freeing a reference | |
sl_remove_ref(ref1); | |
assert(str_ref1->ref_count == 1); | |
// Test completely freeing the string (ref count reaches 0) | |
sl_remove_ref(ref2); | |
assert(str_ref1->ref_count == 0); | |
// Verify string is freed and not accessible anymore | |
size_t ref3 = find_for_len("hello", 6); | |
assert(ref3 == 0); // should not find "hello" since it was freed | |
} | |
void test_stringlist_collision_handling() | |
{ | |
// Test collision handling with different strings | |
size_t ref1 = get_for_len("hello", 6); | |
assert(ref1 != 0); | |
size_t ref2 = get_for_len("world", 6); | |
assert(ref2 != 0); | |
// Ensure they do not collide incorrectly | |
assert(ref1 != ref2); | |
string_ref_t* str_ref1 = get_ref_string(ref1); | |
string_ref_t* str_ref2 = get_ref_string(ref2); | |
assert(memcmp(str_ref1->str, "hello", 6) == 0); | |
assert(memcmp(str_ref2->str, "world", 6) == 0); | |
sl_remove_ref(ref1); | |
sl_remove_ref(ref2); | |
} | |
void test_stringlist_long_strings() | |
{ | |
// Test long string that exceeds 255 bytes | |
char long_str[300]; | |
for (size_t i = 0; i < 300; i++) | |
{ | |
long_str[i] = 'a' + (i % 26); | |
} | |
long_str[299] = '\0'; | |
size_t ref = get_for_len(long_str, sizeof(long_str)); | |
assert(ref != 0); | |
string_ref_t* str_ref = get_ref_string(ref); | |
assert(str_ref != NULL); | |
// Verify the string contents | |
assert(memcmp(str_ref->str, long_str, sizeof(long_str)) == 0); | |
sl_remove_ref(ref); | |
} | |
void test_stringlist_refcount() | |
{ | |
// Allocate a string and verify its ref count | |
size_t ref1 = get_for_len("refcount", 9); | |
assert(ref1 != 0); | |
string_ref_t* str_ref = get_ref_string(ref1); | |
assert(str_ref->ref_count == 1); | |
// Get the same string again, which should increment the ref count | |
size_t ref2 = get_for_len("refcount", 9); | |
assert(ref2 == ref1); | |
assert(str_ref->ref_count == 2); | |
// Remove one reference and verify the count decreases | |
sl_remove_ref(ref1); | |
assert(str_ref->ref_count == 1); | |
// Remove the last reference and ensure it's freed | |
sl_remove_ref(ref2); | |
assert(str_ref->ref_count == 0); | |
} | |
// Helper function to generate a random string of a given length | |
void generate_random_string(char* str, size_t length) | |
{ | |
static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |
if (length) | |
{ | |
--length; | |
for (size_t i = 0; i < length; ++i) | |
{ | |
int key = rand() % (int)(sizeof(charset) - 1); | |
str[i] = charset[key]; | |
} | |
str[length] = '\0'; | |
} | |
} | |
// Stress test case for the stringlist system | |
void stringlist_stress_test() | |
{ | |
printf("Starting stringlist stress test...\n"); | |
srand((unsigned int)time(NULL)); | |
// Array to store references to strings | |
#define MAX_STR_LEN 512 | |
#define NUM_STRINGS 5000 | |
size_t string_refs[NUM_STRINGS]; | |
size_t string_lens[NUM_STRINGS]; | |
// Phase 1: Allocate and add random strings to the system | |
for (int i = 0; i < NUM_STRINGS; i++) | |
{ | |
size_t str_len = (rand() % MAX_STR_LEN) + 1; // Random string length between 1 and MAX_STR_LEN | |
char* random_string = (char*)mt_alloc(str_len + 1); | |
if (random_string == NULL) | |
{ | |
string_refs[i] = 0; | |
continue; | |
} | |
generate_random_string(random_string, str_len + 1); | |
string_lens[i] = str_len + 1; | |
string_refs[i] = get_for_len(random_string, str_len + 1); | |
if (string_refs[i] == 0) | |
{ | |
printf("Error: Failed to allocate string: %s\n", random_string); | |
} | |
mt_free(random_string, str_len + 1); | |
} | |
printf("Allocated %d strings.\n", NUM_STRINGS); | |
// Phase 2: Randomly remove references | |
for (int i = 0; i < NUM_STRINGS; i++) | |
{ | |
// Randomly decide whether to free this string | |
if (rand() % 2 == 0 && string_refs[i] != 0) | |
{ | |
sl_remove_ref(string_refs[i]); | |
string_refs[i] = 0; | |
} | |
} | |
printf("Freed random half of the strings.\n"); | |
// Phase 3: Reallocate some of the freed strings | |
for (int i = 0; i < NUM_STRINGS; i++) | |
{ | |
if (string_refs[i] == 0) | |
{ | |
size_t str_len = (rand() % MAX_STR_LEN) + 1; | |
char* random_string = (char*)mt_alloc(str_len + 1); | |
if (random_string == NULL) | |
{ | |
continue; | |
} | |
generate_random_string(random_string, str_len + 1); | |
string_refs[i] = get_for_len(random_string, str_len + 1); | |
if (string_refs[i] == 0) | |
{ | |
printf("Error: Failed to reallocate string: %s\n", random_string); | |
} | |
mt_free(random_string, str_len + 1); | |
} | |
} | |
printf("Reallocated half of the freed strings.\n"); | |
// Phase 4: Final cleanup, remove all remaining strings | |
for (int i = 0; i < NUM_STRINGS; i++) | |
{ | |
if (string_refs[i] != 0) | |
{ | |
sl_remove_ref(string_refs[i]); | |
} | |
} | |
printf("Cleaned up all remaining strings.\n"); | |
printf("Stringlist stress test completed.\n"); | |
} | |
int main() | |
{ | |
clock_t start_time = clock(); | |
mt_init(); | |
sl_init(); | |
test_case_4(); | |
void* a = mt_alloc(32); | |
void* b = mt_alloc(32); | |
void* c = mt_alloc(64); | |
memset(a, 'a', 32); | |
memset(b, 'b', 32); | |
memset(c, 'c', 64); | |
test_case_1(); | |
test_case_2(); | |
test_case_3(); | |
test_case_5(); | |
test_case_6(); | |
test_case_7(); | |
test_case_8(); | |
test_case_10(); | |
test_case_11(); | |
test_case_12(); | |
test_case_13(); | |
assert(!memcmp(a, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 32)); | |
assert(!memcmp(b, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 32)); | |
assert(!memcmp(c, "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", 64)); | |
mt_free(a, 32); | |
mt_free(b, 32); | |
mt_free(c, 64); | |
clock_t end_time = clock(); | |
printf("All tests completed.\n"); | |
double time_elapsed = (double)(end_time - start_time) / CLOCKS_PER_SEC; | |
printf("Test completed in %f seconds.\n", time_elapsed); | |
mt_print_heads(); | |
test_stringlist_basic(); | |
test_stringlist_collision_handling(); | |
test_stringlist_long_strings(); | |
test_stringlist_refcount(); | |
stringlist_stress_test(); | |
mt_print_heads(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment