- Basic fixed-length string with replacement for C standard printing & fmt stuff

- Jai-like print procedures
	- %s formats 'string' and to format a char* you do %cs
	- It detects if %cs pointer is outside of program memory or stack and asserts
	- AND same for if a char* is passed to %s
	- Print directly writes to stdout without any allocations
	- Basic utlity procedures
	- Buncha string tests
- Beef up assert to display file & line as well
- Don't define memcpy procedures if compiler has intrinsics for them
- Init memory arena for allocations in initialization time before heap is ready (stack memory)
- os_compare_and_swap
- Spinlock "primitive" (wrapper around a bool using compare_and_swap)
- Switched to using spinlock instead of os mutex in heap for synchronization.
- is_pointer_valid() which checks if address is in program_memory or thread stack
This commit is contained in:
Charlie 2024-06-28 18:50:30 +02:00
parent 49ca7f3406
commit db73d0bd2e
11 changed files with 744 additions and 185 deletions

112
.gitignore vendored
View file

@ -1,55 +1,57 @@
*.code-workspace *.code-workspace
*.raddbgi *.raddbgi
**/worlds **/worlds
data data
uid uid
*.rdbg *.rdbg
*.exe *.exe
*.pdb *.pdb
.build/ .build/
*.sublime-workspace *.sublime-workspace
*.sublime-project *.sublime-project
*.vs *.vs
*.sln *.sln
*.vcxproj *.vcxproj
*.vcxproj.* *.vcxproj.*
*.log *.log
*.ini *.ini
*.VC.db *.VC.db
*.obj *.obj
*.ilk *.ilk
*.spall *.spall
release/ release/
output.txt output.txt
*.blend1 *.blend1
renderdoc.cap renderdoc.cap
*.abs *.abs
*.asf *.asf
env.jai env.jai
save.txt save.txt
settings.txt settings.txt
*_BRAIN *_BRAIN
*_PLAYTEST *_PLAYTEST
*_SYNC *_SYNC
*_TAG_LEGEND *_TAG_LEGEND
*_LOCAL_CHANGES *_LOCAL_CHANGES
*charlie.code-workspace *charlie.code-workspace
!proj/arcane/windows_glslc.exe !proj/arcane/windows_glslc.exe
!proj/arcane/linux_glslc !proj/arcane/linux_glslc
!proj/arcane/macos_glslc !proj/arcane/macos_glslc
*TRACES/ *TRACES/
fetch.bat fetch.bat
thing.bat thing.bat
*crash-* *crash-*
*.dmp *.dmp
*crash.txt *crash.txt
*crash-uid.txt *crash-uid.txt
*crash-version.txt *crash-version.txt
output2.txt output2.txt
test_doc.vkn test_doc.vkn
*.focus-config *.focus-config
*keybinds *keybinds
*.rdi *.rdi
.vscode

View file

@ -1,8 +1,9 @@
/// ///
// Build config stuff // Build config stuff
#define VERY_DEBUG 0 #define VERY_DEBUG 0
#define RUN_TESTS 1 #define RUN_TESTS 0
typedef struct Context_Extra { typedef struct Context_Extra {
int monkee; int monkee;

View file

@ -7,7 +7,7 @@ pushd build
mkdir release mkdir release
pushd release pushd release
cl ../../main.c /Zi /Fecgame.exe /Ox /DRELEASE /std:c11 cl ../../build.c /Zi /Fecgame.exe /Ox /DRELEASE /MD /std:c11
popd popd
popd popd

6
main.c
View file

@ -1,11 +1,11 @@
int oogabooga_main(int argc, char **argv) { int oogabooga_main(int argc, char **argv) {
print(cstr("This is our print! %i\n"), 5);
// alloc calls to context.allocator.proc which by default is set to the // alloc calls to context.allocator.proc which by default is set to the
// heap allocator in memory.c // heap allocator in memory.c
int *a = cast(int*)alloc(sizeof(int)); int *a = (int*)alloc(sizeof(int));
dealloc(a); dealloc(a);
// We can do an old school memory dump to save our game with the global variables // We can do an old school memory dump to save our game with the global variables
@ -37,8 +37,6 @@ int oogabooga_main(int argc, char **argv) {
context.extra.monkee = 69; context.extra.monkee = 69;
int hello; int hello;
hello = 5; hello = 5;

View file

@ -21,43 +21,59 @@ typedef u8 bool;
#define thread_local _Thread_local #define thread_local _Thread_local
#define ifnt(x) if (!(x))
#ifdef _MSC_VER #ifdef _MSC_VER
#define os_debug_break __debugbreak inline void os_break() {
__debugbreak();
int *a = 0;
*a = 5;
}
#else #else
#error "Only msvc compiler supported at the moment"; #error "Only msvc compiler supported at the moment";
#endif #endif
#define assert(cond, ...) if (!(cond)) { printf("Assertion failed for condition: " #cond ". Message: " __VA_ARGS__); os_debug_break(); }
void printf(const char* fmt, ...);
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define assert_line(line, cond, ...) if (!(cond)) { printf("Assertion failed in file " __FILE__ " on line " STR(line) "\nFailed Condition: " #cond ". Message: " __VA_ARGS__); os_break(); }
#define assert(cond, ...) assert_line(__LINE__, cond, __VA_ARGS__);
#define cast(t) (t) #define cast(t) (t)
/// ///
// inline // Compiler specific stuff
// (Credit to chatgpt, so it might not be 100% correct)
#ifdef _MSC_VER #ifdef _MSC_VER
// Microsoft Visual C++ // Microsoft Visual C++
#define inline __forceinline #define inline __forceinline
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
#elif defined(__GNUC__) || defined(__GNUG__) #elif defined(__GNUC__) || defined(__GNUG__)
// GNU GCC/G++ // GNU GCC/G++
#define inline __attribute__((always_inline)) inline #define inline __attribute__((always_inline)) inline
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
#elif defined(__clang__) #elif defined(__clang__)
// Clang/LLVM // Clang/LLVM
#define inline __attribute__((always_inline)) inline #define inline __attribute__((always_inline)) inline
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
#elif defined(__INTEL_COMPILER) || defined(__ICC) #elif defined(__INTEL_COMPILER) || defined(__ICC)
// Intel C++ Compiler // Intel C++ Compiler
#define inline __forceinline #define inline __forceinline
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
#elif defined(__BORLANDC__) #elif defined(__BORLANDC__)
// Borland C++ // Borland C++
#define inline __inline #define inline __inline
#elif defined(__MINGW32__) || defined(__MINGW64__) #elif defined(__MINGW32__) || defined(__MINGW64__)
// MinGW (Minimalist GNU for Windows) // MinGW (Minimalist GNU for Windows)
#define inline __attribute__((always_inline)) inline #define inline __attribute__((always_inline)) inline
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) #elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
// Oracle Solaris Studio // Oracle Solaris Studio
#define inline inline __attribute__((always_inline)) #define inline inline __attribute__((always_inline))
#elif defined(__IBMC__) || defined(__IBMCPP__) #elif defined(__IBMC__) || defined(__IBMCPP__)
// IBM XL C/C++ Compiler // IBM XL C/C++ Compiler
#define inline __attribute__((always_inline)) inline #define inline __attribute__((always_inline)) inline
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
#elif defined(__PGI) #elif defined(__PGI)
// Portland Group Compiler // Portland Group Compiler
#define inline inline __attribute__((always_inline)) #define inline inline __attribute__((always_inline))
@ -120,3 +136,5 @@ void push_allocator(Allocator a) {
push_context(c); push_context(c);
} }
void pop_allocator() { pop_context(); } void pop_allocator() { pop_context(); }

View file

@ -1,5 +1,37 @@
void* program_memory = 0;
u64 program_memory_size = 0;
#ifndef INIT_MEMORY_SIZE
#define INIT_MEMORY_SIZE (1024*50)
#endif
// We may need to allocate stuff in initialization time before the heap is ready.
// That's what this is for.
u8 init_memory_arena[INIT_MEMORY_SIZE];
u8 *init_memory_head = init_memory_arena;
void* initialization_allocator_proc(u64 size, void *p, Allocator_Message message) {
switch (message) {
case ALLOCATOR_ALLOCATE: {
p = init_memory_head;
init_memory_head += size;
if (init_memory_head >= ((u8*)init_memory_arena+INIT_MEMORY_SIZE)) {
os_write_string_to_stdout(cstr("Out of initialization memory! Please provide more by increasing INIT_MEMORY_SIZE"));
os_break();
}
return p;
break;
}
case ALLOCATOR_DEALLOCATE: {
return 0;
}
}
return 0;
}
/// ///
/// ///
// Basic general heap allocator, free list // Basic general heap allocator, free list
@ -34,7 +66,7 @@ typedef struct {
Heap_Block *heap_head; Heap_Block *heap_head;
bool heap_initted = false; bool heap_initted = false;
Mutex_Handle heap_mutex; // This is terrible but I don't care for now Spinlock *heap_lock; // This is terrible but I don't care for now
u64 get_heap_block_size_excluding_metadata(Heap_Block *block) { u64 get_heap_block_size_excluding_metadata(Heap_Block *block) {
@ -47,6 +79,17 @@ u64 get_heap_block_size_including_metadata(Heap_Block *block) {
bool is_pointer_in_program_memory(void *p) { bool is_pointer_in_program_memory(void *p) {
return (u8*)p >= (u8*)program_memory && (u8*)p<((u8*)program_memory+program_memory_size); return (u8*)p >= (u8*)program_memory && (u8*)p<((u8*)program_memory+program_memory_size);
} }
bool is_pointer_in_stack(void* p) {
void* stack_base = os_get_stack_base();
void* stack_limit = os_get_stack_limit();
return (uintptr_t)p >= (uintptr_t)stack_limit && (uintptr_t)p < (uintptr_t)stack_base;
}
bool is_pointer_in_static_memory(void* p) {
return (uintptr_t)p >= (uintptr_t)os.static_memory_start && (uintptr_t)p < (uintptr_t)os.static_memory_end;
}
bool is_pointer_valid(void *p) {
return is_pointer_in_program_memory(p) || is_pointer_in_stack(p) || is_pointer_in_static_memory(p);
}
// Meant for debug // Meant for debug
void santiy_check_free_node_tree(Heap_Block *block) { void santiy_check_free_node_tree(Heap_Block *block) {
@ -137,7 +180,6 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) {
u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1; u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1;
u64 new_program_size = (cast(u64)(minimum_size * 1.5)); u64 new_program_size = (cast(u64)(minimum_size * 1.5));
assert(new_program_size >= minimum_size, "Bröd"); assert(new_program_size >= minimum_size, "Bröd");
printf("Growing program memory to %llu bytes\n", new_program_size);
const u64 ATTEMPTS = 1000; const u64 ATTEMPTS = 1000;
for (u64 i = 0; i <= ATTEMPTS; i++) { 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. if (program_memory_size >= new_program_size) break; // Another thread might have resized already, causing it to fail here.
@ -158,9 +200,10 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) {
} }
void heap_init() { void heap_init() {
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE); if (heap_initted) return;
heap_mutex = os_make_mutex();
heap_initted = true; heap_initted = true;
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE);
heap_lock = os_make_spinlock();
} }
void *heap_alloc(u64 size) { void *heap_alloc(u64 size) {
@ -168,7 +211,7 @@ void *heap_alloc(u64 size) {
if (!heap_initted) heap_init(); if (!heap_initted) heap_init();
// #Sync #Speed oof // #Sync #Speed oof
os_lock_mutex(heap_mutex); os_spinlock_lock(heap_lock);
size += sizeof(Heap_Allocation_Metadata); size += sizeof(Heap_Allocation_Metadata);
@ -257,7 +300,7 @@ void *heap_alloc(u64 size) {
#endif #endif
// #Sync #Speed oof // #Sync #Speed oof
os_unlock_mutex(heap_mutex); os_spinlock_unlock(heap_lock);
return ((u8*)meta)+sizeof(Heap_Allocation_Metadata); return ((u8*)meta)+sizeof(Heap_Allocation_Metadata);
} }
@ -266,15 +309,15 @@ void heap_dealloc(void *p) {
if (!heap_initted) heap_init(); if (!heap_initted) heap_init();
os_lock_mutex(heap_mutex); os_spinlock_lock(heap_lock);
assert(is_pointer_in_program_memory(p), "Garbage pointer; out of program memory bounds!"); assert(is_pointer_in_program_memory(p), "Garbage pointer; out of program memory bounds!");
p = (u8*)p-sizeof(Heap_Allocation_Metadata); p = (u8*)p-sizeof(Heap_Allocation_Metadata);
Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)(p); Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)(p);
// If > 256GB then prolly not legit lol // If > 256GB then prolly not legit lol
assert(meta->size < 1024ULL*1024ULL*1024ULL*256ULL, "Garbage pointer passed to heap_dealloc !!!"); assert(meta->size < 1024ULL*1024ULL*1024ULL*256ULL, "Garbage pointer passed to heap_dealloc !!! Or could be corrupted memory.");
assert(is_pointer_in_program_memory(meta->block), "Garbage pointer passed to heap_dealloc !!!"); assert(is_pointer_in_program_memory(meta->block), "Garbage pointer passed to heap_dealloc !!! Or could be corrupted memory.");
// Yoink meta data before we start overwriting it // Yoink meta data before we start overwriting it
Heap_Block *block = meta->block; Heap_Block *block = meta->block;
@ -327,38 +370,8 @@ void heap_dealloc(void *p) {
#endif #endif
// #Sync #Speed oof // #Sync #Speed oof
os_unlock_mutex(heap_mutex); os_spinlock_unlock(heap_lock);
} }
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);
}
/// ///
/// ///
@ -416,7 +429,7 @@ void* talloc(u64 size) {
if ((u8*)temporary_storage_pointer >= (u8*)temporary_storage+TEMPORARY_STORAGE_SIZE) { if ((u8*)temporary_storage_pointer >= (u8*)temporary_storage+TEMPORARY_STORAGE_SIZE) {
if (!has_warned_temporary_storage_overflow) { if (!has_warned_temporary_storage_overflow) {
printf("WARNING: temporary storage was overflown, we wrap around at the start.\n"); os_write_string_to_stdout(cstr("WARNING: temporary storage was overflown, we wrap around at the start.\n"));
} }
temporary_storage_pointer = temporary_storage; temporary_storage_pointer = temporary_storage;
return talloc(size);; return talloc(size);;
@ -437,4 +450,4 @@ void reset_temporary_storage() {
void push_temp_allocator() { void push_temp_allocator() {
if (!temporary_storage_initted) temporary_storage_init(); if (!temporary_storage_initted) temporary_storage_init();
push_allocator(temp); push_allocator(temp);
} }

View file

@ -1,4 +1,19 @@
#ifdef _WIN32
#include <Windows.h>
#define OS_WINDOWS
#elif defined(__linux__)
// Include whatever #Incomplete #Portability
#define OS_LINUX
#error "Linux is not supported yet";
#elif defined(__APPLE__) && defined(__MACH__)
// Include whatever #Incomplete #Portability
#define OS_MAC
#error "Mac is not supported yet";
#else
#error "Current OS not supported!";
#endif
#include "base.c" #include "base.c"
#include "string.c" #include "string.c"
@ -9,6 +24,7 @@
#ifdef OS_WINDOWS #ifdef OS_WINDOWS
#include "os_impl_windows.c" #include "os_impl_windows.c"
#elif defined (OS_LINUX) #elif defined (OS_LINUX)
@ -33,9 +49,13 @@ void oogabooga_init(u64 program_memory_size) {
#define RUN_TESTS 0 #define RUN_TESTS 0
#endif #endif
int oogabooga_main(int argc, char **argv);
int main(int argc, char **argv) { int main(int argc, char **argv) {
printf("Ooga booga program started\n"); context.allocator.proc = initialization_allocator_proc;
oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE); oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE);
printf("Ooga booga program started\n");
// This can be disabled in build.c // This can be disabled in build.c
#if RUN_TESTS #if RUN_TESTS

View file

@ -25,6 +25,23 @@ void os_init(u64 program_memory_size) {
GetSystemInfo(&si); GetSystemInfo(&si);
os.granularity = cast(u64)si.dwAllocationGranularity; os.granularity = cast(u64)si.dwAllocationGranularity;
os.page_size = cast(u64)si.dwPageSize; os.page_size = cast(u64)si.dwPageSize;
os.static_memory_start = 0;
os.static_memory_end = 0;
MEMORY_BASIC_INFORMATION mbi;
unsigned char* addr = 0;
while (VirtualQuery(addr, &mbi, sizeof(mbi))) {
if (mbi.Type == MEM_IMAGE) {
if (os.static_memory_start == NULL) {
os.static_memory_start = mbi.BaseAddress;
}
os.static_memory_end = (unsigned char*)mbi.BaseAddress + mbi.RegionSize;
}
addr += mbi.RegionSize;
}
program_memory_mutex = os_make_mutex(); program_memory_mutex = os_make_mutex();
@ -35,16 +52,17 @@ void os_init(u64 program_memory_size) {
heap_allocator.proc = heap_allocator_proc; heap_allocator.proc = heap_allocator_proc;
heap_allocator.data = 0; heap_allocator.data = 0;
heap_init();
context.allocator = heap_allocator; context.allocator = heap_allocator;
os.crt = os_load_dynamic_library(const_string("msvcrt.dll")); os.crt = os_load_dynamic_library(const_string("msvcrt.dll"));
assert(os.crt != 0, "Could not load win32 crt library. Might be compiled with non-msvc? #Incomplete #Portability"); assert(os.crt != 0, "Could not load win32 crt library. Might be compiled with non-msvc? #Incomplete #Portability");
os.crt_vprintf = (Crt_Vprintf_Proc)os_dynamic_library_load_symbol(os.crt, const_string("vprintf"));
assert(os.crt_vprintf, "Missing vprintf in crt");
os.crt_printf = (Crt_Printf_Proc)os_dynamic_library_load_symbol(os.crt, const_string("printf"));
assert(os.crt_printf, "Missing printf in crt");
os.crt_vsnprintf = (Crt_Vsnprintf_Proc)os_dynamic_library_load_symbol(os.crt, const_string("vsnprintf")); os.crt_vsnprintf = (Crt_Vsnprintf_Proc)os_dynamic_library_load_symbol(os.crt, const_string("vsnprintf"));
assert(os.crt_vsnprintf, "Missing vsnprintf in crt"); assert(os.crt_vsnprintf, "Missing vsnprintf in crt");
os.crt_vprintf = (Crt_Vprintf_Proc)os_dynamic_library_load_symbol(os.crt, const_string("vprintf"));
assert(os.crt_vprintf, "Missing vprintf in crt");
os.crt_vsprintf = (Crt_Vsprintf_Proc)os_dynamic_library_load_symbol(os.crt, const_string("vsprintf"));
assert(os.crt_vsprintf, "Missing vsprintf in crt");
os.crt_memcpy = (Crt_Memcpy_Proc)os_dynamic_library_load_symbol(os.crt, const_string("memcpy")); os.crt_memcpy = (Crt_Memcpy_Proc)os_dynamic_library_load_symbol(os.crt, const_string("memcpy"));
assert(os.crt_memcpy, "Missing memcpy in crt"); assert(os.crt_memcpy, "Missing memcpy in crt");
os.crt_memcmp = (Crt_Memcmp_Proc)os_dynamic_library_load_symbol(os.crt, const_string("memcmp")); os.crt_memcmp = (Crt_Memcmp_Proc)os_dynamic_library_load_symbol(os.crt, const_string("memcmp"));
@ -204,4 +222,59 @@ void os_write_string_to_stdout(string s) {
if (win32_stdout == INVALID_HANDLE_VALUE) return; if (win32_stdout == INVALID_HANDLE_VALUE) return;
WriteFile(win32_stdout, s.data, s.count, 0, NULL); WriteFile(win32_stdout, s.data, s.count, 0, NULL);
}
void* os_get_stack_base() {
NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
return tib->StackBase;
}
void* os_get_stack_limit() {
NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
return tib->StackLimit;
}
Spinlock *os_make_spinlock() {
Spinlock *l = cast(Spinlock*)alloc(sizeof(Spinlock));
l->locked = false;
return l;
}
void os_spinlock_lock(Spinlock *l) {
while (true) {
bool expected = false;
if (os_compare_and_swap_bool(&l->locked, true, expected)) {
return;
}
while (l->locked) {
// spinny boi
}
}
}
void os_spinlock_unlock(Spinlock *l) {
bool expected = true;
bool success = os_compare_and_swap_bool(&l->locked, false, expected);
assert(success, "This thread should have acquired the spinlock but compare_and_swap failed");
}
bool os_compare_and_swap_8(u8 *a, u8 b, u8 old) {
// #Portability not sure how portable this is.
return _InterlockedCompareExchange8((volatile CHAR*)a, (CHAR)b, (CHAR)old) == (CHAR)old;
}
bool os_compare_and_swap_16(u16 *a, u16 b, u16 old) {
return InterlockedCompareExchange16((volatile SHORT*)a, (SHORT)b, (SHORT)old) == (SHORT)old;
}
bool os_compare_and_swap_32(u32 *a, u32 b, u32 old) {
return InterlockedCompareExchange((volatile LONG*)a, (LONG)b, (LONG)old) == (LONG)old;
}
bool os_compare_and_swap_64(u64 *a, u64 b, u64 old) {
return InterlockedCompareExchange64((volatile LONG64*)a, (LONG64)b, (LONG64)old) == (LONG64)old;
}
bool os_compare_and_swap_bool(bool *a, bool b, bool old) {
return os_compare_and_swap_8(cast(u8*)a, cast(u8)b, cast(u8)old);
} }

View file

@ -1,24 +1,17 @@
#ifdef _WIN32 #ifdef _WIN32
#include <Windows.h>
#define OS_WINDOWS
typedef HANDLE Mutex_Handle; typedef HANDLE Mutex_Handle;
typedef HANDLE Thread_Handle; typedef HANDLE Thread_Handle;
typedef HMODULE Dynamic_Library_Handle; typedef HMODULE Dynamic_Library_Handle;
#elif defined(__linux__) #elif defined(__linux__)
// Include whatever #Incomplete #Portability
#define OS_LINUX
typedef SOMETHING Mutex_Handle; typedef SOMETHING Mutex_Handle;
typedef SOMETHING Thread_Handle; typedef SOMETHING Thread_Handle;
typedef SOMETHING Dynamic_Library_Handle; typedef SOMETHING Dynamic_Library_Handle;
#error "Linux is not supported yet"; #error "Linux is not supported yet";
#elif defined(__APPLE__) && defined(__MACH__) #elif defined(__APPLE__) && defined(__MACH__)
// Include whatever #Incomplete #Portability
#define OS_MAC
typedef SOMETHING Mutex_Handle; typedef SOMETHING Mutex_Handle;
typedef SOMETHING Thread_Handle; typedef SOMETHING Thread_Handle;
typedef SOMETHING Dynamic_Library_Handle; typedef SOMETHING Dynamic_Library_Handle;
@ -34,16 +27,12 @@
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define oogabooga_va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v)) typedef void* (__cdecl *Crt_Memcpy_Proc) (void*, const void*, size_t);
#define oogabooga_va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) typedef int (__cdecl *Crt_Memcmp_Proc) (const void*, const void*, size_t);
#define oogabooga_va_end(ap) (ap = (va_list)0) typedef void* (__cdecl *Crt_Memset_Proc) (void*, int, size_t);
typedef void* (__cdecl *Crt_Memcpy_Proc) (void*, const void*, size_t);
typedef int (__cdecl *Crt_Memcmp_Proc) (const void*, const void*, size_t);
typedef void* (__cdecl *Crt_Memset_Proc) (void*, int, size_t);
typedef int (__cdecl *Crt_Printf_Proc) (const char*, ...);
typedef int (__cdecl *Crt_Vprintf_Proc) (const char*, va_list); typedef int (__cdecl *Crt_Vprintf_Proc) (const char*, va_list);
typedef int (__cdecl *Crt_Vsnprintf_Proc) (char*, size_t, const char*, va_list); typedef int (__cdecl *Crt_Vsnprintf_Proc) (char*, size_t, const char*, va_list);
typedef int (__cdecl *Crt_Vsprintf_Proc) (char*, const char*, va_list);
typedef struct Os_Info { typedef struct Os_Info {
u64 page_size; u64 page_size;
@ -54,66 +43,61 @@ typedef struct Os_Info {
Crt_Memcpy_Proc crt_memcpy; Crt_Memcpy_Proc crt_memcpy;
Crt_Memcmp_Proc crt_memcmp; Crt_Memcmp_Proc crt_memcmp;
Crt_Memset_Proc crt_memset; Crt_Memset_Proc crt_memset;
Crt_Printf_Proc crt_printf; // #Cleanup remove after we have our own print Crt_Vprintf_Proc crt_vprintf;
Crt_Vprintf_Proc crt_vprintf; // #Cleanup remove after we have our own print
Crt_Vsnprintf_Proc crt_vsnprintf; Crt_Vsnprintf_Proc crt_vsnprintf;
Crt_Vsprintf_Proc crt_vsprintf;
void *static_memory_start, *static_memory_end;
} Os_Info; } Os_Info;
Os_Info os; Os_Info os;
inline void* naive_memcpy(void* dest, const void* source, size_t size) { inline int crt_vprintf(const char* fmt, va_list args) {
for (u64 i = 0; i < (u64)size; i++) ((u8*)dest)[i] = ((u8*)source)[i]; return os.crt_vprintf(fmt, args);
return dest;
} }
inline void* memcpy(void* dest, const void* source, size_t size) {
if (!os.crt_memcpy) return naive_memcpy(dest, source, size);
return os.crt_memcpy(dest, source, size);
}
inline int naive_memcmp(const void* a, const void* b, size_t amount) {
// I don't understand the return value of memcmp but I also dont care
for (u64 i = 0; i < (u64)amount; i++) {
if (((u8*)a)[i] != ((u8*)b)[i]) return -1;
}
return 0;
}
inline int memcmp(const void* a, const void* b, size_t amount) {
if (!os.crt_memcmp) return naive_memcmp(a, b, amount);
return os.crt_memcmp(a, b, amount);
}
inline void* naive_memset(void* dest, int value, size_t amount) {
for (u64 i = 0; i < (u64)amount; i++) ((u8*)dest)[i] = (u8)value;
return dest;
}
inline void* memset(void* dest, int value, size_t amount) {
if (!os.crt_memset) return naive_memset(dest, value, amount);
return os.crt_memset(dest, value, amount);
}
inline int printf(const char* fmt, ...) {
char fast_buffer[8196];
char *large_buffer = 0;
va_list args; #if !defined(COMPILER_HAS_MEMCPY_INTRINSICS) || defined(DEBUG)
oogabooga_va_start(args, fmt); inline void* naive_memcpy(void* dest, const void* source, size_t size) {
int r = vprintf(fmt, args); for (u64 i = 0; i < (u64)size; i++) ((u8*)dest)[i] = ((u8*)source)[i];
oogabooga_va_end(args); return dest;
}
return r; inline void* memcpy(void* dest, const void* source, size_t size) {
} if (!os.crt_memcpy) return naive_memcpy(dest, source, size);
void os_write_string_to_stdout(string s); return os.crt_memcpy(dest, source, size);
inline int vprintf(const char* fmt, va_list args) { }
if (os.crt_vprintf) return os.crt_vprintf(fmt, args); inline int naive_memcmp(const void* a, const void* b, size_t amount) {
else { // I don't understand the return value of memcmp but I also dont care
os_write_string_to_stdout(cstr(fmt)); for (u64 i = 0; i < (u64)amount; i++) {
os_write_string_to_stdout(cstr(" <crt_vprintf is not loaded so we cannot vprintf.>")); if (((u8*)a)[i] != ((u8*)b)[i]) return -1;
}
return 0; return 0;
} }
} inline int memcmp(const void* a, const void* b, size_t amount) {
if (!os.crt_memcmp) return naive_memcmp(a, b, amount);
return os.crt_memcmp(a, b, amount);
}
inline void* naive_memset(void* dest, int value, size_t amount) {
for (u64 i = 0; i < (u64)amount; i++) ((u8*)dest)[i] = (u8)value;
return dest;
}
inline void* memset(void* dest, int value, size_t amount) {
if (!os.crt_memset) return naive_memset(dest, value, amount);
return os.crt_memset(dest, value, amount);
}
#endif
inline int vsnprintf(char* buffer, size_t n, const char* fmt, va_list args) { inline int vsnprintf(char* buffer, size_t n, const char* fmt, va_list args) {
os.crt_vsnprintf(buffer, n, fmt, args); return os.crt_vsnprintf(buffer, n, fmt, args);
}
inline int crt_sprintf(char *str, const char *format, ...) {
va_list args;
va_start(args, format);
int r = os.crt_vsprintf(str, format, args);
va_end(args);
return r;
} }
void* program_memory = 0;
u64 program_memory_size = 0;
Mutex_Handle program_memory_mutex = 0; Mutex_Handle program_memory_mutex = 0;
bool os_grow_program_memory(size_t new_size); bool os_grow_program_memory(size_t new_size);
@ -152,6 +136,24 @@ void os_destroy_mutex(Mutex_Handle m);
void os_lock_mutex(Mutex_Handle m); void os_lock_mutex(Mutex_Handle m);
void os_unlock_mutex(Mutex_Handle m); void os_unlock_mutex(Mutex_Handle m);
///
// Spinlock primitive
typedef struct Spinlock {
bool locked;
} Spinlock;
Spinlock *os_make_spinlock();
void os_spinlock_lock(Spinlock* l);
void os_spinlock_unlock(Spinlock* l);
///
// Sync utilities
bool os_compare_and_swap_8 (u8 *a, u8 b, u8 old);
bool os_compare_and_swap_16 (u16 *a, u16 b, u16 old);
bool os_compare_and_swap_32 (u32 *a, u32 b, u32 old);
bool os_compare_and_swap_64 (u64 *a, u64 b, u64 old);
bool os_compare_and_swap_bool(bool *a, bool b, bool old);
/// ///
/// ///
@ -178,4 +180,67 @@ void os_unload_dynamic_library(Dynamic_Library_Handle l);
// IO // IO
/// ///
void os_write_string_to_stdout(string s); void os_write_string_to_stdout(string s);
// context.allocator (alloc & dealloc)
void print_va_list(const string fmt, va_list args) {
string s = sprint_va_list(fmt, args);
os_write_string_to_stdout(s);
dealloc(s.data);
}
// print for 'string' and printf for 'char*'
#define PRINT_BUFFER_SIZE 4096
// Avoids all and any allocations but overhead in speed and memory.
// Need this for standard printing so we don't get infinite recursions.
// (for example something in memory might fail assert and it needs to print that)
void print_va_list_buffered(const string fmt, va_list args) {
string current = fmt;
char buffer[PRINT_BUFFER_SIZE];
while (true) {
u64 size = min(current.count, PRINT_BUFFER_SIZE-1);
if (current.count <= 0) break;
memcpy(buffer, current.data, size);
char fmt_cstring[PRINT_BUFFER_SIZE+1];
memcpy(fmt_cstring, current.data, size);
fmt_cstring[size] = 0;
string s = sprint_null_terminated_string_va_list_to_buffer(fmt_cstring, args, buffer, PRINT_BUFFER_SIZE);
os_write_string_to_stdout(s);
current.count -= size;
current.data += size;
}
}
// context.allocator (alloc & dealloc)
void print(const string fmt, ...) {
va_list args;
va_start(args, fmt);
print_va_list_buffered(fmt, args);
va_end(args);
}
// context.allocator (alloc & dealloc)
void printf(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
string s;
s.data = cast(u8*)fmt;
s.count = strlen(fmt);
print_va_list_buffered(s, args);
va_end(args);
}
///
///
// Memory
///
void* os_get_stack_base();
void* os_get_stack_limit();

View file

@ -1,15 +1,57 @@
/*
Usage:
// We need to use macro const_string to convert literal to string
string fmt = const_string("Pointer address: 0x%x");
print(fmt, cast(u64)&a); // Print to stdout
// Format a string and allocate with context.allocator
string a = sprint("Hello, %cs!\n", "balls"); // %cs for char*
string balls = const_string("balls");
// tprint for temporary allocation
string b = tprint(const_string("Hello, %s!\n"), balls); // %s for string
// Allocate a new string of length 12 (with context allocator)
string c = alloc_string(12);
dealloc_string(c);
// We can use raw char* for format with printf/sprintf/tprintf
printf("Hello, %!\n", balls);
// concatenation
string concatenated = string_concat(a, b);
// Use temporary memory to make a null-terminated copy of fixed-length string
char* cstring = temp_convert_to_null_terminated_string(balls);
// To convert a cstring to string (using same memory)
string s;
s.data = (u8*)cstring;
s.count = strlen(cstring);
// String matching
bool match = strings_match(a, b);
// View into "Hello, balls!\n" from index 7 with a count of 5; "balls"
string balls2 = string_view(a, 7, 5);
*/
void * memcpy (void *,const void *,size_t); void * memcpy (void *,const void *,size_t);
void* talloc(u64);
typedef struct string { typedef struct string {
u8 *data;
u64 count; u64 count;
u8 *data;
} string; } string;
void push_temp_allocator(); void push_temp_allocator();
#define cstr const_string #define cstr const_string
#define const_string(s) (string){ (u8*)s, length_of_null_terminated_string(s) } #define const_string(s) (string){ length_of_null_terminated_string(s), (u8*)s }
inline u64 length_of_null_terminated_string(const char* cstring) { inline u64 length_of_null_terminated_string(const char* cstring) {
u64 len = 0; u64 len = 0;
@ -20,8 +62,18 @@ inline u64 length_of_null_terminated_string(const char* cstring) {
return len; return len;
} }
string alloc_string(u64 count) {
string s;
s.count = count;
s.data = cast(u8*)alloc(count);
return s;
}
void dealloc_string(string s) {
dealloc(s.data);
}
// context.allocator ! // context.allocator !
string string_concat(string left, string right) { string string_concat(const string left, const string right) {
string result; string result;
result.count = left.count + right.count; result.count = left.count + right.count;
result.data = cast(u8*)alloc(result.count); result.data = cast(u8*)alloc(result.count);
@ -30,16 +82,204 @@ string string_concat(string left, string right) {
return result; return result;
} }
// context.allocator ! // context.allocator !
char *convert_to_null_terminated_string(string s) { char *convert_to_null_terminated_string(const string s) {
char *cstring = cast(char*)alloc(s.count+1); char *cstring = cast(char*)alloc(s.count+1);
memcpy(cstring, s.data, s.count); memcpy(cstring, s.data, s.count);
cstring[s.count] = 0; cstring[s.count] = 0;
return cstring; return cstring;
} }
char *temp_convert_to_null_terminated_string(string s) { char *temp_convert_to_null_terminated_string(const string s) {
push_temp_allocator(); push_temp_allocator();
char *c = convert_to_null_terminated_string(s); char *c = convert_to_null_terminated_string(s);
pop_allocator(); pop_allocator();
return c; return c;
} }
inline int crt_sprintf(char *str, const char *format, ...);
int vsnprintf(char* buffer, size_t n, const char* fmt, va_list args);
bool is_pointer_valid(void *p);
u64 format_string_to_buffer(char* buffer, u64 count, const char* fmt, va_list args) {
if (!buffer) count = UINT64_MAX;
const char* p = fmt;
char* bufp = buffer;
while (*p != '\0' && (bufp - buffer) < count - 1) {
if (*p == '%') {
p += 1;
if (*p == 's') {
// We replace %s formatting with our fixed length string
p += 1;
string s = va_arg(args, string);
assert(s.count < (1024ULL*1024ULL*1024ULL*256ULL), "Ypu passed something else than a fixed-length 'string' to %%s. Maybe you passed a char* and should do %%cs instead?");
for (u64 i = 0; i < s.count && (bufp - buffer) < count - 1; i++) {
if (buffer) *bufp = s.data[i];
bufp += 1;
}
} else if (*p == 'c' && *(p+1) == 's') {
// We extend the standard formatting and add %cs so we can format c strings if we need to
p += 2;
char* s = va_arg(args, char*);
assert(is_pointer_valid(s), "You passed an invalid pointer to %%cs");
u64 len = 0;
while (*s != '\0' && (bufp - buffer) < count - 1) {
assert(is_pointer_valid(s) && len < (1024ULL*1024ULL*1024ULL*1ULL), "The argument passed to %%cs is either way too big, missing null-termination or simply not a char*.");
if (buffer) {
*bufp = *s;
}
s += 1;
bufp += 1;
len += 1;
assert(is_pointer_valid(s) && len < (1024ULL*1024ULL*1024ULL*1ULL), "The argument passed to %%cs is either way too big, missing null-termination or simply not a char*.");
}
} else {
// Fallback to standard vsnprintf
char temp_buffer[512];
char format_specifier[64];
int specifier_len = 0;
format_specifier[specifier_len++] = '%';
while (*p != '\0' && strchr("diuoxXfFeEgGaAcCpn%", *p) == NULL) {
format_specifier[specifier_len++] = *p++;
}
if (*p != '\0') {
format_specifier[specifier_len++] = *p++;
}
format_specifier[specifier_len] = '\0';
int temp_len = vsnprintf(temp_buffer, sizeof(temp_buffer), format_specifier, args);
switch (format_specifier[specifier_len - 1]) {
case 'd': case 'i': va_arg(args, int); break;
case 'u': case 'x': case 'X': case 'o': va_arg(args, unsigned int); break;
case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': va_arg(args, double); break;
case 'c': va_arg(args, int); break;
case 's': va_arg(args, char*); break;
case 'p': va_arg(args, void*); break;
case 'n': va_arg(args, int*); break;
default: break;
}
if (temp_len < 0) {
return -1; // Error in formatting
}
for (int i = 0; i < temp_len && (bufp - buffer) < count - 1; i++) {
if (buffer) *bufp = temp_buffer[i];
bufp += 1;
}
}
} else {
if (buffer) {
*bufp = *p;
}
bufp += 1;
p += 1;
}
}
if (buffer) *bufp = '\0';
return bufp - buffer;
}
string sprint_null_terminated_string_va_list_to_buffer(const char *fmt, va_list args, void* buffer, u64 count) {
u64 formatted_length = format_string_to_buffer((char*)buffer, count, fmt, args);
string result;
result.data = (u8*)buffer;
if (formatted_length >= 0 && formatted_length < count) {
result.count = formatted_length;
} else {
result.count = count - 1;
}
return result;
}
string sprint_va_list_to_buffer(const string fmt, va_list args, void* buffer, u64 count) {
char* fmt_cstring = temp_convert_to_null_terminated_string(fmt);
return sprint_null_terminated_string_va_list_to_buffer(fmt_cstring, args, buffer, count);
}
// context.allocator
string sprint_va_list(const string fmt, va_list args) {
char* fmt_cstring = temp_convert_to_null_terminated_string(fmt);
u64 count = format_string_to_buffer(NULL, 0, fmt_cstring, args) + 1;
char* buffer = NULL;
buffer = (char*)alloc(count);
return sprint_null_terminated_string_va_list_to_buffer(fmt_cstring, args, buffer, count);
}
// context.allocator
string sprint(const string fmt, ...) {
va_list args = 0;
va_start(args, fmt);
string s = sprint_va_list(fmt, args);
va_end(args);
return s;
}
// temp allocator
string tprint(const string fmt, ...) {
va_list args = 0;
va_start(args, fmt);
push_temp_allocator();
string s = sprint_va_list(fmt, args);
pop_allocator();
va_end(args);
return s;
}
// context.allocator
string sprintf(const char *fmt, ...) {
string sfmt;
sfmt.data = cast(u8*)fmt;
sfmt.count = strlen(fmt);
va_list args;
va_start(args, fmt);
string s = sprint_va_list(sfmt, args);
va_end(args);
return s;
}
// temp allocator
string tprintf(const char *fmt, ...) {
string sfmt;
sfmt.data = cast(u8*)fmt;
sfmt.count = strlen(fmt);
va_list args;
va_start(args, fmt);
push_temp_allocator();
string s = sprint_va_list(sfmt, args);
pop_allocator();
va_end(args);
return s;
}
bool strings_match(string a, string b) {
if (a.count != b.count) return false;
// Count match, pointer match: they are the same
if (a.data == b.data) return true;
return memcmp(a.data, b.data, a.count) == 0;
}
string string_view(string s, u64 start_index, u64 count) {
assert(start_index < s.count, "array_view start_index % out of range for string count %", start_index, s.count);
assert(count > 0, "array_view count must be more than 0");
assert(start_index + count <= s.count, "array_view start_index + count is out of range");
string result;
result.data = s.data+start_index;
result.count = count;
return result;
}
// Defined in os interface
void print(string s, ...);

View file

@ -1,4 +1,33 @@
void log_heap() {
os_spinlock_lock(heap_lock);
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_spinlock_unlock(heap_lock);
}
void test_allocator(bool do_log_heap) { void test_allocator(bool do_log_heap) {
// Basic allocation and free // Basic allocation and free
@ -70,6 +99,8 @@ void test_allocator(bool do_log_heap) {
} }
reset_temporary_storage();
push_allocator(temp); push_allocator(temp);
int* foo = (int*)alloc(72); int* foo = (int*)alloc(72);
@ -222,10 +253,108 @@ void test_allocator_threaded(Thread *t) {
} }
void test_strings() { void test_strings() {
string s = (string){ (u8*)"Ooga booga", length_of_null_terminated_string("Ooga booga") }; // Test length_of_null_terminated_string
string a = const_string("Ooga booga"); assert(length_of_null_terminated_string("Test") == 4, "Failed: length_of_null_terminated_string");
assert(length_of_null_terminated_string("") == 0, "Failed: length_of_null_terminated_string");
// Test alloc_string and dealloc_string
string alloc_str = alloc_string(10);
assert(alloc_str.data != NULL, "Failed: alloc_string");
assert(alloc_str.count == 10, "Failed: alloc_string");
dealloc_string(alloc_str);
// Test string_concat
string str1 = const_string("Hello, ");
string str2 = const_string("World!");
string concat_str = string_concat(str1, str2);
assert(concat_str.count == str1.count + str2.count, "Failed: string_concat");
assert(memcmp(concat_str.data, "Hello, World!", concat_str.count) == 0, "Failed: string_concat");
dealloc_string(concat_str);
// Test convert_to_null_terminated_string
char* cstr = convert_to_null_terminated_string(str1);
assert(strcmp(cstr, "Hello, ") == 0, "Failed: convert_to_null_terminated_string");
dealloc(cstr);
// Test temp_convert_to_null_terminated_string
cstr = temp_convert_to_null_terminated_string(str2);
assert(strcmp(cstr, "World!") == 0, "Failed: temp_convert_to_null_terminated_string");
// No need to dealloc, it's temporary storage
// Test sprint
string format_str = const_string("Number: %d");
string formatted_str = sprint(format_str, 42);
char* formatted_cstr = convert_to_null_terminated_string(formatted_str);
assert(strcmp(formatted_cstr, "Number: 42") == 0, "Failed: sprint");
dealloc(formatted_str.data);
dealloc(formatted_cstr);
// Test tprint
string temp_formatted_str = tprint(format_str, 100);
formatted_cstr = temp_convert_to_null_terminated_string(temp_formatted_str);
assert(strcmp(formatted_cstr, "Number: 100") == 0, "Failed: tprint");
// No need to dealloc, it's temporary storage
// Test print and printf (visual inspection)
printf("Expected output: Hello, World!\n");
print(const_string("Hello, %s!\n"), const_string("World"));
printf("Expected output: Number: 1234\n");
print(const_string("Number: %d\n"), 1234);
printf("Expected output: Number: 1234\n");
print(const_string("Number: %d\n"), 1234);
printf("Expected output: Mixed values: 42 and 3.14\n");
print(const_string("Mixed values: %d and %.2f\n"), 42, 3.14);
// This should fail assert and print descriptive error
//printf("Expected output (printf): Hello, World!\n");
//printf("Hello, %cs!\n", 5);
// This should fail assert and print descriptive error
// printf("Expected output (printf): Hello, World!\n");
// printf("Hello, %s!\n", "World");
printf("Expected output (printf): Hello, World!\n");
printf("Hello, %s!\n", cstr("World"));
printf("Expected output (printf): Number: 5678\n");
printf("Number: %d\n", 5678);
printf("Expected output (printf): Mixed values: 99 and 2.71\n");
printf("Mixed values: %d and %.2f\n", 99, 2.71);
// Test handling of empty strings
string empty_str = const_string("");
string concat_empty_str = string_concat(empty_str, empty_str);
assert(concat_empty_str.count == 0, "Failed: string_concat with empty strings");
dealloc_string(concat_empty_str);
// Test very large strings (performance test)
string large_str1 = alloc_string(1024 * 1024);
string large_str2 = alloc_string(1024 * 1024);
string large_concat_str = string_concat(large_str1, large_str2);
assert(large_concat_str.count == 2 * 1024 * 1024, "Failed: large string_concat");
dealloc_string(large_str1);
dealloc_string(large_str2);
dealloc_string(large_concat_str);
// Test string with special characters
string special_char_str = const_string("Special chars: \n\t\r");
cstr = convert_to_null_terminated_string(special_char_str);
assert(strcmp(cstr, "Special chars: \n\t\r") == 0, "Failed: special character string");
dealloc(cstr);
string a = tprintf("Hello, %cs!\n", "balls");
string balls1 = string_view(a, 7, 5);
string balls2 = const_string("balls");
assert(strings_match(balls1, balls2), "String match failed");
assert(!strings_match(balls1, a), "String match failed");
} }
void oogabooga_run_tests() { void oogabooga_run_tests() {
printf("Testing allocator...\n"); printf("Testing allocator...\n");
test_allocator(true); test_allocator(true);