commit f63877562dfb3d3602c84a12c5742aaa6a030dcc Author: Kevin Trogant Date: Mon Dec 1 01:30:08 2025 +0100 First version diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1d6e399 --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +--- +AllowAllArgumentsOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AlwaysBreakAfterReturnType: AllDefinitions +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBraces: Allman +ColumnLimit: '120' +IndentPPDirectives: BeforeHash +IndentWidth: '2' +Language: Cpp +PointerAlignment: Right +SortIncludes: 'false' +TabWidth: '2' +UseTab: ForContinuationAndIndentation + +... diff --git a/rtcore.h b/rtcore.h new file mode 100644 index 0000000..4f0224b --- /dev/null +++ b/rtcore.h @@ -0,0 +1,473 @@ +#ifndef RT_CORE_H +#define RT_CORE_H + +/* My base layer library. + * - fixed width types + * - arena allocator + * - utility macros + * - string type + * - simd header inclusion + * - threading wrapper + * - atomics + * - simple file io + */ + +#include +#include +#include +#include +#include + +#ifndef RTC_API + #ifdef __cplusplus + #define RTC_API extern "C" + #else + #define RTC_API extern + #endif +#endif + +/* Nowadays, immintrin.h includes everything I tend to care about (simd) + * This assume x64, which is what I'm always using. + * You still need to build with the appropriate compilation + * flags (-march=avx2 etc. for gcc, /arch:AVX2 etc. for msvc) */ +#include +/* MSVC additionally has intrin.h for microsoft specific intrinsics */ +#ifdef _MSC_VER + #include +#endif + +/* Types */ +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; +typedef float f32; +typedef double f64; +typedef ptrdiff_t isize; +typedef size_t usize; +typedef char byte; +typedef char16_t c16; +typedef int32_t b32; + +/* Utility macros */ +#if defined(__GNUC__) || defined(__clang__) + #ifdef __has_builtin + #if __has_builtin(__builtin_expect) + #define likely(x) __builtin_expect(!!(x), 1) + #define unlikely(x) __builtin_expect(!!(x), 0) + #endif + #endif +#endif +#ifndef likely + #define likely(x) (x) + #define unlikely(x) (x) +#endif + +#define internal static +#define global_variable static + +#if defined(__GNUC__) || defined(__clang__) + #define exported __attribute__((visibility("default"))) +#elif defined(_MSC_VER) + #define exported __dllexport +#endif + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +#define kilobytes(n) ((n) * 1024) +#define megabytes(n) ((n) * 1024 * 1024) +#define gigabytes(n) ((n) * 1024 * 1024 * 1024) + +#define isizeof(x) (isize)sizeof(x) +/* number of elements in array a */ +#define countof(a) (isize)(sizeof(a) / sizeof(*(a))) +/* number of characters in string constant s (exluding the terminating 0) */ +#define lengthof(s) (countof(s) - 1) +#if defined(__GNUC__) || defined(__clang__) + #define assert(x) \ + while (!(x)) \ + { \ + __builtin_trap(); \ + __builtin_unreachable(); \ + } + + #define force_inline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define assert(x) \ + if (!(x)) \ + { \ + __debugbreak(); \ + } + + #define force_inline __forceinline +#endif + +/* Arena allocator */ +typedef struct arena +{ + byte *begin; + byte *end; +} arena; + +typedef struct arena_cp +{ + byte *cp; +} arena_cp; + +static force_inline arena_cp SaveArena(arena a) +{ + return (arena_cp){a.begin}; +} + +static force_inline void RestoreArena(arena *a, arena_cp cp) +{ + a->begin = cp.cp; +} + +enum +{ + /* Disables memory zeroing */ + ALLOC_NOZERO = 0x1, + /* Allocation failure returns NULL instead of aborting */ + ALLOC_SOFTFAIL = 0x2, +}; + +/* allocate objects from an arena. + * usage is one of: + * - a(a, t) - allocate one object of type t from arena a + * - alloc(a, t, n) - allocate n objects of type t from arena a + * - (a, t, n, f) - allocate n objects of type t from arena a using flags f + */ +#define alloc(...) allocx(__VA_ARGS__, alloc4, alloc3, alloc2)(__VA_ARGS__) +#define allocx(a, b, c, d, e, ...) e +#define alloc2(a, t) (t*)ArenaAlloc(a, isizeof(t), _Alignof(t), 1, 0) +#define alloc3(a, t, n) (t*)ArenaAlloc(a, isizeof(t), _Alignof(t), n, 0) +#define alloc4(a, t, n, f) (t*)ArenaAlloc(a, isizeof(t), _Alignof(t), n, f) + +RTC_API void *ArenaAlloc(arena *a, isize size, isize align, isize n, int flags); + +/* String type */ +typedef struct s8 +{ + u8 *data; + isize length; +} s8; +#define S8(_s) \ + (s8) { .data = (u8 *)_s, .length = lengthof(_s), } + +typedef struct { s8 first; s8 second; } split_result; + +/* constructs a string containing the bytes between begin and end (exclusive) */ +RTC_API s8 S8Span(u8* begin, u8* end); +/* check if two strings are equal */ +RTC_API b32 S8Equals(s8 a, s8 b); +/* compare two strings. analogue to strcmp */ +RTC_API isize S8Compare(s8 a, s8 b); +/* compute a simple hash value for a string. */ +RTC_API u64 S8Hash(s8 s); +/* returns s with leading and trailing whitespace removed. + * Treats all characters with a numerical value smaller and equal to 0x20 (space) as whitespace + * The new string shares memory with s */ +RTC_API s8 S8Trim(s8 s); +/* returns s with leading whitespace removed. + * Treats all characters with a numerical value smaller and equal to 0x20 (space) as whitespace. + * The new string shares memory with s */ +RTC_API s8 S8TrimLeft(s8 s); +/* returns s with trailing whitespace removed. + * Treats all characters with a numerical value smaller and equal to 0x20 (space) as whitespace. + * The new string shares memory with s */ +RTC_API s8 S8TrimRight(s8 s); +/* Returns a pointer to the first occurence of character c (or NULL) */ +RTC_API u8 *S8Chr(s8 s, u8 c); +/* Returns a pointer to the last valid character of s */ +RTC_API u8 *S8End(s8 s); +/* Splits the given string s at the first occurence of c. + * If c does not occur, split_result.first contains the whole string */ +RTC_API split_result S8Split(s8 s, u8 c); +/* Creates a clone of string s on arena a */ +RTC_API s8 S8Clone(s8 s, arena *a); + +/* Basic file io */ +typedef struct file_buffer +{ + byte *data; + isize length; +} file_buffer; +/* Loads the content of a file. */ +RTC_API file_buffer ReadEntireFile(s8 path, arena *a); +/* Loads the content of a file into a s8 */ +RTC_API s8 ReadEntireFileS8(s8 path, arena *a); +/* Write the given buffer to a file */ +RTC_API b32 WriteEntireFile(s8 path, byte *data, isize length); + +/* Atomics */ +#if defined(__GNUC__) || defined(__clang__) + /* Atomic add */ + #define AtomicAdd32(_addend, _val) __atomic_add_fetch((i32 *)_addend, _val, __ATOMIC_SEQ_CST) + #define AtomicAdd64(_addend, _val) __atomic_add_fetch((i64 *)_addend, _val, __ATOMIC_SEQ_CST) + #define AtomicStore(_ptr, _val) __atomic_store_n(_ptr, _val, __ATOMIC_SEQ_CST) + #define AtomicStoreRelease(_ptr, _val) __atomic_store_n(_ptr, _val, __ATOMIC_RELEASE) + #define AtomicLoad(_ptr) __atomic_load_n(_ptr, __ATOMIC_SEQ_CST) + #define AtomicLoadAcquire(_ptr) __atomic_load_n(_ptr, __ATOMIC_ACQUIRE) +#elif defined(_MSC_VER) + #define AtomicAdd32(_addend, _val) _InterlockedExchangeAdd((volatile long *)_addend, _val) + #define AtomicAdd64(_addend, _val) _InterlockedExchangeAdd64((volatile __int64 *)_addend, _val) + #define AtomicStore(_ptr, _val) _InterlockedExchange((volatile long *)_ptr, _val) + #define AtomicStoreRelease(_ptr, _val) _InterlockedExchange_HLERelease(_ptr, _val) + #define AtomicLoad(_ptr) _InterlockedOr(_ptr, 0) + #define AtomicLoadAcquire(_ptr) _InterlockedOr_HLEAcquire(_ptr, 0) +#endif + +/* Threading wrapper */ +typedef struct thread thread; + +/* Win32 uses DWORD as the return type, Linux uses void *. + * I don't think that I've ever used a threads return value... */ +#ifdef _WIN32 +#define THREAD_RETURN_TYPE u32 +#elif defined(__linux__) +#define THREAD_RETURN_TYPE void * +#endif + +/* Generates a thread entry point */ +#define THREAD_FN(_name) THREAD_RETURN_TYPE _name(void *param) +typedef THREAD_FN(thread_fn); +/* Starts a new thread */ +RTC_API thread *StartThread(thread_fn *fn, void *param); +/* Waits until a thread terminates, returns its return value. */ +RTC_API THREAD_RETURN_TYPE JoinThread(thread *t); + +#endif + +#ifdef RT_CORE_IMPLEMENTATION +#undef RT_CORE_IMPLEMENTATION + +#include +#include + +#ifdef __linux__ + #include +#elif defined(_WIN32) + #define WIN32_LEAN_AND_MEAN + #include +#endif + +/* Alloc */ +RTC_API void * +ArenaAlloc(arena *a, isize size, isize align, isize n, int flags) +{ + isize padding = -(usize)a->begin & (align - 1); + isize available = a->end - a->begin - padding; + if (available < 0 || n > available / size) + { + if (!(flags & ALLOC_SOFTFAIL)) + abort(); + return NULL; + } + void *p = a->begin + padding; + a->begin += padding - n * size; + return (flags & ALLOC_NOZERO) ? p : memset(p, 0, n * size); +} + +/* S8 funcs */ +RTC_API s8 +S8Span(u8* begin, u8* end) +{ + s8 s = {0}; + s.data = begin; + s.length = begin ? end - begin : 0; + return s; +} +RTC_API b32 +S8Equals(s8 a, s8 b) +{ + return a.length == b.length && (!a.length || (memcmp(a.data, b.data, a.length) == 0)); +} +RTC_API isize +S8Compare(s8 a, s8 b) +{ + isize n = min(a.length, b.length); + for (isize i = 0; i < n; ++i) + { + if (a.data[i] != b.data[i]) + return (isize)a.data[i] - (isize)b.data[i]; + } + return a.length - b.length; +} +RTC_API u64 +S8Hash(s8 s) +{ + u64 hash = 0xcbf29ce484222325; + for (isize i = 0; i < s.length; ++i) + hash = (hash ^ s.data[i]) * 0x00000100000001b3; + return hash; +} +RTC_API s8 +S8Trim(s8 s) +{ + for (; s.length && *s.data <= ' '; s.data++, s.length--) + { + } + for (; s.length && s.data[s.length - 1] <= ' '; s.length--) + { + } + return s; +} +RTC_API s8 +S8TrimLeft(s8 s) +{ + for (; s.length && *s.data <= ' '; s.data++, s.length--) + { + } + return s; +} +RTC_API s8 +S8TrimRight(s8 s) +{ + for (; s.length && s.data[s.length - 1] <= ' '; s.length--) + { + } + return s; +} +RTC_API u8 * +S8Chr(s8 s, u8 c) +{ + for (isize i = 0; i < s.length; ++i) + { + if (s.data[i] == c) + return &s.data[i]; + } + return NULL; +} +RTC_API u8 * +S8End(s8 s) +{ + return &s.data[s.length - 1]; +} +RTC_API split_result +S8Split(s8 s, u8 c) +{ + u8 *chr = S8Chr(s, c); + if (!chr) + return (split_result){s}; + return (split_result){ + S8Span(s.data, chr), + S8Span(chr, S8End(s)), + }; +} +RTC_API s8 +S8Clone(s8 s, arena *a) +{ + s8 c = {0}; + c.data = alloc(a, u8, s.length); + c.length = s.length; + memcpy(c.data, s.data, s.length); + return s; +} + +/* Basic file io */ +RTC_API file_buffer +ReadEntireFile(s8 path, arena *a) +{ + char _p[260]; + if (path.length >= countof(_p)) + return (file_buffer){0}; + memcpy(_p, path.data, path.length); + _p[path.length] = '\0'; + FILE *f = fopen(_p, "rb"); + if (!f) + return (file_buffer){0}; + size_t max = (size_t)(a->end - a->begin); + size_t num = fread(a->begin, 1, max, f); + if (ferror(f)) + { + fclose(f); + return (file_buffer){0}; + } + fclose(f); + file_buffer fbuf = { + .data = (byte *)a->begin, + .length = (isize)num, + }; + a->begin += num; + return fbuf; +} +RTC_API s8 +ReadEntireFileS8(s8 path, arena *a) +{ + file_buffer fb = ReadEntireFile(path, a); + return (s8){ + .data = (u8 *)fb.data, + .length = fb.length, + }; +} +RTC_API b32 +WriteEntireFile(s8 path, byte *data, isize length) +{ + char _p[260]; + if (path.length >= countof(_p)) + return 0; + memcpy(_p, path.data, path.length); + _p[path.length] = '\0'; + FILE *f = fopen(_p, "wb"); + if (!f) + return 0; + size_t num = fwrite(data, (size_t)length, 1, f); + fclose(f); + return num == 1; +} + +/* Threading */ +#ifdef __linux__ +RTC_API thread * +StartThread(thread_fn *fn, void *param) +{ + pthread_t t; + if (pthread_create(&t, NULL, fn, param) != 0) + return NULL; + return (thread *)(usize)t; +} +RTC_API THREAD_RETURN_TYPE +JoinThread(thread *t) +{ + void *ret = NULL; + pthread_join((pthread_t)(usize)t, &ret); + return ret; +} + +#elif defined(_WIN32) +RTC_API thread * +StartThread(thread_fn *fn, void *param) +{ + HANDLE h = CreateThread( + NULL, + 0, /* Use default stack size */ + (LPTHREAD_START_ROUTINE)fn, + param, + 0, + NULL); + return (thread *)h; +} +RTC_API THREAD_RETURN_TYPE +JoinThread(thread *t) +{ + HANDLE h = (HANDLE)t; + if (WaitForSingleObject(h, INFINITE) == WAIT_OBJECT_0) + { + DWORD exit; + GetExitCodeThread(h, &exit); + CloseHandle(h); + return exit; + } + return 0; +} + +#endif + +#endif + diff --git a/somedata.txt b/somedata.txt new file mode 100644 index 0000000..a32a434 --- /dev/null +++ b/somedata.txt @@ -0,0 +1 @@ +1234567890 diff --git a/test.c b/test.c new file mode 100644 index 0000000..2e688a3 --- /dev/null +++ b/test.c @@ -0,0 +1,60 @@ +#define RT_CORE_IMPLEMENTATION +#include "rtcore.h" + +#include + +THREAD_FN(TestThread) +{ + int *p = param; + printf("Thread got param %d\n", AtomicLoad(p)); + AtomicStore(p, 42); + return 0; +} + +internal int +AllocTest(void) +{ + char space[260]; + arena a = {.begin = space, .end = space + countof(space) }; + + int *t = alloc(&a, int); + if (!t) + return 0; + *t = 42; + int *t2 = alloc(&a, int); + if (t == t2) + return 0; + *t2 = *t; + + return 1; +} + +internal int +ReadFileTest(void) +{ + char space[260]; + arena a = {.begin = space, .end = space + countof(space) }; + s8 data = ReadEntireFileS8(S8("somedata.txt"), &a); + return S8Equals(data, S8("1234567890\n")); +} + +internal int +ThreadTest(void) +{ + int p = 32; + thread *t = StartThread(TestThread, &p); + JoinThread(t); + return AtomicLoad(&p) == 42; +} + +int +main() +{ + if (!AllocTest()) + return 1; + if (!ReadFileTest()) + return 2; + if (!ThreadTest()) + return 3; + return 0; +}