- 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:
parent
d79900449f
commit
98feda0cbb
11 changed files with 1154 additions and 18 deletions
|
@ -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
19
build.c
Normal 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
13
build_release.bat
Normal 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
60
main.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
10
main.cpp
10
main.cpp
|
@ -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
92
oogabooga/base.c
Normal 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
433
oogabooga/memory.c
Normal 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
29
oogabooga/oogabooga.c
Normal 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
174
oogabooga/os_impl_windows.c
Normal 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
83
oogabooga/os_interface.c
Normal 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
243
oogabooga/tests.c
Normal 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");
|
||||||
|
}
|
Reference in a new issue