diff --git a/build.bat b/build.bat index 1d0f9c0..e05bcf3 100644 --- a/build.bat +++ b/build.bat @@ -1,9 +1,9 @@ -@echo off -rmdir /S /Q build -mkdir build - -pushd build - -cl ../main.cpp /Zi - +@echo off +rmdir /S /Q build +mkdir build + +pushd build + +cl ../build.c /Zi /Fecgame.exe /Od /DDEBUG /MD /DEBUG /std:c11 + popd \ No newline at end of file diff --git a/build.c b/build.c new file mode 100644 index 0000000..90aa346 --- /dev/null +++ b/build.c @@ -0,0 +1,19 @@ + +/// +// Build config stuff +#define VERY_DEBUG 0 +#define RUN_TESTS 0 + +typedef struct Context_Extra { + int monkee; +} Context_Extra; +// This needs to be defined before oogabooga if we want extra stuff in context +#define CONTEXT_EXTRA Context_Extra + +#include "oogabooga/oogabooga.c" + + +// Includes for game goes here +// ... + +#include "main.c" \ No newline at end of file diff --git a/build_release.bat b/build_release.bat new file mode 100644 index 0000000..dc025a8 --- /dev/null +++ b/build_release.bat @@ -0,0 +1,13 @@ +@echo off +rmdir /S /Q build +mkdir build + +pushd build + +mkdir release +pushd release + +cl ../../main.c /Zi /Fecgame.exe /Ox /DRELEASE /std:c11 + +popd +popd \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..829061a --- /dev/null +++ b/main.c @@ -0,0 +1,60 @@ + +int main() { + printf("Program started\n"); + oogabooga_init(1024 * 1024 * 100); // Start with 100mb program memory + + // alloc calls to context.allocator.proc which by default is set to the + // heap allocator in memory.c + int *a = cast(int*)alloc(sizeof(int)); + dealloc(a); + + // We can do an old school memory dump to save our game with the global variables + // (void*) program_memory and (u64) program_memory_size + // program_memory will ALWAYS have the same virtual memory base address, so all + // pointers will remain valid when read back from disk. + + // We can push allocator like jai + push_allocator(temp); + // all calls to alloc() here will be with the temporary allocator + pop_allocator(); // But this is C so we have to pop it manually! + + // or we can just do this for temporary allocation + int *b = talloc(sizeof(int)); + + // Call each frame + reset_temporary_storage(); + + Context c = context; + // ... modify c + push_context(c); + // ... do stuff in modified context + pop_context(); + + // For now, context only has the allocator and CONTEXT_EXTRA which can be defined + // to add some custom stuff to context + // But it needs to be in build.c before including oogabooga.c. + // #define CONTEXT_EXTRA struct { int monkee; } + context.extra.monkee = 69; + + + // This can be disabled in build.c +#if RUN_TESTS + oogabooga_run_tests(); +#endif + + int hello; + hello = 5; + +#ifdef DEBUG + printf("Hello, balls! (debug)\n"); +#endif + +#ifdef RELEASE + printf("Hello, balls! (release)\n"); +#endif + + printf("Program exit as expected\n"); + + return 0; +} + diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 6bd983f..0000000 --- a/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int main() { - - int hello; - hello = 5; - - printf("Hello, balls!\n"); - return 0; -} \ No newline at end of file diff --git a/oogabooga/base.c b/oogabooga/base.c new file mode 100644 index 0000000..05773c2 --- /dev/null +++ b/oogabooga/base.c @@ -0,0 +1,92 @@ + +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +typedef float f32; +typedef double f64; +typedef f32 float32; +typedef f64 float64; + +typedef u8 bool; +#define false 0 +#define true 1 + +#define thread_local _Thread_local + +#ifdef _MSC_VER + #define os_debug_break __debugbreak +#else + #error "Only msvc compiler supported at the moment"; +#endif + +#define assert(cond, ...) if (!(cond)) { printf("Assertion failed for condition: " #cond ". Message: " __VA_ARGS__); os_debug_break(); } + +#ifndef max + #define max(a, b) ((a) > (b) ? (a) : (b)) + #define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define cast(t) (t) + +typedef struct Nothing {int nothing;} Nothing; + +#ifndef CONTEXT_EXTRA + #define CONTEXT_EXTRA Nothing +#endif + +typedef enum Allocator_Message { + ALLOCATOR_ALLOCATE, + ALLOCATOR_DEALLOCATE, +} Allocator_Message; +typedef void*(*Allocator_Proc)(u64, void*, Allocator_Message); + +typedef struct Allocator { + Allocator_Proc proc; + void *data; +} Allocator; + +typedef struct Context { + Allocator allocator; + + CONTEXT_EXTRA extra; +} Context; + + +#define CONTEXT_STACK_MAX 512 +thread_local Context context; +thread_local Context context_stack[CONTEXT_STACK_MAX]; +thread_local u64 num_contexts = 0; + + +void* alloc(u64 size) { return context.allocator.proc(size, NULL, ALLOCATOR_ALLOCATE); } +void dealloc(void *p) { context.allocator.proc(0, p, ALLOCATOR_DEALLOCATE); } + + + +void push_context(Context c) { + assert(num_contexts < CONTEXT_STACK_MAX, "Context stack overflow"); + + context_stack[num_contexts] = context; + context = c; + num_contexts += 1; +} +void pop_context() { + assert(num_contexts > 0, "No contexts to pop!"); + num_contexts -= 1; + context = context_stack[num_contexts]; +} + +void push_allocator(Allocator a) { + Context c = context; + c.allocator = a; + push_context(c); +} +void pop_allocator() { pop_context(); } diff --git a/oogabooga/memory.c b/oogabooga/memory.c new file mode 100644 index 0000000..f4ad2cf --- /dev/null +++ b/oogabooga/memory.c @@ -0,0 +1,433 @@ + + +/// +/// +// Basic general heap allocator, free list +/// +// Technically thread safe but synchronization is horrible. +// Fragmentation is catastrophic. +// We could fix it by merging free nodes every now and then +// BUT: We aren't really supposed to allocate/deallocate directly on the heap too much anyways... + +#define DEFAULT_HEAP_BLOCK_SIZE min((os.page_size * 1024ULL * 50ULL), program_memory_size) +#define HEAP_ALIGNMENT (sizeof(Heap_Free_Node)) +typedef struct Heap_Free_Node Heap_Free_Node; +typedef struct Heap_Block Heap_Block; + +typedef struct Heap_Free_Node { + u64 size; + Heap_Free_Node *next; +} Heap_Free_Node; + +typedef struct Heap_Block { + u64 size; + Heap_Free_Node *free_head; + void* start; + Heap_Block *next; + // 32 bytes !! +} Heap_Block; + +typedef struct { + u64 size; + Heap_Block *block; +} Heap_Allocation_Metadata; + +Heap_Block *heap_head; +bool heap_initted = false; +Mutex_Handle heap_mutex; // This is terrible but I don't care for now + + +u64 get_heap_block_size_excluding_metadata(Heap_Block *block) { + return block->size - sizeof(Heap_Block); +} +u64 get_heap_block_size_including_metadata(Heap_Block *block) { + return block->size; +} + +bool is_pointer_in_program_memory(void *p) { + return (u8*)p >= (u8*)program_memory && (u8*)p<((u8*)program_memory+program_memory_size); +} + +// Meant for debug +void santiy_check_free_node_tree(Heap_Block *block) { + Heap_Free_Node *node = block->free_head; + + u64 total_free = 0; + while (node != 0) { + Heap_Free_Node *other_node = node->next; + + while (other_node != 0) { + assert(other_node != node, "Circular reference in heap free node tree. That's bad."); + other_node = other_node->next; + } + total_free += node->size; + assert(total_free <= block->size, "Free nodes are fucky wucky"); + node = node->next; + } + +} + +typedef struct { + Heap_Free_Node *best_fit; + Heap_Free_Node *previous; + u64 delta; +} Heap_Search_Result; +Heap_Search_Result search_heap_block(Heap_Block *block, u64 size) { + + if (block->free_head == 0) return (Heap_Search_Result){0, 0, 0}; + + Heap_Free_Node *node = block->free_head; + Heap_Free_Node *previous = 0; + + Heap_Free_Node *best_fit = 0; + Heap_Free_Node *before_best_fit = 0; + u64 best_fit_delta = 0; + + while (node != 0) { + + if (node->size == size) { + Heap_Search_Result result; + result.best_fit = node; + result.previous = previous; + result.delta = 0; + assert(result.previous != result.best_fit); + return result; + } + + if (node->size >= size) { + u64 delta = node->size - size; + + if (delta < best_fit_delta || !best_fit) { + before_best_fit = previous; + best_fit = node; + best_fit_delta = delta; + } + } + + if (node->next) previous = node; + node = node->next; + } + + if (!best_fit) return (Heap_Search_Result){0, 0, 0}; + + Heap_Search_Result result; + result.best_fit = best_fit; + result.previous = before_best_fit; + result.delta = best_fit_delta; + assert(result.previous != result.best_fit); + return result; +} + +Heap_Block *make_heap_block(Heap_Block *parent, u64 size) { + + size = (size) & ~(HEAP_ALIGNMENT-1); + + Heap_Block *block; + if (parent) { + block = (Heap_Block*)(((u8*)parent)+get_heap_block_size_including_metadata(parent)); + parent->next = block; + } else { + block = (Heap_Block*)program_memory; + } + + + + // #Speed #Cleanup + if (((u8*)block)+size >= ((u8*)program_memory)+program_memory_size) { + u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1; + u64 new_program_size = (cast(u64)(minimum_size * 1.5)); + assert(new_program_size >= minimum_size, "Bröd"); + printf("Growing program memory to %llu bytes\n", new_program_size); + const u64 ATTEMPTS = 1000; + for (u64 i = 0; i <= ATTEMPTS; i++) { + if (program_memory_size >= new_program_size) break; // Another thread might have resized already, causing it to fail here. + assert(i < ATTEMPTS, "OS is not letting us allocate more memory. Maybe we are out of memory?"); + if (os_grow_program_memory(new_program_size)) + break; + } + } + + block->start = ((u8*)block)+sizeof(Heap_Block); + block->size = size; + block->next = 0; + block->free_head = (Heap_Free_Node*)block->start; + block->free_head->size = get_heap_block_size_excluding_metadata(block); + block->free_head->next = 0; + + return block; +} + +void heap_init() { + heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE); + heap_mutex = os_make_mutex(); + heap_initted = true; +} + +void *heap_alloc(u64 size) { + // #Sync #Speed oof + os_lock_mutex(heap_mutex); + + if (!heap_initted) heap_init(); + + size += sizeof(Heap_Allocation_Metadata); + + size = (size+HEAP_ALIGNMENT) & ~(HEAP_ALIGNMENT-1); + + assert(size < DEFAULT_HEAP_BLOCK_SIZE, "Past Charlie has been lazy and did not handle large allocations like this. I apologize on behalf of past Charlie. A quick fix could be to increase the heap block size for now."); + + Heap_Block *block = heap_head; + Heap_Block *last_block = 0; + Heap_Free_Node *best_fit = 0; + Heap_Block *best_fit_block = 0; + Heap_Free_Node *previous = 0; + u64 best_fit_delta = 0; + // #Speed + // Maybe instead of going through EVERY free node to find best fit we do a good-enough fit + while (block != 0) { + Heap_Search_Result result = search_heap_block(block, size); + Heap_Free_Node *node = result.best_fit; + if (node) { + if (node->size < size) continue; + if (node->size == size) { + best_fit = node; + best_fit_block = block; + previous = result.previous; + best_fit_delta = 0; + break; + } + + u64 delta = node->size-size; + if (delta < best_fit_delta || !best_fit) { + best_fit = node; + best_fit_block = block; + previous = result.previous; + best_fit_delta = delta; + } + } + + last_block = block; + block = block->next; + } + + if (!best_fit) { + block = make_heap_block(last_block, DEFAULT_HEAP_BLOCK_SIZE); + previous = 0; + best_fit = block->free_head; + best_fit_block = block; + } + + + // Ideally this should not be possible. + // If we run out of program_memory, we should just grow it and if that fails + // we crash because out of memory. + assert(best_fit != 0, "Internal heap allocation failed"); + + Heap_Free_Node *new_free_node = 0; + if (size != best_fit->size) { + u64 remainder = best_fit->size - size; + new_free_node = (Heap_Free_Node*)(((u8*)best_fit)+size); + new_free_node->size = remainder; + new_free_node->next = best_fit->next; + } + + if (previous && new_free_node) { + assert(previous->next == best_fit, "Bro what"); + previous->next = new_free_node; + } else if (previous) { + assert(previous->next == best_fit, "Bro what"); + previous->next = best_fit->next; + } + + if (best_fit_block->free_head == best_fit) { + // If we allocated the first free node then replace with new free node or just + // remove it if perfect fit. + if (new_free_node) { + new_free_node->next = best_fit_block->free_head->next; + best_fit_block->free_head = new_free_node; + } else best_fit_block->free_head = best_fit_block->free_head->next; + } + + Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)best_fit; + meta->size = size; + meta->block = best_fit_block; + +#if VERY_DEBUG + santiy_check_free_node_tree(meta->block); +#endif + + // #Sync #Speed oof + os_unlock_mutex(heap_mutex); + + return ((u8*)meta)+sizeof(Heap_Allocation_Metadata); +} +void heap_dealloc(void *p) { + // #Sync #Speed oof + os_lock_mutex(heap_mutex); + + if (!heap_initted) heap_init(); + + assert(is_pointer_in_program_memory(p), "Garbage pointer; out of program memory bounds!"); + p = (u8*)p-sizeof(Heap_Allocation_Metadata); + Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)(p); + + // If > 256GB then prolly not legit lol + assert(meta->size < 1024ULL*1024ULL*1024ULL*256ULL, "Garbage pointer passed to heap_dealloc !!!"); + assert(is_pointer_in_program_memory(meta->block), "Garbage pointer passed to heap_dealloc !!!"); + + // Yoink meta data before we start overwriting it + Heap_Block *block = meta->block; + u64 size = meta->size; + + Heap_Free_Node *new_node = cast(Heap_Free_Node*)p; + new_node->size = size; + + if (new_node < block->free_head) { + if ((u8*)new_node+size == (u8*)block->free_head) { + new_node->size = size + block->free_head->size; + new_node->next = block->free_head->next; + block->free_head = new_node; + } else { + new_node->next = block->free_head; + block->free_head = new_node; + } + } else { + Heap_Free_Node *node = block->free_head; + + + while (true) { + + assert(node != 0, "We didn't find where the free node should be! uh oh"); + + if (new_node > node) { + u8* node_tail = (u8*)node + node->size; + if (cast(u8*)new_node == node_tail) { + node->size += new_node->size; + break; + } else { + new_node->next = node->next; + node->next = new_node; + + u8* new_node_tail = (u8*)new_node + new_node->size; + if (new_node->next && (u8*)new_node->next == new_node_tail) { + new_node->size += new_node->next->size; + new_node->next = new_node->next->next; + } + break; + } + } + + node = node->next; + } + } + +#if VERY_DEBUG + santiy_check_free_node_tree(block); +#endif + + // #Sync #Speed oof + os_unlock_mutex(heap_mutex); +} +void log_heap() { + os_lock_mutex(heap_mutex); + printf("\nHEAP:\n"); + + Heap_Block *block = heap_head; + + while (block != 0) { + + printf("\tBLOCK @ 0x%I64x, %llu bytes\n", (u64)block, block->size); + + Heap_Free_Node *node = block->free_head; + + u64 total_free = 0; + + while (node != 0) { + + printf("\t\tFREE NODE @ 0x%I64x, %llu bytes\n", (u64)node, node->size); + + total_free += node->size; + + node = node->next; + } + + printf("\t TOTAL FREE: %llu\n\n", total_free); + + block = block->next; + } + os_unlock_mutex(heap_mutex); +} + + +/// +/// +// Temporary storage +/// + +#ifndef TEMPORARY_STORAGE_SIZE + #define TEMPORARY_STORAGE_SIZE (1024ULL*1024ULL*16ULL) // 16mb +#endif + +void* talloc(u64); +void* temp_allocator_proc(u64 size, void *p, Allocator_Message message); + +thread_local void * temporary_storage = 0; +thread_local bool temporary_storage_initted = false; +thread_local void * temporary_storage_pointer = 0; +thread_local bool has_warned_temporary_storage_overflow = false; +thread_local Allocator temp; + + +void* temp_allocator_proc(u64 size, void *p, Allocator_Message message) { + switch (message) { + case ALLOCATOR_ALLOCATE: { + return talloc(size); + break; + } + case ALLOCATOR_DEALLOCATE: { + return 0; + } + } + return 0; +} + +void temporary_storage_init() { + if (temporary_storage_initted) return; + + temporary_storage = heap_alloc(TEMPORARY_STORAGE_SIZE); + assert(temporary_storage, "Failed allocating temporary storage"); + temporary_storage_pointer = temporary_storage; + + temp.proc = temp_allocator_proc; + temp.data = 0; + + temporary_storage_initted = true; +} + +void* talloc(u64 size) { + if (!temporary_storage_initted) temporary_storage_init(); + + assert(size < TEMPORARY_STORAGE_SIZE, "Bruddah this is too large for temp allocator"); + + void* p = temporary_storage_pointer; + + temporary_storage_pointer = (u8*)temporary_storage_pointer + size; + + if ((u8*)temporary_storage_pointer >= (u8*)temporary_storage+TEMPORARY_STORAGE_SIZE) { + if (!has_warned_temporary_storage_overflow) { + printf("WARNING: temporary storage was overflown, we wrap around at the start.\n"); + } + temporary_storage_pointer = temporary_storage; + return talloc(size);; + } + + return p; +} + +void reset_temporary_storage() { + if (!temporary_storage_initted) temporary_storage_init(); + + temporary_storage_pointer = temporary_storage; + + has_warned_temporary_storage_overflow = true; +} + diff --git a/oogabooga/oogabooga.c b/oogabooga/oogabooga.c new file mode 100644 index 0000000..6d4d5b2 --- /dev/null +++ b/oogabooga/oogabooga.c @@ -0,0 +1,29 @@ + +#include // #Cleanup just using this for printf +#include + +#include "base.c" + +#include "os_interface.c" + +#include "memory.c" + + + + +#ifdef OS_WINDOWS + #include "os_impl_windows.c" +#elif defined (OS_LINUX) + +#elif defined (OS_MAC) + +#endif + +#include "tests.c" + + +void oogabooga_init(u64 program_memory_size) { + os_init(program_memory_size); + heap_init(); + temporary_storage_init(); +} \ No newline at end of file diff --git a/oogabooga/os_impl_windows.c b/oogabooga/os_impl_windows.c new file mode 100644 index 0000000..62b44fb --- /dev/null +++ b/oogabooga/os_impl_windows.c @@ -0,0 +1,174 @@ + + +#define VIRTUAL_MEMORY_BASE ((void*)0x0000010000000000ULL) + +void* heap_alloc(u64); +void heap_dealloc(void*); + +void* heap_allocator_proc(u64 size, void *p, Allocator_Message message) { + switch (message) { + case ALLOCATOR_ALLOCATE: { + return heap_alloc(size); + break; + } + case ALLOCATOR_DEALLOCATE: { + heap_dealloc(p); + return 0; + } + } + return 0; +} + +void os_init(u64 program_memory_size) { + + SYSTEM_INFO si; + GetSystemInfo(&si); + os.granularity = cast(u64)si.dwAllocationGranularity; + os.page_size = cast(u64)si.dwPageSize; + + + program_memory_mutex = os_make_mutex(); + os_grow_program_memory(program_memory_size); + + Allocator heap_allocator; + + heap_allocator.proc = heap_allocator_proc; + heap_allocator.data = 0; + + context.allocator = heap_allocator; + +} + +bool os_grow_program_memory(u64 new_size) { + os_lock_mutex(program_memory_mutex); // #Sync + if (program_memory_size >= new_size) { + os_unlock_mutex(program_memory_mutex); // #Sync + return true; + } + + + + bool is_first_time = program_memory == 0; + + if (is_first_time) { + u64 aligned_size = (new_size+os.granularity) & ~(os.granularity); + void* aligned_base = (void*)(((u64)VIRTUAL_MEMORY_BASE+os.granularity) & ~(os.granularity-1)); + + u64 m = aligned_size & os.granularity; + assert(m == 0); + + program_memory = VirtualAlloc(aligned_base, aligned_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (program_memory == 0) { + os_unlock_mutex(program_memory_mutex); // #Sync + return false; + } + program_memory_size = aligned_size; + } else { + void* tail = (u8*)program_memory + program_memory_size; + u64 m = ((u64)program_memory_size % os.granularity); + assert(m == 0, "program_memory_size is not aligned to granularity!"); + m = ((u64)tail % os.granularity); + assert(m == 0, "Tail is not aligned to granularity!"); + u64 amount_to_allocate = new_size-program_memory_size; + amount_to_allocate = ((amount_to_allocate+os.granularity)&~(os.granularity-1)); + m = ((u64)amount_to_allocate % os.granularity); + assert(m == 0, "amount_to_allocate is not aligned to granularity!"); + // Just keep allocating at the tail of the current chunk + void* result = VirtualAlloc(tail, amount_to_allocate, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (result == 0) { + os_unlock_mutex(program_memory_mutex); // #Sync + return false; + } + assert(tail == result, "It seems tail is not aligned properly. o nein"); + + program_memory_size += amount_to_allocate; + + m = ((u64)program_memory_size % os.granularity); + assert(m == 0, "program_memory_size is not aligned to granularity!"); + } + + + + os_unlock_mutex(program_memory_mutex); // #Sync + return true; +} + +Mutex_Handle os_make_mutex() { + return CreateMutex(0, FALSE, 0); +} +void os_destroy_mutex(Mutex_Handle m) { + CloseHandle(m); +} +void os_lock_mutex(Mutex_Handle m) { + DWORD wait_result = WaitForSingleObject(m, INFINITE); + + switch (wait_result) { + case WAIT_OBJECT_0: + break; + + case WAIT_ABANDONED: + break; + + default: + assert(false, "Unexpected mutex lock result"); + break; + } +} +void os_unlock_mutex(Mutex_Handle m) { + BOOL result = ReleaseMutex(m); + assert(result, "Unlock mutex failed"); +} + +DWORD WINAPI win32_thread_invoker(LPVOID param) { + Thread *t = (Thread*)param; + temporary_storage_init(); + context = t->initial_context; + t->proc(t); + return 0; +} + +Thread* os_make_thread(Thread_Proc proc) { + Thread *t = (Thread*)alloc(sizeof(Thread)); + t->id = 0; // This is set when we start it + t->proc = proc; + t->initial_context = context; + + return t; +} +void os_start_thread(Thread *t) { + t->os_handle = CreateThread( + 0, + 0, + win32_thread_invoker, + t, + 0, + (DWORD*)&t->id + ); + + assert(t->os_handle, "Failed creating thread"); +} +void os_join_thread(Thread *t) { + WaitForSingleObject(t->os_handle, INFINITE); + CloseHandle(t->os_handle); +} + +void os_sleep(u32 ms) { + Sleep(ms); +} + +void os_yield_thread() { + SwitchToThread(); +} + +#include +u64 os_get_current_cycle_count() { + return __rdtsc(); +} + +float64 os_get_current_time_in_seconds() { + LARGE_INTEGER frequency, counter; + if (!QueryPerformanceFrequency(&frequency) || !QueryPerformanceCounter(&counter)) { + return -1.0; + } + return (double)counter.QuadPart / (double)frequency.QuadPart; +} \ No newline at end of file diff --git a/oogabooga/os_interface.c b/oogabooga/os_interface.c new file mode 100644 index 0000000..4d69c87 --- /dev/null +++ b/oogabooga/os_interface.c @@ -0,0 +1,83 @@ + + +#ifdef _WIN32 + #include + #define OS_WINDOWS + + typedef HANDLE Mutex_Handle; + typedef HANDLE Thread_Handle; + +#elif defined(__linux__) + // Include whatever #Incomplete #Portability + #define OS_LINUX + + typedef SOMETHING Mutex_Handle; + typedef SOMETHING Thread_Handle; + +#elif defined(__APPLE__) && defined(__MACH__) + // Include whatever #Incomplete #Portability + #define OS_MAC + + typedef SOMETHING Mutex_Handle; + typedef SOMETHING Thread_Handle; +#else + #error "Current OS not supported!"; +#endif + +typedef struct Os_Info { + u64 page_size; + u64 granularity; +} Os_Info; +Os_Info os; + + + + +void* program_memory = 0; +u64 program_memory_size = 0; +Mutex_Handle program_memory_mutex = 0; + +bool os_grow_program_memory(size_t new_size); + +/// +/// +// Threading +/// + +typedef struct Thread Thread; + +typedef void(*Thread_Proc)(Thread*); + +typedef struct Thread { + u64 id; + Context initial_context; + void* data; + Thread_Proc proc; + Thread_Handle os_handle; + +} Thread; + +/// +// Thread primitive +Thread* os_make_thread(); +void os_start_thread(Thread* t); +void os_join_thread(Thread* t); + +void os_sleep(u32 ms); +void os_yield_thread(); + +/// +// Mutex primitive +Mutex_Handle os_make_mutex(); +void os_destroy_mutex(Mutex_Handle m); +void os_lock_mutex(Mutex_Handle m); +void os_unlock_mutex(Mutex_Handle m); + + +/// +/// +// Time +/// + +u64 os_get_current_cycle_count(); +float64 os_get_current_time_in_seconds(); \ No newline at end of file diff --git a/oogabooga/tests.c b/oogabooga/tests.c new file mode 100644 index 0000000..1ba3594 --- /dev/null +++ b/oogabooga/tests.c @@ -0,0 +1,243 @@ + + +void test_allocator(bool do_log_heap) { + // Basic allocation and free + int* a = (int*)alloc(sizeof(int)); + int* b = (int*)alloc(sizeof(int)); + int* c = (int*)alloc(sizeof(int)); + + *a = 69; + *b = 420; + *c = 1337; + + assert(*a == 69, "Test failed: Memory corrupted"); + assert(*b == 420, "Test failed: Memory corrupted"); + assert(*c == 1337, "Test failed: Memory corrupted"); + + // Test growing memory + os_grow_program_memory(1024 * 1024 * 1000); + + assert(*a == 69, "Test failed: Memory corrupted"); + assert(*b == 420, "Test failed: Memory corrupted"); + assert(*c == 1337, "Test failed: Memory corrupted"); + + // Allocate and free large block + void* large_block = alloc(1024 * 1024 * 100); + dealloc(large_block); + + // Allocate multiple small blocks + void* blocks[100]; + for (int i = 0; i < 100; ++i) { + blocks[i] = alloc(128); + assert(blocks[i] != NULL, "Failed to allocate small block"); + } + + for (int i = 0; i < 100; ++i) { + dealloc(blocks[i]); + } + + // Stress test with various sizes + for (int i = 1; i <= 1000; ++i) { + void* p = alloc(i * 64); + assert(p != NULL, "Failed to allocate varying size block"); + dealloc(p); + } + + // Free in reverse order + for (int i = 0; i < 100; ++i) { + blocks[i] = alloc(128); + assert(blocks[i] != NULL, "Failed to allocate small block"); + } + + for (int i = 99; i >= 0; --i) { + dealloc(blocks[i]); + } + + // Test memory integrity with various allocation patterns + int* nums[10]; + for (int i = 0; i < 10; ++i) { + nums[i] = (int*)alloc(sizeof(int) * 10); + for (int j = 0; j < 10; ++j) { + nums[i][j] = i * 10 + j; + } + } + + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + assert(nums[i][j] == i * 10 + j, "Memory corruption detected"); + } + dealloc(nums[i]); + } + + + push_allocator(temp); + + int* foo = (int*)alloc(72); + *foo = 1337; + void* bar = alloc(69); + void* baz = alloc(420); + + assert(*foo == 1337, "Temp memory corruptada"); + + int* old_foo = foo; + + reset_temporary_storage(); + + foo = (int*)alloc(72); + + assert(old_foo == foo, "Temp allocator goof"); + + pop_allocator(); + + // Repeated Allocation and Free + for (int i = 0; i < 10000; ++i) { + void* temp = alloc(128); + assert(temp != NULL && "Repeated allocation failed"); + dealloc(temp); + } + + // Mixed Size Allocations + void* mixed_blocks[200]; + for (int i = 0; i < 200; ++i) { + if (i % 2 == 0) { + mixed_blocks[i] = alloc(128); + } else { + mixed_blocks[i] = alloc(1024 * 1024); // 1MB blocks + } + assert(mixed_blocks[i] != NULL && "Mixed size allocation failed"); + } + + for (int i = 0; i < 200; ++i) { + if (i % 2 == 0) { + dealloc(mixed_blocks[i]); + } + } + + for (int i = 0; i < 200; ++i) { + if (i % 2 != 0) { + dealloc(mixed_blocks[i]); + } + } + + // Fragmentation Stress Test + for (int i = 0; i < 50; ++i) { + blocks[i] = alloc(256); + assert(blocks[i] != NULL && "Failed to allocate small block for fragmentation test"); + } + + for (int i = 0; i < 50; i += 2) { + dealloc(blocks[i]); + } + + for (int i = 50; i < 100; ++i) { + blocks[i] = alloc(128); + assert(blocks[i] != NULL && "Failed to allocate small block in fragmented heap"); + } + + for (int i = 50; i < 100; ++i) { + dealloc(blocks[i]); + } + + for (int i = 1; i < 50; i += 2) { + dealloc(blocks[i]); + } + + if (do_log_heap) log_heap(); +} + +void test_thread_proc1(Thread* t) { + os_sleep(5); + printf("Hello from thread %llu\n", t->id); + os_sleep(5); + printf("Hello from thread %llu\n", t->id); + os_sleep(5); + printf("Hello from thread %llu\n", t->id); + os_sleep(5); + printf("Hello from thread %llu\n", t->id); + os_sleep(5); + printf("Hello from thread %llu\n", t->id); +} +Mutex_Handle test_mutex; +void test_thread_proc2(Thread* t) { + os_lock_mutex(test_mutex); + printf("Thread %llu part 1\n", t->id); + os_sleep(1); + printf("Thread %llu part 2\n", t->id); + os_sleep(1); + printf("Thread %llu part 3\n", t->id); + os_unlock_mutex(test_mutex); +} +void test_threads() { + Thread* t = os_make_thread(test_thread_proc1); + os_start_thread(t); + os_sleep(10); + printf("This should be printed in middle of thread execution\n"); + os_join_thread(t); + printf("Thread is joined\n"); + + Mutex_Handle m = os_make_mutex(); + os_lock_mutex(m); + os_unlock_mutex(m); + + + test_mutex = os_make_mutex(); + Thread *threads[100]; + for (int i = 0; i < 100; i++) { + threads[i] = os_make_thread(test_thread_proc2); + os_start_thread(threads[i]); + } + for (int i = 0; i < 100; i++) { + os_join_thread(threads[i]); + } +} + +void test_allocator_threaded(Thread *t) { + for (int i = 0; i < 1000; ++i) { + void* temp = alloc(128); + assert(temp != NULL && "Repeated allocation failed"); + dealloc(temp); + } + + void* mixed_blocks[40]; + for (int i = 0; i < 40; ++i) { + if (i % 2 == 0) { + mixed_blocks[i] = alloc(128); + } else { + mixed_blocks[i] = alloc(1024 * 1024); // 1MB blocks + } + assert(mixed_blocks[i] != NULL && "Mixed size allocation failed"); + } + + for (int i = 0; i < 40; ++i) { + if (i % 2 == 0) { + dealloc(mixed_blocks[i]); + } + } + + for (int i = 0; i < 40; ++i) { + if (i % 2 != 0) { + dealloc(mixed_blocks[i]); + } + } +} + +void oogabooga_run_tests() { + printf("Testing allocator...\n"); + test_allocator(true); + printf("OK!\n"); + + printf("Testing threads...\n"); + test_threads(); + printf("OK!\n"); + + printf("Thread bombing allocator...\n"); + Thread* threads[100]; + for (int i = 0; i < 100; i++) { + threads[i] = os_make_thread(test_allocator_threaded); + os_start_thread(threads[i]); + } + for (int i = 0; i < 100; i++) { + os_join_thread(threads[i]); + } + printf("OK!\n"); +} \ No newline at end of file