- 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
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -53,3 +53,5 @@ test_doc.vkn
|
|||
|
||||
*keybinds
|
||||
*.rdi
|
||||
|
||||
.vscode
|
3
build.c
3
build.c
|
@ -1,8 +1,9 @@
|
|||
|
||||
|
||||
///
|
||||
// Build config stuff
|
||||
#define VERY_DEBUG 0
|
||||
#define RUN_TESTS 1
|
||||
#define RUN_TESTS 0
|
||||
|
||||
typedef struct Context_Extra {
|
||||
int monkee;
|
||||
|
|
|
@ -7,7 +7,7 @@ pushd build
|
|||
mkdir 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
|
6
main.c
6
main.c
|
@ -1,11 +1,11 @@
|
|||
|
||||
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
|
||||
// heap allocator in memory.c
|
||||
int *a = cast(int*)alloc(sizeof(int));
|
||||
int *a = (int*)alloc(sizeof(int));
|
||||
dealloc(a);
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
|
||||
int hello;
|
||||
hello = 5;
|
||||
|
||||
|
|
|
@ -21,43 +21,59 @@ typedef u8 bool;
|
|||
|
||||
#define thread_local _Thread_local
|
||||
|
||||
#define ifnt(x) if (!(x))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define os_debug_break __debugbreak
|
||||
inline void os_break() {
|
||||
__debugbreak();
|
||||
int *a = 0;
|
||||
*a = 5;
|
||||
}
|
||||
#else
|
||||
#error "Only msvc compiler supported at the moment";
|
||||
#endif
|
||||
|
||||
#define assert(cond, ...) if (!(cond)) { printf("Assertion failed for condition: " #cond ". Message: " __VA_ARGS__); os_debug_break(); }
|
||||
|
||||
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)
|
||||
|
||||
///
|
||||
// inline
|
||||
// (Credit to chatgpt, so it might not be 100% correct)
|
||||
// Compiler specific stuff
|
||||
#ifdef _MSC_VER
|
||||
// Microsoft Visual C++
|
||||
#define inline __forceinline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
// GNU GCC/G++
|
||||
#define inline __attribute__((always_inline)) inline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
|
||||
#elif defined(__clang__)
|
||||
// Clang/LLVM
|
||||
#define inline __attribute__((always_inline)) inline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
|
||||
#elif defined(__INTEL_COMPILER) || defined(__ICC)
|
||||
// Intel C++ Compiler
|
||||
#define inline __forceinline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
|
||||
#elif defined(__BORLANDC__)
|
||||
// Borland C++
|
||||
#define inline __inline
|
||||
#elif defined(__MINGW32__) || defined(__MINGW64__)
|
||||
// MinGW (Minimalist GNU for Windows)
|
||||
#define inline __attribute__((always_inline)) inline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
|
||||
#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
|
||||
// Oracle Solaris Studio
|
||||
#define inline inline __attribute__((always_inline))
|
||||
#elif defined(__IBMC__) || defined(__IBMCPP__)
|
||||
// IBM XL C/C++ Compiler
|
||||
#define inline __attribute__((always_inline)) inline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 1
|
||||
#elif defined(__PGI)
|
||||
// Portland Group Compiler
|
||||
#define inline inline __attribute__((always_inline))
|
||||
|
@ -120,3 +136,5 @@ void push_allocator(Allocator a) {
|
|||
push_context(c);
|
||||
}
|
||||
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
|
||||
|
@ -34,7 +66,7 @@ typedef struct {
|
|||
|
||||
Heap_Block *heap_head;
|
||||
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) {
|
||||
|
@ -47,6 +79,17 @@ u64 get_heap_block_size_including_metadata(Heap_Block *block) {
|
|||
bool is_pointer_in_program_memory(void *p) {
|
||||
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
|
||||
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 new_program_size = (cast(u64)(minimum_size * 1.5));
|
||||
assert(new_program_size >= minimum_size, "Bröd");
|
||||
printf("Growing program memory to %llu bytes\n", new_program_size);
|
||||
const u64 ATTEMPTS = 1000;
|
||||
for (u64 i = 0; i <= ATTEMPTS; i++) {
|
||||
if (program_memory_size >= new_program_size) break; // Another thread might have resized already, causing it to fail here.
|
||||
|
@ -158,9 +200,10 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) {
|
|||
}
|
||||
|
||||
void heap_init() {
|
||||
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE);
|
||||
heap_mutex = os_make_mutex();
|
||||
if (heap_initted) return;
|
||||
heap_initted = true;
|
||||
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE);
|
||||
heap_lock = os_make_spinlock();
|
||||
}
|
||||
|
||||
void *heap_alloc(u64 size) {
|
||||
|
@ -168,7 +211,7 @@ void *heap_alloc(u64 size) {
|
|||
if (!heap_initted) heap_init();
|
||||
|
||||
// #Sync #Speed oof
|
||||
os_lock_mutex(heap_mutex);
|
||||
os_spinlock_lock(heap_lock);
|
||||
|
||||
size += sizeof(Heap_Allocation_Metadata);
|
||||
|
||||
|
@ -257,7 +300,7 @@ void *heap_alloc(u64 size) {
|
|||
#endif
|
||||
|
||||
// #Sync #Speed oof
|
||||
os_unlock_mutex(heap_mutex);
|
||||
os_spinlock_unlock(heap_lock);
|
||||
|
||||
return ((u8*)meta)+sizeof(Heap_Allocation_Metadata);
|
||||
}
|
||||
|
@ -266,15 +309,15 @@ void heap_dealloc(void *p) {
|
|||
|
||||
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!");
|
||||
p = (u8*)p-sizeof(Heap_Allocation_Metadata);
|
||||
Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)(p);
|
||||
|
||||
// If > 256GB then prolly not legit lol
|
||||
assert(meta->size < 1024ULL*1024ULL*1024ULL*256ULL, "Garbage pointer passed to heap_dealloc !!!");
|
||||
assert(is_pointer_in_program_memory(meta->block), "Garbage pointer passed to heap_dealloc !!!");
|
||||
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 !!! Or could be corrupted memory.");
|
||||
|
||||
// Yoink meta data before we start overwriting it
|
||||
Heap_Block *block = meta->block;
|
||||
|
@ -327,38 +370,8 @@ void heap_dealloc(void *p) {
|
|||
#endif
|
||||
|
||||
// #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 (!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;
|
||||
return talloc(size);;
|
||||
|
|
|
@ -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 "string.c"
|
||||
|
@ -9,6 +24,7 @@
|
|||
|
||||
|
||||
|
||||
|
||||
#ifdef OS_WINDOWS
|
||||
#include "os_impl_windows.c"
|
||||
#elif defined (OS_LINUX)
|
||||
|
@ -33,9 +49,13 @@ void oogabooga_init(u64 program_memory_size) {
|
|||
#define RUN_TESTS 0
|
||||
#endif
|
||||
|
||||
|
||||
int oogabooga_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);
|
||||
printf("Ooga booga program started\n");
|
||||
|
||||
// This can be disabled in build.c
|
||||
#if RUN_TESTS
|
||||
|
|
|
@ -26,6 +26,23 @@ void os_init(u64 program_memory_size) {
|
|||
os.granularity = cast(u64)si.dwAllocationGranularity;
|
||||
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();
|
||||
os_grow_program_memory(program_memory_size);
|
||||
|
@ -35,16 +52,17 @@ void os_init(u64 program_memory_size) {
|
|||
heap_allocator.proc = heap_allocator_proc;
|
||||
heap_allocator.data = 0;
|
||||
|
||||
heap_init();
|
||||
context.allocator = heap_allocator;
|
||||
|
||||
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");
|
||||
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"));
|
||||
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"));
|
||||
assert(os.crt_memcpy, "Missing memcpy in crt");
|
||||
os.crt_memcmp = (Crt_Memcmp_Proc)os_dynamic_library_load_symbol(os.crt, const_string("memcmp"));
|
||||
|
@ -205,3 +223,58 @@ void os_write_string_to_stdout(string s) {
|
|||
|
||||
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
|
||||
#include <Windows.h>
|
||||
#define OS_WINDOWS
|
||||
|
||||
typedef HANDLE Mutex_Handle;
|
||||
typedef HANDLE Thread_Handle;
|
||||
typedef HMODULE Dynamic_Library_Handle;
|
||||
|
||||
#elif defined(__linux__)
|
||||
// Include whatever #Incomplete #Portability
|
||||
#define OS_LINUX
|
||||
typedef SOMETHING Mutex_Handle;
|
||||
typedef SOMETHING Thread_Handle;
|
||||
typedef SOMETHING Dynamic_Library_Handle;
|
||||
|
||||
#error "Linux is not supported yet";
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
// Include whatever #Incomplete #Portability
|
||||
#define OS_MAC
|
||||
typedef SOMETHING Mutex_Handle;
|
||||
typedef SOMETHING Thread_Handle;
|
||||
typedef SOMETHING Dynamic_Library_Handle;
|
||||
|
@ -34,16 +27,12 @@
|
|||
|
||||
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
|
||||
|
||||
#define oogabooga_va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v))
|
||||
#define oogabooga_va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
|
||||
#define oogabooga_va_end(ap) (ap = (va_list)0)
|
||||
|
||||
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_Vsnprintf_Proc) (char*, size_t, const char*, va_list);
|
||||
typedef int (__cdecl *Crt_Vsprintf_Proc) (char*, const char*, va_list);
|
||||
|
||||
typedef struct Os_Info {
|
||||
u64 page_size;
|
||||
|
@ -54,13 +43,20 @@ typedef struct Os_Info {
|
|||
Crt_Memcpy_Proc crt_memcpy;
|
||||
Crt_Memcmp_Proc crt_memcmp;
|
||||
Crt_Memset_Proc crt_memset;
|
||||
Crt_Printf_Proc crt_printf; // #Cleanup remove after we have our own print
|
||||
Crt_Vprintf_Proc crt_vprintf; // #Cleanup remove after we have our own print
|
||||
Crt_Vprintf_Proc crt_vprintf;
|
||||
Crt_Vsnprintf_Proc crt_vsnprintf;
|
||||
Crt_Vsprintf_Proc crt_vsprintf;
|
||||
|
||||
void *static_memory_start, *static_memory_end;
|
||||
|
||||
} Os_Info;
|
||||
Os_Info os;
|
||||
|
||||
inline int crt_vprintf(const char* fmt, va_list args) {
|
||||
return os.crt_vprintf(fmt, args);
|
||||
}
|
||||
|
||||
#if !defined(COMPILER_HAS_MEMCPY_INTRINSICS) || defined(DEBUG)
|
||||
inline void* naive_memcpy(void* dest, const void* source, size_t size) {
|
||||
for (u64 i = 0; i < (u64)size; i++) ((u8*)dest)[i] = ((u8*)source)[i];
|
||||
return dest;
|
||||
|
@ -88,32 +84,20 @@ 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;
|
||||
#endif
|
||||
|
||||
inline int vsnprintf(char* buffer, size_t n, const char* fmt, va_list args) {
|
||||
return os.crt_vsnprintf(buffer, n, fmt, args);
|
||||
}
|
||||
|
||||
inline int crt_sprintf(char *str, const char *format, ...) {
|
||||
va_list args;
|
||||
oogabooga_va_start(args, fmt);
|
||||
int r = vprintf(fmt, args);
|
||||
oogabooga_va_end(args);
|
||||
|
||||
va_start(args, format);
|
||||
int r = os.crt_vsprintf(str, format, args);
|
||||
va_end(args);
|
||||
return r;
|
||||
}
|
||||
void os_write_string_to_stdout(string s);
|
||||
inline int vprintf(const char* fmt, va_list args) {
|
||||
if (os.crt_vprintf) return os.crt_vprintf(fmt, args);
|
||||
else {
|
||||
os_write_string_to_stdout(cstr(fmt));
|
||||
os_write_string_to_stdout(cstr(" <crt_vprintf is not loaded so we cannot vprintf.>"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
inline int vsnprintf(char* buffer, size_t n, const char* fmt, va_list args) {
|
||||
os.crt_vsnprintf(buffer, n, fmt, args);
|
||||
}
|
||||
|
||||
void* program_memory = 0;
|
||||
u64 program_memory_size = 0;
|
||||
Mutex_Handle program_memory_mutex = 0;
|
||||
|
||||
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_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);
|
||||
|
||||
|
||||
///
|
||||
///
|
||||
|
@ -179,3 +181,66 @@ void os_unload_dynamic_library(Dynamic_Library_Handle l);
|
|||
///
|
||||
|
||||
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* talloc(u64);
|
||||
|
||||
typedef struct string {
|
||||
u8 *data;
|
||||
u64 count;
|
||||
u8 *data;
|
||||
} string;
|
||||
|
||||
void push_temp_allocator();
|
||||
|
||||
#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) {
|
||||
u64 len = 0;
|
||||
|
@ -20,8 +62,18 @@ inline u64 length_of_null_terminated_string(const char* cstring) {
|
|||
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 !
|
||||
string string_concat(string left, string right) {
|
||||
string string_concat(const string left, const string right) {
|
||||
string result;
|
||||
result.count = left.count + right.count;
|
||||
result.data = cast(u8*)alloc(result.count);
|
||||
|
@ -30,16 +82,204 @@ string string_concat(string left, string right) {
|
|||
return result;
|
||||
}
|
||||
// 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);
|
||||
memcpy(cstring, s.data, s.count);
|
||||
cstring[s.count] = 0;
|
||||
return cstring;
|
||||
}
|
||||
|
||||
char *temp_convert_to_null_terminated_string(string s) {
|
||||
char *temp_convert_to_null_terminated_string(const string s) {
|
||||
push_temp_allocator();
|
||||
char *c = convert_to_null_terminated_string(s);
|
||||
pop_allocator();
|
||||
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) {
|
||||
// Basic allocation and free
|
||||
|
@ -70,6 +99,8 @@ void test_allocator(bool do_log_heap) {
|
|||
}
|
||||
|
||||
|
||||
reset_temporary_storage();
|
||||
|
||||
push_allocator(temp);
|
||||
|
||||
int* foo = (int*)alloc(72);
|
||||
|
@ -222,10 +253,108 @@ void test_allocator_threaded(Thread *t) {
|
|||
}
|
||||
|
||||
void test_strings() {
|
||||
string s = (string){ (u8*)"Ooga booga", length_of_null_terminated_string("Ooga booga") };
|
||||
string a = const_string("Ooga booga");
|
||||
// Test length_of_null_terminated_string
|
||||
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() {
|
||||
printf("Testing allocator...\n");
|
||||
test_allocator(true);
|
||||
|
|
Reference in a new issue