- build.c which includes everything to be built and configures build with #defines

- Some compile options that make sense for build.bat, and a build_release.bat
- oogabooga:
	- Big buffer program_memory that always has same base address (goodbye serialisation problems)
		- Big buffer grows as needed, crash when out of memory
	- Very sexy platform abstraction & basic os call procedures
	- Heap allocator around the program memory.
    - Jaiyfication (context, context.allocator, push_allocator, temp allocator)
	- Some tests to make sure we ooga the right boogas
This commit is contained in:
Charlie 2024-06-28 03:28:23 +02:00
parent d79900449f
commit 98feda0cbb
11 changed files with 1154 additions and 18 deletions

View file

@ -4,6 +4,6 @@ mkdir build
pushd build pushd build
cl ../main.cpp /Zi cl ../build.c /Zi /Fecgame.exe /Od /DDEBUG /MD /DEBUG /std:c11
popd popd

19
build.c Normal file
View file

@ -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"

13
build_release.bat Normal file
View file

@ -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

60
main.c Normal file
View file

@ -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;
}

View file

@ -1,10 +0,0 @@
#include <stdio.h>
int main() {
int hello;
hello = 5;
printf("Hello, balls!\n");
return 0;
}

92
oogabooga/base.c Normal file
View file

@ -0,0 +1,92 @@
#include <stdint.h>
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(); }

433
oogabooga/memory.c Normal file
View file

@ -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;
}

29
oogabooga/oogabooga.c Normal file
View file

@ -0,0 +1,29 @@
#include <stdio.h> // #Cleanup just using this for printf
#include <stdlib.h>
#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();
}

174
oogabooga/os_impl_windows.c Normal file
View file

@ -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 <intrin.h>
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;
}

83
oogabooga/os_interface.c Normal file
View file

@ -0,0 +1,83 @@
#ifdef _WIN32
#include <Windows.h>
#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();

243
oogabooga/tests.c Normal file
View file

@ -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");
}