- 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
16
build.bat
16
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
|
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