- 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:
parent
49ca7f3406
commit
db73d0bd2e
11 changed files with 744 additions and 185 deletions
112
.gitignore
vendored
112
.gitignore
vendored
|
@ -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
|
3
build.c
3
build.c
|
@ -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;
|
||||||
|
|
|
@ -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
6
main.c
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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, ...);
|
|
@ -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);
|
||||||
|
|
Reference in a new issue