#ifndef RT_CORE_H #define RT_CORE_H /* * rtcore.h - Simple base layer library for C * Copyright (C) 2025 Kevin Trogant * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* 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); typedef struct { i64 i; b32 ok; } s8_parse_i64_result; typedef struct { i32 i; b32 ok; } s8_parse_i32_result; /* Parses a integer from string s */ RTC_API s8_parse_i64_result S8ParseI64(s8 s, int base); /* Parses an integer from string s */ RTC_API s8_parse_i32_result S8ParseI32(s8 s, int base); /* 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 + 1, S8End(s) + 1), }; } 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; } RTC_API s8_parse_i64_result S8ParseI64(s8 s, int base) { isize at = s.length - 1; i64 exp = 1; i64 val = 0; while (at >= 0) { u8 c = s.data[at]; int digit = 0; if (c >= '0' && c <= '9') digit = c - '0'; else if (c >= 'A' && c <= 'Z') digit = c - 'A'; else if (c >= 'a' && c <= 'z') digit = c - 'a'; else if (c == '-') { val *= -1; break; } else if (c == '+') { break; } else return (s8_parse_i64_result){0}; if (digit >= base) return (s8_parse_i64_result){0}; val += digit * exp; exp *= base; --at; } return (s8_parse_i64_result){ .i = val, .ok = 1, }; } RTC_API s8_parse_i32_result S8ParseI32(s8 s, int base) { isize at = s.length - 1; i32 exp = 1; i32 val = 0; while (at >= 0) { u8 c = s.data[at]; int digit = 0; if (c >= '0' && c <= '9') digit = c - '0'; else if (c >= 'A' && c <= 'Z') digit = c - 'A'; else if (c >= 'a' && c <= 'z') digit = c - 'a'; else if (c == '-') { val *= -1; break; } else if (c == '+') { break; } else return (s8_parse_i32_result){0}; if (digit >= base) return (s8_parse_i32_result){0}; val += digit * exp; exp *= base; --at; } return (s8_parse_i32_result){ .i = val, .ok = 1, }; } /* 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