rtcore/rtcore.h
2025-12-12 11:27:16 +01:00

639 lines
16 KiB
C

#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 <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <uchar.h>
#include <string.h>
#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 <immintrin.h>
/* MSVC additionally has intrin.h for microsoft specific intrinsics */
#ifdef _MSC_VER
#include <intrin.h>
#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);
/* Splits the given string s at the first occurence of c.
* If c does not occur, split_result.first contains the whole string.
* This version also consumes repeating occurences of c */
RTC_API split_result S8Split2(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
/* Aliases for intrinsics */
#if defined(__GNUC__) || defined(__clang__)
#define CTZ32(ui) __builtin_ctz(ui)
#define CTZ64(ul) __builtin_ctzl(ul)
#define CLZ32(ui) __builtin_clz(ui)
#define CLZ64(ul) __builtin_clzl(ul)
#define PopCount32(_x) __builtin_popcount(_x)
#define PopCount64(_x) __builtin_popcountl(_x)
#elif defined(_MSC_VER)
static force_inline unsigned int CTZ32(u32 x)
{
unsigned int index;
_BitScanReverse(&index, x);
return index;
}
static force_inline unsigned int CTZ64(u64 x)
{
unsigned int index;
_BitScanReverse64(&index, x);
return index;
}
static force_inline unsigned int CLZ32(u32 x)
{
unsigned int index;
_BitScanForward(&index, x);
return index;
}
static force_inline unsigned int CLZ64(u64 x)
{
unsigned int index;
_BitScanForward64(&index, x);
return index;
}
#define PopCount32(_x) __popcnt(_x)
#define PopCount64(_x) __popcnt64(_x)
#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 <stdlib.h>
#include <stdio.h>
#ifdef __linux__
#include <pthread.h>
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#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 = (isize)(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 split_result
S8Split2(s8 s, u8 c)
{
u8 *chr = S8Chr(s, c);
if (!chr)
return (split_result){s};
split_result r = {
.first = S8Span(s.data, chr),
};
u8 *end = S8End(s) + 1;
while (chr != end && *chr == c)
++chr;
r.second = S8Span(chr, end);
return r;
}
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