#pragma once #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include #define Assert(x) \ if (!(x)) { \ __debugbreak(); \ } #define InvalidCodepath() Assert(!"invalid codepath") #define ElseInvalidCodepath() else {InvalidCodepath()} #if defined(__APPLE__) && defined(__MACH__) #define OS_MAC 1 #elif defined(_WIN32) #define OS_WINDOWS 1 #elif defined(__linux__) #define OS_POSIX 1 #define OS_LINUX 1 #else #error Unsupported platform #endif #if defined(__clang__) #define COMPILER_CLANG 1 #elif defined(__GNUC__) || defined(__GNUG__) #define COMPILER_GCC 1 #elif defined(_MSC_VER) #define COMPILER_MSVC 1 #else #error Unsupported compiler #endif #ifndef OS_MAC #define OS_MAC 0 #endif #ifndef OS_WINDOWS #define OS_WINDOWS 0 #endif #ifndef OS_LINUX #define OS_LINUX 0 #endif #ifndef OS_POSIX #define OS_POSIX 0 #endif #ifndef COMPILER_MSVC #define COMPILER_MSVC 0 #endif #ifndef COMPILER_CLANG #define COMPILER_CLANG 0 #endif #ifndef COMPILER_GCC #define COMPILER_GCC 0 #endif #define KiB(x) ((x##ull) * 1024ull) #define MiB(x) (KiB(x) * 1024ull) #define GiB(x) (MiB(x) * 1024ull) #define TiB(x) (GiB(x) * 1024ull) #define Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0])))) using U8 = uint8_t; using U16 = uint16_t; using U32 = uint32_t; using U64 = uint64_t; using S8 = int8_t; using S16 = int16_t; using S32 = int32_t; using S64 = int64_t; using Int = S64; using UInt = U64; template T Min(T a, T b) { if (a > b) return b; return a; } template T ClampTop(T a, T top) { return Min(a, top); } template T Max(T a, T b) { if (a > b) return a; return b; } template T ClampBottom(T bottom, T b) { return Max(bottom, b); } template T Clamp(T value, T min, T max) { if (value < min) return min; if (value > max) return max; return value; } template void Swap(T *a, T *b) { T temp = *a; *a = *b; *b = temp; } inline bool IsPowerOf2(size_t x) { size_t result = (((x) & ((x)-1)) == 0); return result; } inline size_t WrapAroundPowerOf2(size_t x, size_t pow2) { Assert(IsPowerOf2(pow2)); size_t result = (((x) & ((pow2)-1llu))); return result; } inline uint64_t HashBytes(void *data, unsigned size) { uint8_t *data8 = (uint8_t *)data; uint64_t hash = (uint64_t)14695981039346656037ULL; for (unsigned i = 0; i < size; i++) { hash = hash ^ (uint64_t)(data8[i]); hash = hash * (uint64_t)1099511628211ULL; } return hash; } inline size_t GetAlignOffset(size_t size, size_t align) { Assert(IsPowerOf2(align)); size_t mask = align - 1; size_t val = size & mask; if (val) { val = align - val; } return val; } inline size_t AlignUp(size_t size, size_t align) { size_t result = size + GetAlignOffset(size, align); return result; } inline size_t AlignDown(size_t size, size_t align) { size += 1; // Make sure when align is 8 doesn't get rounded down to 0 size_t result = size - (align - GetAlignOffset(size, align)); return result; } const int AllocatorKind_Allocate = 1; const int AllocatorKind_Deallocate = 2; struct Allocator { void *(*proc)(void *object, int kind, void *p, size_t size); void *object; }; inline void *AllocSize(Allocator alo, size_t size) { void *result = alo.proc(alo.object, AllocatorKind_Allocate, NULL, size); memset(result, 0, size); return result; } #define AllocType(alo, Type) (Type *)AllocSize(alo, sizeof(Type)) #define AllocArray(alo, Type, count) (Type *)AllocSize(alo, sizeof(Type) * (count)) template void Dealloc(Allocator alo, T **p) { if (*p == NULL) return; alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0); *p = NULL; } Allocator GetSystemAllocator(); #define MemoryZero(x, size) memset(x, 0, size) #define MemoryCopy(dst, src, size) memcpy(dst, src, size) #define MemoryMove(dst, src, size) memmove(dst, src, size) /* // Iterating and removing elements for (int i = 0; i < array.len; i += 1) { auto &it = array[i]; bool remove_item = false; defer { if (remove_item) { array.ordered_remove(it); i -= 1; } } } // Simple delete IterRemove(arr) { IterRemovePrepare(arr); remove_item = true; } // Deleting backwards For(arr.reverse_iter()) { defer{ arr.unordered_remove(it); }; } */ #define IterRemove(a) for (int i = 0; i < (a).len; i += 1) #define IterRemovePrepare(a) \ auto &it = (a)[i]; \ bool remove_item = false; \ defer { \ if (remove_item) { \ Remove(&(a), it); \ i -= 1; \ } \ } #define ForItem(it, array) for (auto &it : (array)) #define For(array) ForItem(it, array) constexpr int64_t SLICE_LAST = INT64_MIN; inline int64_t StringLen(char *string) { if (!string) return 0; int64_t i = 0; while (string[i]) i += 1; return i; } inline int64_t WideLength(char16_t *string) { if (!string) return 0; int64_t len = 0; while (*string++ != 0) len++; return len; } template struct Slice { T *data; int64_t len; Slice() = default; Slice(T *s, int64_t l) : data(s), len(l) {} Slice(char *s) : data(s), len(StringLen(s)) {} Slice(const char *s) : data((char *)s), len(StringLen((char *)s)) {} Slice(const char *s, int64_t l) : data((char *)s), len(l) {} Slice(char16_t *s) : data(s), len(WideLength(s)) {} Slice(const char16_t *s) : data((char16_t *)s), len(WideLength((char16_t *)s)) {} Slice(const char16_t *s, int64_t l) : data((char16_t *)s), len(l) {} T &operator[](int64_t index) { Assert(index < len); return data[index]; } T *begin() { return data; } T *end() { return data + len; } }; template Slice Copy(Allocator alo, Slice array) { Slice result = {}; result.data = AllocArray(alo, T, array.len); memcpy(result.data, array.data, sizeof(T) * array.len); result.len = array.len; return result; } template T Pop(Slice *arr) { Assert(arr->len > 0); return arr->data[--arr->len]; } template bool Contains(Slice &arr, T item) { For(arr) if (it == item) return true; return false; } template int64_t GetIndex(Slice &arr, const T &item) { ptrdiff_t index = (ptrdiff_t)(&item - arr.data); Assert(index >= 0 && index < arr.len); return (int64_t)index; } template T Get(Slice &arr, int64_t i, T default_value = {}) { T result = default_value; if (i >= 0 && i < arr.len) result = arr[i]; return result; } template bool IsLast(Slice &arr, T &item) { bool result = arr.last() == &item; return result; } template bool IsFirst(Slice &arr, T &item) { bool result = arr.first() == &item; return result; } template T *GetFirst(Slice &arr) { Assert(arr.len > 0); return arr.data; } template T *GetLast(Slice &arr) { Assert(arr.len > 0); return arr.data + arr.len - 1; } template Slice Chop(Slice &arr, int64_t len) { len = ClampTop(len, arr.len); Slice result = {arr.data, arr.len - len}; return result; } template Slice Skip(Slice &arr, int64_t len) { len = ClampTop(len, arr.len); Slice result = {arr.data + len, arr.len - len}; return result; } template Slice GetPostfix(Slice &arr, int64_t len) { len = ClampTop(len, arr.len); int64_t remain_len = arr.len - len; Slice result = {arr.data + remain_len, len}; return result; } template Slice GetPrefix(Slice &arr, int64_t len) { len = ClampTop(len, arr.len); Slice result = {arr.data, len}; return result; } template Slice GetSlice(Slice &arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) { // Negative indexes work in python style, they return you the index counting from end of list if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len; if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; if (first_index == SLICE_LAST) first_index = arr.len; if (first_index < 0) first_index = arr.len + first_index; Slice result = {arr.data, arr.len}; if (arr.len > 0) { if (one_past_last_index > first_index) { first_index = ClampTop(first_index, arr.len - 1); one_past_last_index = ClampTop(one_past_last_index, arr.len); result.data += first_index; result.len = one_past_last_index - first_index; } else { result.len = 0; } } return result; } // Make arrays resize on every item #define ARRAY_DEBUG 0 #if ARRAY_DEBUG #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF #else #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) ELSE #endif template struct Array { Allocator allocator; int64_t cap; union { Slice slice; struct { T *data; int64_t len; }; }; T &operator[](int64_t index) { Assert(index >= 0 && index < len); return data[index]; } T *begin() { return data; } T *end() { return data + len; } }; template T *GetFirst(Array &arr) { Assert(arr.len > 0); return arr.data; } template T *GetLast(Array &arr) { Assert(arr.len > 0); return arr.data + arr.len - 1; } template void Reserve(Array *arr, int64_t size) { if (size > arr->cap) { if (!arr->allocator.proc) arr->allocator = GetSystemAllocator(); T *new_data = AllocArray(arr->allocator, T, size); Assert(new_data); memcpy(new_data, arr->data, arr->len * sizeof(T)); Dealloc(arr->allocator, &arr->data); arr->data = new_data; arr->cap = size; } } template void TryGrowing(Array *arr) { if (arr->len + 1 > arr->cap) { int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16); int64_t new_size = ClampBottom(initial_size, arr->cap ARRAY_IF_DEBUG_ELSE(+1, *2)); Reserve(arr, new_size); } } template void TryGrowing(Array *arr, int64_t item_count) { if (arr->len + item_count > arr->cap) { int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16); int64_t new_size = ClampBottom(initial_size, (arr->cap + item_count) ARRAY_IF_DEBUG_ELSE(+1, *2)); Reserve(arr, new_size); } } template void Add(Array *arr, T item) { TryGrowing(arr); arr->data[arr->len++] = item; } template void Add(Array *arr, Array &another) { For(another) Add(arr, it); } template void Add(Array *arr, T *items, int64_t item_count) { for (int64_t i = 0; i < item_count; i += 1) Add(arr, items[i]); } template void BoundedAdd(Array *arr, T item) { if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item; } template void BoundedAddError(Array *arr, T item) { Assert(arr->len + 1 <= arr->cap); if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item; } template void Insert(Array *arr, T item, int64_t index) { if (index == arr->len) { Add(arr, item); return; } Assert(index < arr->len); Assert(index >= 0); TryGrowing(arr); int64_t right_len = arr->len - index; memmove(arr->data + index + 1, arr->data + index, sizeof(T) * right_len); arr->data[index] = item; arr->len += 1; } template Array Copy(Allocator alo, Array array) { Array result = {alo}; Reserve(&result, array.cap); memcpy(result.data, array.data, sizeof(T) * array.len); result.len = array.len; return result; } template Array TightCopy(Allocator alo, Array array) { Array result = {alo}; Reserve(&result, array.len); memcpy(result.data, array.data, sizeof(T) * array.len); result.len = array.len; return result; } template T Pop(Array *arr) { Assert(arr->len > 0); return arr->data[--arr->len]; } template bool Contains(Array &arr, T item) { For(arr) if (it == item) return true; return false; } template int64_t GetIndex(Array &arr, const T &item) { ptrdiff_t index = (ptrdiff_t)(&item - arr.data); Assert(index >= 0 && index < arr.len); return (int64_t)index; } template void RemoveByIndex(Array *arr, int64_t index) { Assert(index >= 0 && index < arr->len); int64_t right_len = arr->len - index - 1; memmove(arr->data + index, arr->data + index + 1, right_len * sizeof(T)); arr->len -= 1; } template void RemoveManyByIndex(Array *arr, int64_t index, int64_t count) { if (count == 0) return; Assert(index >= 0 && index < arr->len); Assert((index + count) > 0 && (index + count) <= arr->len); int64_t right_len = arr->len - index - count; memmove(arr->data + index, arr->data + index + count, right_len * sizeof(T)); arr->len -= count; } template void RemoveMany(Array *arr, T &item, int64_t count) { Assert(arr->len > 0); Assert(&item >= arr->begin() && &item < arr->end()); int64_t index = GetIndex(*arr, item); RemoveManyByIndex(arr, index, count); } template void Remove(Array *arr, T &item) { Assert(arr->len > 0); Assert(&item >= arr->begin() && &item < arr->end()); int64_t index = GetIndex(*arr, item); RemoveByIndex(arr, index); } template void UnorderedRemove(Array *arr, T &item) { Assert(arr->len > 0); Assert((&item >= arr->begin()) && (&item < arr->end())); item = arr->data[--arr->len]; } template void UnorderedRemoveByIndex(Array *arr, int64_t index) { Assert(arr->len > 0); Assert(index >= 0 && index < arr->len); arr->data[index] = arr->data[--arr->len]; } template void InsertArray(Array *arr, T *items, int64_t count, int64_t index) { if (index == arr->len) { Add(arr, items, count); return; } Assert(index < arr->len); TryGrowing(arr, count); T *gap_begin = arr->data + index; T *gap_end = gap_begin + count; int64_t item_count = arr->len - index; memmove(gap_end, gap_begin, item_count * sizeof(T)); for (int64_t i = 0; i < count; i += 1) arr->data[index + i] = items[i]; arr->len += count; } template T Get(Array &arr, int64_t i, T default_value = {}) { T result = default_value; if (i >= 0 && i < arr.len) result = arr[i]; return result; } template void Dealloc(Array *arr) { if (arr->data) Dealloc(arr->allocator, &arr->data); arr->len = arr->cap = 0; } template bool IsLast(Array &arr, T &item) { bool result = GetLast(arr) == &item; return result; } template bool IsFirst(Array &arr, T &item) { bool result = GetFirst(arr) == &item; return result; } template Slice Chop(Array &arr, int64_t len) { len = ClampTop(len, arr.len); Slice result = {arr.data, arr.len - len}; return result; } template Slice Skip(Array &arr, int64_t len) { len = ClampTop(len, arr.len); Slice result = {arr.data + len, arr.len - len}; return result; } template Slice GetPostfix(Array &arr, int64_t len) { len = ClampTop(len, arr.len); int64_t remain_len = arr.len - len; Slice result = {arr.data + remain_len, len}; return result; } template Slice GetPrefix(Array &arr, int64_t len) { len = ClampTop(len, arr.len); Slice result = {arr.data, len}; return result; } template Slice GetSlice(Array &arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) { // Negative indexes work in python style, they return you the index counting from end of list if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len; if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; if (first_index == SLICE_LAST) first_index = arr.len; if (first_index < 0) first_index = arr.len + first_index; Slice result = {arr.data, arr.len}; if (arr.len > 0) { if (one_past_last_index > first_index) { first_index = ClampTop(first_index, arr.len - 1); one_past_last_index = ClampTop(one_past_last_index, arr.len); result.data += first_index; result.len = one_past_last_index - first_index; } else { result.len = 0; } } return result; } template struct ReverseIter { T *data; Slice *arr; ReverseIter operator++(int) { ReverseIter ret = *this; data -= 1; return ret; } ReverseIter &operator++() { data -= 1; return *this; } T &operator*() { return data[0]; } T *operator->() { return data; } friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; }; friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; }; ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; } ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; } }; template ReverseIter IterateInReverse(Array *arr) { return {arr->end() - 1, &arr->slice}; } template ReverseIter IterateInReverse(Slice *slice) { return {slice->end() - 1, slice}; } template struct CircularArray { Allocator allocator; T *data; int16_t cap; int16_t write; }; template CircularArray MakeCircularArray(Allocator allocator, int size) { CircularArray arr = {allocator}; arr.data = AllocArray(allocator, T, size); arr.cap = size; return arr; } template void Add(CircularArray *arr, T item) { if (arr->cap == 0) { arr->allocator = GetSystemAllocator(); arr->cap = 128; arr->data = AllocArray(arr->allocator, T, arr->cap); } int16_t i = arr->write; arr->write = (arr->write + 1) % arr->cap; arr->data[i] = item; } static int GetCircularIndex(int cap, int idx) { int result = idx % cap; if (result < 0) result = cap + result; return result; } template T Get(CircularArray *arr, int idx, T default_value) { int idx = circ->write - 1 - i; idx = GetCircularIndex(circ->size, idx); int result = circ->data[idx]; return result; } struct UTF32Result { uint32_t out_str; int64_t advance; int64_t error; }; struct UTF8Result { uint8_t out_str[4]; int64_t len; int64_t error; }; struct UTF16Result { uint16_t out_str[2]; int64_t len; int64_t error; }; struct UTF8Iter { char *data; int64_t len; int64_t utf8_codepoint_byte_size; int64_t i; uint32_t item; UTF8Iter &operator++() { void Advance(UTF8Iter * iter); Advance(this); return *this; } friend bool operator!=(const UTF8Iter &a, const UTF8Iter &b) { return a.item != b.item; } UTF8Iter &operator*() { return *this; } UTF8Iter begin() { UTF8Iter IterateUTF8Ex(char *data, int64_t len); return {IterateUTF8Ex(data, len)}; } UTF8Iter end() { return {}; } }; using String = Slice; using String16 = Slice; bool IsValid(UTF8Iter &iter); void Advance(UTF8Iter *iter); UTF8Iter IterateUTF8Ex(char *data, int64_t len); UTF8Iter IterateUTF8(char *data); UTF8Iter IterateUTF8(String string); bool IsAlphabetic(char a); #define FmtString(string) (int)(string).len, (string).data bool AreEqual(String a, String b, unsigned ignore_case = false); int64_t CreateWidecharFromChar(char16_t *buffer, int64_t buffer_size, char *in, int64_t inlen); inline bool operator==(String a, String b) { return AreEqual(a, b); } inline bool operator!=(String a, String b) { return !AreEqual(a, b); } #define STRING_FORMAT(allocator, data, result) \ va_list args1; \ va_start(args1, data); \ String result = FormatV(allocator, data, args1); \ va_end(args1) String Format(Allocator allocator, const char *data, ...); String FormatV(Allocator allocator, const char *data, va_list args1); String ToString(Allocator allocator, String16 string); String ToString(Allocator allocator, char16_t *string, int64_t len); String ToString(Allocator allocator, char16_t *wstring); String16 ToString16(Allocator allocator, String string); char16_t *ToWidechar(Allocator allocator, String string); void NormalizePathInPlace(String s); String CutLastSlash(String *s); String ChopLastSlash(String s); String ChopLastPeriod(String s); String SkipToLastSlash(String s); String SkipToLastPeriod(String s); String Copy(Allocator allocator, String string); Int GetSize(Array array); /* Hash table implementation: Pointers to values Open adressing Linear Probing Power of 2 Robin Hood hashing Resizes on high probe count (min max load factor) Hash 0 is reserved for empty hash table entry */ template struct Table { struct Entry { uint64_t hash; uint64_t key; size_t distance; Value value; }; Allocator allocator; size_t len, cap; Entry *values; static const size_t max_load_factor = 80; static const size_t min_load_factor = 50; static const size_t significant_distance = 8; // load factor calculation was rearranged // to get rid of division: //> 100 * len / cap = load_factor //> len * 100 = load_factor * cap inline bool reached_load_factor(size_t lfactor) { return (len + 1) * 100 >= lfactor * cap; } inline bool is_empty(Entry *entry) { return entry->hash == 0; } inline bool is_occupied(Entry *entry) { return entry->hash != 0; } void reserve(size_t size) { Assert(size > cap && "New size is smaller then original size"); Assert(IsPowerOf2(size)); if (!allocator.proc) allocator = GetSystemAllocator(); Entry *old_values = values; size_t old_cap = cap; values = (Entry *)AllocSize(allocator, sizeof(Entry) * size); for (size_t i = 0; i < size; i += 1) values[i] = {}; cap = size; Assert(!(old_values == 0 && len != 0)); if (len == 0) { if (old_values) Dealloc(allocator, &old_values); return; } len = 0; for (size_t i = 0; i < old_cap; i += 1) { Entry *it = old_values + i; if (is_occupied(it)) { insert(it->key, it->value); } } Dealloc(allocator, &old_values); } Entry *get_table_entry(uint64_t key) { if (len == 0) return 0; uint64_t hash = HashBytes(&key, sizeof(key)); if (hash == 0) hash += 1; uint64_t index = WrapAroundPowerOf2(hash, cap); uint64_t i = index; uint64_t distance = 0; for (;;) { Entry *it = values + i; if (distance > it->distance) { return 0; } if (it->hash == hash && it->key == key) { return it; } distance += 1; i = WrapAroundPowerOf2(i + 1, cap); if (i == index) return 0; } Assert(!"Invalid codepath"); } void insert(uint64_t key, const Value &value) { if (reached_load_factor(max_load_factor)) { if (cap == 0) cap = 16; // 32 cause cap*2 reserve(cap * 2); } uint64_t hash = HashBytes(&key, sizeof(key)); if (hash == 0) hash += 1; uint64_t index = WrapAroundPowerOf2(hash, cap); uint64_t i = index; Entry to_insert = {hash, key, 0, value}; for (;;) { Entry *it = values + i; if (is_empty(it)) { *it = to_insert; len += 1; // If we have more then 8 consecutive items we try to resize if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { reserve(cap * 2); } return; } if (it->hash == hash && it->key == key) { *it = to_insert; // If we have more then 8 consecutive items we try to resize if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { reserve(cap * 2); } return; } // Robin hood hashing if (to_insert.distance > it->distance) { Entry temp = to_insert; to_insert = *it; *it = temp; } to_insert.distance += 1; i = WrapAroundPowerOf2(i + 1, cap); Assert(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible"); } Assert(!"Invalid codepath"); } void remove(uint64_t key) { Entry *entry = get_table_entry(key); entry->hash = 0; entry->distance = 0; len -= 1; } Value *get(uint64_t key) { Entry *v = get_table_entry(key); if (!v) return 0; return &v->value; } Value get(uint64_t key, Value default_value) { Entry *v = get_table_entry(key); if (!v) return default_value; return v->value; } Value *get(String s) { uint64_t hash = HashBytes(s.data, (unsigned)s.len); return get(hash); } Value get(String s, Value default_value) { uint64_t hash = HashBytes(s.data, (unsigned)s.len); return get(hash, default_value); } void put(String s, const Value &value) { uint64_t hash = HashBytes(s.data, (unsigned)s.len); insert(hash, value); } void reset() { len = 0; for (size_t i = 0; i < cap; i += 1) { Entry *it = values + i; it->hash = 0; } } void dealloc() { Dealloc(allocator, &values); len = 0; cap = 0; } }; template struct DEFER_ExitScope { T lambda; DEFER_ExitScope(T lambda) : lambda(lambda) {} ~DEFER_ExitScope() { lambda(); } DEFER_ExitScope(const DEFER_ExitScope &i) : lambda(i.lambda){}; private: DEFER_ExitScope &operator=(const DEFER_ExitScope &); }; class DEFER_ExitScopeHelp { public: template DEFER_ExitScope operator+(T t) { return t; } }; #define DEFER_CONCAT_INTERNAL(x, y) x##y #define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y) #define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]() const int PAGE_SIZE = 4096; const int DEFAULT_ALIGNMENT = sizeof(void *); struct Arena { uint8_t *data; size_t len; size_t base_len; // to prevent self deleting the arena size_t reserve; size_t commit; size_t align; operator Allocator() { void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size); return {ArenaAllocatorProc, this}; } }; struct TempArena { Arena *arena; size_t len; }; inline void SetLen(Arena *arena, size_t len) { arena->len = Clamp(len, arena->base_len, arena->len); } inline void Pop(Arena *arena, size_t size) { SetLen(arena, arena->len - size); } inline TempArena BeginTemp(Arena *arena) { return {arena, arena->len}; } inline void EndTemp(TempArena temp) { SetLen(temp.arena, temp.len); } inline void Clear(Arena *arena) { SetLen(arena, 0); } void *VReserve(size_t size); bool VCommit(void *p, size_t size); bool VRelease(void *p); bool VDecommit(void *p, size_t size); void InitArena(Arena *arena, size_t reserve = GiB(4)); Arena *AllocArena(size_t reserve = GiB(4)); Arena *AllocArena(Allocator allocator, size_t size); void *PushSize(Arena *arena, size_t size); void Release(Arena *arena); void InitScratch(); TempArena GetScratchEx(Arena **conflicts, int conflict_count); inline TempArena GetScratch(Arena *c1 = NULL, Arena *c2 = NULL) { int count = c1 ? 1 : 0; count += c2 ? 1 : 0; Arena *conflicts[] = {c1, c2}; return GetScratchEx(conflicts, count); } struct Scratch { TempArena checkpoint; Scratch() { this->checkpoint = GetScratch(); } Scratch(Arena *conflict) { this->checkpoint = GetScratch(conflict); } Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); } Scratch(Allocator conflict) { this->checkpoint = GetScratch((Arena *)conflict.object); } Scratch(Allocator c1, Allocator c2) { this->checkpoint = GetScratch((Arena *)c1.object, (Arena *)c2.object); } ~Scratch() { EndTemp(checkpoint); } operator Arena *() { return checkpoint.arena; } operator Allocator() { return *checkpoint.arena; } private: // @Note: Disable copy constructors, cause its error prone Scratch(Scratch &arena); Scratch(Scratch &arena, Scratch &a2); }; struct RandomSeed { uint64_t a; }; inline uint64_t GetRandomU64(RandomSeed *state) { uint64_t x = state->a; x ^= x << 13; x ^= x >> 7; x ^= x << 17; return state->a = x; } // // Implementation // #ifdef BASIC_IMPL UTF32Result UTF16ToUTF32(uint16_t *c, int64_t max_advance) { UTF32Result result; MemoryZero(&result, sizeof(result)); if (max_advance >= 1) { result.advance = 1; result.out_str = c[0]; if (c[0] >= 0xD800 && c[0] <= 0xDBFF && c[1] >= 0xDC00 && c[1] <= 0xDFFF) { if (max_advance >= 2) { result.out_str = 0x10000; result.out_str += (uint32_t)(c[0] & 0x03FF) << 10u | (c[1] & 0x03FF); result.advance = 2; } else result.error = 2; } } else { result.error = 1; } return result; } UTF8Result UTF32ToUTF8(uint32_t codepoint) { UTF8Result result; MemoryZero(&result, sizeof(result)); if (codepoint <= 0x7F) { result.len = 1; result.out_str[0] = (char)codepoint; } else if (codepoint <= 0x7FF) { result.len = 2; result.out_str[0] = 0xc0 | (0x1f & (codepoint >> 6)); result.out_str[1] = 0x80 | (0x3f & codepoint); } else if (codepoint <= 0xFFFF) { // 16 bit word result.len = 3; result.out_str[0] = 0xe0 | (0xf & (codepoint >> 12)); // 4 bits result.out_str[1] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits result.out_str[2] = 0x80 | (0x3f & codepoint); // 6 bits } else if (codepoint <= 0x10FFFF) { // 21 bit word result.len = 4; result.out_str[0] = 0xf0 | (0x7 & (codepoint >> 18)); // 3 bits result.out_str[1] = 0x80 | (0x3f & (codepoint >> 12)); // 6 bits result.out_str[2] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits result.out_str[3] = 0x80 | (0x3f & codepoint); // 6 bits } else { result.error = 1; } return result; } UTF32Result UTF8ToUTF32(uint8_t *c, int64_t max_advance) { UTF32Result result; MemoryZero(&result, sizeof(result)); if ((c[0] & 0x80) == 0) { // Check if leftmost zero of first byte is unset if (max_advance >= 1) { result.out_str = c[0]; result.advance = 1; } else result.error = 1; } else if ((c[0] & 0xe0) == 0xc0) { if ((c[1] & 0xc0) == 0x80) { // Continuation byte required if (max_advance >= 2) { result.out_str = (uint32_t)(c[0] & 0x1f) << 6u | (c[1] & 0x3f); result.advance = 2; } else result.error = 2; } else result.error = 2; } else if ((c[0] & 0xf0) == 0xe0) { if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80) { // Two continuation bytes required if (max_advance >= 3) { result.out_str = (uint32_t)(c[0] & 0xf) << 12u | (uint32_t)(c[1] & 0x3f) << 6u | (c[2] & 0x3f); result.advance = 3; } else result.error = 3; } else result.error = 3; } else if ((c[0] & 0xf8) == 0xf0) { if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80) { // Three continuation bytes required if (max_advance >= 4) { result.out_str = (uint32_t)(c[0] & 0xf) << 18u | (uint32_t)(c[1] & 0x3f) << 12u | (uint32_t)(c[2] & 0x3f) << 6u | (uint32_t)(c[3] & 0x3f); result.advance = 4; } else result.error = 4; } else result.error = 4; } else result.error = 4; return result; } UTF16Result UTF32ToUTF16(uint32_t codepoint) { UTF16Result result; MemoryZero(&result, sizeof(result)); if (codepoint < 0x10000) { result.out_str[0] = (uint16_t)codepoint; result.out_str[1] = 0; result.len = 1; } else if (codepoint <= 0x10FFFF) { uint32_t code = (codepoint - 0x10000); result.out_str[0] = (uint16_t)(0xD800 | (code >> 10)); result.out_str[1] = (uint16_t)(0xDC00 | (code & 0x3FF)); result.len = 2; } else { result.error = 1; } return result; } #define UTF__HANDLE_DECODE_ERROR(question_mark, I) \ { \ if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \ i += I; \ } int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, char16_t *in, int64_t inlen) { int64_t outlen = 0; for (int64_t i = 0; i < inlen && in[i];) { UTF32Result decode = UTF16ToUTF32((uint16_t *)(in + i), (int64_t)(inlen - i)); if (!decode.error) { i += decode.advance; UTF8Result encode = UTF32ToUTF8(decode.out_str); if (!encode.error) { for (int64_t j = 0; j < encode.len; j++) { if (outlen < buffer_size - 1) { buffer[outlen++] = encode.out_str[j]; } } } else UTF__HANDLE_DECODE_ERROR('?', 0); } else UTF__HANDLE_DECODE_ERROR('?', 1); } buffer[outlen] = 0; return outlen; } int64_t CreateWidecharFromChar(char16_t *buffer, int64_t buffer_size, char *in, int64_t inlen) { int64_t outlen = 0; for (int64_t i = 0; i < inlen;) { UTF32Result decode = UTF8ToUTF32((uint8_t *)(in + i), (int64_t)(inlen - i)); if (!decode.error) { i += decode.advance; UTF16Result encode = UTF32ToUTF16(decode.out_str); if (!encode.error) { for (int64_t j = 0; j < encode.len; j++) { if (outlen < buffer_size - 1) { buffer[outlen++] = encode.out_str[j]; } } } else UTF__HANDLE_DECODE_ERROR(0x003f, 0); } else UTF__HANDLE_DECODE_ERROR(0x003f, 1); } buffer[outlen] = 0; return outlen; } bool IsValid(UTF8Iter &iter) { return iter.item; } void Advance(UTF8Iter *iter) { iter->i += iter->utf8_codepoint_byte_size; UTF32Result r = UTF8ToUTF32((uint8_t *)(iter->data + iter->i), iter->len - iter->i); if (r.error) { iter->item = 0; return; } iter->utf8_codepoint_byte_size = r.advance; iter->item = r.out_str; } UTF8Iter IterateUTF8Ex(char *data, int64_t len) { UTF8Iter result; MemoryZero(&result, sizeof(result)); result.data = data; result.len = len; if (len) Advance(&result); return result; } UTF8Iter IterateUTF8(char *data) { int64_t length = 0; while (data[length]) length += 1; return IterateUTF8Ex(data, length); } UTF8Iter IterateUTF8(String string) { return IterateUTF8Ex(string.data, string.len); } bool IsUTF8ContinuationByte(char c) { char result = (c & 0b11000000) == 0b10000000; return result; } char ToLowerCase(char a) { if (a >= 'A' && a <= 'Z') a += 32; return a; } char ToUpperCase(char a) { if (a >= 'a' && a <= 'z') a -= 32; return a; } bool IsWhitespace(char w) { bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r'; return result; } bool IsAlphabetic(char a) { bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); return result; } bool IsIdent(char a) { bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_'; return result; } bool IsDigit(char a) { bool result = a >= '0' && a <= '9'; return result; } bool IsAlphanumeric(char a) { bool result = IsDigit(a) || IsAlphabetic(a); return result; } bool AreEqual(String a, String b, unsigned ignore_case) { if (a.len != b.len) return false; for (int64_t i = 0; i < a.len; i++) { char A = a.data[i]; char B = b.data[i]; if (ignore_case) { A = ToLowerCase(A); B = ToLowerCase(B); } if (A != B) return false; } return true; } bool EndsWith(String a, String end, unsigned ignore_case = false) { String a_end = GetPostfix(a, end.len); bool result = AreEqual(end, a_end, ignore_case); return result; } bool StartsWith(String a, String start, unsigned ignore_case = false) { String a_start = GetPrefix(a, start.len); bool result = AreEqual(start, a_start, ignore_case); return result; } String Trim(String string) { if (string.len == 0) return string; int64_t whitespace_begin = 0; for (; whitespace_begin < string.len; whitespace_begin++) { if (!IsWhitespace(string.data[whitespace_begin])) { break; } } int64_t whitespace_end = string.len; for (; whitespace_end != whitespace_begin; whitespace_end--) { if (!IsWhitespace(string.data[whitespace_end - 1])) { break; } } if (whitespace_begin == whitespace_end) { string.len = 0; } else { string = GetSlice(string, whitespace_begin, whitespace_end); } return string; } String TrimEnd(String string) { int64_t whitespace_end = string.len; for (; whitespace_end != 0; whitespace_end--) { if (!IsWhitespace(string.data[whitespace_end - 1])) { break; } } String result = GetPrefix(string, whitespace_end); return result; } String Copy(Allocator allocator, String string) { char *copy = (char *)AllocSize(allocator, sizeof(char) * (string.len + 1)); memcpy(copy, string.data, string.len); copy[string.len] = 0; String result = {copy, string.len}; return result; } String Copy(Allocator allocator, char *string) { return Copy(allocator, {string, (int64_t)strlen(string)}); } char *NullTerminate(Allocator allocator, String string) { if (string.data[string.len] != 0) return Copy(allocator, string).data; return string.data; } void NormalizePathInPlace(String s) { for (int64_t i = 0; i < s.len; i++) { if (s.data[i] == '\\') s.data[i] = '/'; } } String NormalizePath(Allocator allocator, String s) { String copy = Copy(allocator, s); NormalizePathInPlace(copy); return copy; } typedef int SeekFlag; enum { SeekFlag_None = 0, SeekFlag_IgnoreCase = 1, SeekFlag_MatchFindLast = 2, }; bool Seek(String string, String find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None) { bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false; bool result = false; if (flags & SeekFlag_MatchFindLast) { for (int64_t i = string.len; i != 0; i--) { int64_t index = i - 1; String substring = GetSlice(string, index, index + find.len); if (AreEqual(substring, find, ignore_case)) { if (index_out) *index_out = index; result = true; break; } } } else { for (int64_t i = 0; i < string.len; i++) { String substring = GetSlice(string, i, i + find.len); if (AreEqual(substring, find, ignore_case)) { if (index_out) *index_out = i; result = true; break; } } } return result; } Array Split(Allocator allocator, String string, String delimiter) { Array result = {allocator}; int64_t index = 0; while (Seek(string, delimiter, &index)) { String before_match = {string.data, index}; Add(&result, before_match); string = Skip(string, index + delimiter.len); } Add(&result, string); return result; } String CutLastSlash(String *s) { String result = *s; Seek(*s, "/", &s->len, SeekFlag_MatchFindLast); result = Skip(result, s->len); return result; } String ChopLastSlash(String s) { String result = s; Seek(s, "/", &result.len, SeekFlag_MatchFindLast); return result; } String ChopLastPeriod(String s) { String result = s; Seek(s, ".", &result.len, SeekFlag_MatchFindLast); return result; } String SkipToLastSlash(String s) { int64_t pos; String result = s; if (Seek(s, "/", &pos, SeekFlag_MatchFindLast)) { result = Skip(result, pos + 1); } return result; } String SkipToLastPeriod(String s) { int64_t pos; String result = s; if (Seek(s, ".", &pos, SeekFlag_MatchFindLast)) { result = Skip(result, pos + 1); } return result; } String CutPrefix(String *string, int64_t len) { String result = GetPrefix(*string, len); *string = Skip(*string, len); return result; } String CutPostfix(String *string, int64_t len) { String result = GetPostfix(*string, len); *string = Chop(*string, len); return result; } String Merge(Allocator allocator, Array list, String separator = " ") { int64_t char_count = 0; For(list) char_count += it.len; if (char_count == 0) return {}; int64_t node_count = list.len; int64_t base_size = (char_count + 1); int64_t sep_size = (node_count - 1) * separator.len; int64_t size = base_size + sep_size; char *buff = (char *)AllocSize(allocator, sizeof(char) * (size + 1)); String string = {buff, 0}; For(list) { Assert(string.len + it.len <= size); memcpy(string.data + string.len, it.data, it.len); string.len += it.len; if (!IsLast(list, it)) { memcpy(string.data + string.len, separator.data, separator.len); string.len += separator.len; } } Assert(string.len == size - 1); string.data[size] = 0; return string; } Int GetSize(Array array) { Int result = 0; For (array) result += it.len; return result; } #include String FormatV(Allocator allocator, const char *data, va_list args1) { va_list args2; va_copy(args2, args1); int64_t len = vsnprintf(0, 0, data, args2); va_end(args2); char *result = (char *)AllocSize(allocator, sizeof(char) * (len + 1)); vsnprintf(result, (int)(len + 1), data, args1); String res = {result, len}; return res; } String Format(Allocator allocator, const char *data, ...) { STRING_FORMAT(allocator, data, result); return result; } String16 ToString16(Allocator allocator, String string) { Assert(sizeof(char16_t) == 2); char16_t *buffer = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1)); int64_t size = CreateWidecharFromChar(buffer, string.len + 1, string.data, string.len); String16 result = {buffer, size}; return result; } char16_t *ToWidechar(Allocator allocator, String string) { String16 result = ToString16(allocator, string); return result.data; } String ToString(Allocator allocator, String16 string) { Assert(sizeof(char16_t) == 2); int64_t buffer_size = (string.len + 1) * 2; char *buffer = (char *)AllocSize(allocator, buffer_size); int64_t size = CreateCharFromWidechar(buffer, buffer_size, string.data, string.len); String result = {buffer, size}; Assert(size < buffer_size); return result; } String ToString(Allocator allocator, char16_t *string, int64_t len) { return ToString(allocator, {string, len}); } String ToString(Allocator allocator, char16_t *wstring) { int64_t size = WideLength(wstring); String result = ToString(allocator, {wstring, size}); return result; } #if defined(USE_ADDRESS_SANITIZER) #include #endif #if !defined(ASAN_POISON_MEMORY_REGION) #define MA_ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) #define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) #else #define MA_ASAN_POISON_MEMORY_REGION(addr, size) ASAN_POISON_MEMORY_REGION(addr, size) #define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ASAN_UNPOISON_MEMORY_REGION(addr, size) #endif void InitArena(Arena *arena, size_t reserve) { reserve = AlignUp(reserve, PAGE_SIZE); arena->align = DEFAULT_ALIGNMENT; arena->data = (uint8_t *)VReserve(reserve); if (arena->data) { arena->reserve = reserve; } } Arena *AllocArena(Allocator allocator, size_t size) { Arena *result = AllocType(allocator, Arena); result->data = (uint8_t *)AllocSize(allocator, size); result->reserve = size; result->commit = size; result->align = DEFAULT_ALIGNMENT; return result; } Arena *AllocArena(size_t reserve) { Arena *result = NULL; void *data = VReserve(reserve); if (!data) return result; bool success = VCommit(data, PAGE_SIZE); if (!success) { VRelease(data); return result; } result = (Arena *)data; result->data = (uint8_t *)data; result->reserve = reserve; result->commit = PAGE_SIZE; result->len = result->base_len = sizeof(Arena); result->align = DEFAULT_ALIGNMENT; return result; } void *PushSize(Arena *arena, size_t size) { // base_len is used for bootstraping arenas, it denotes the // space occupied by the arena. If len is smaller then base_len then // we start to overwrite the arena itself - pure barbarism. Assert(arena->len >= arena->base_len); size_t align_offset = 0; if (arena->align) { align_offset = GetAlignOffset((uintptr_t)arena->data + arena->len, arena->align); } size_t size_with_alignment = size + align_offset; size_t new_len = arena->len + size_with_alignment; if (new_len > arena->commit) { size_t new_len_aligned_to_page_size = AlignUp(new_len, PAGE_SIZE); size_t to_commit = new_len_aligned_to_page_size - arena->commit; size_t to_commit_clamped = ClampTop(to_commit, arena->reserve); if (to_commit_clamped > 0) { bool success = VCommit(arena->data + arena->commit, to_commit_clamped); if (success) { MA_ASAN_UNPOISON_MEMORY_REGION(arena->data + arena->commit, to_commit_clamped); arena->commit += to_commit_clamped; } } if (new_len > arena->commit) { return NULL; } } uint8_t *result = arena->data + arena->len + align_offset; arena->len = new_len; MA_ASAN_UNPOISON_MEMORY_REGION(result, size); return (void *)result; } void Release(Arena *arena) { if (arena == NULL || arena->data == NULL) return; bool zero_memory = (uint8_t *)arena != arena->data; VRelease(arena->data); if (zero_memory) MemoryZero(arena, sizeof(Arena)); } void PopToPos(Arena *arena, size_t pos) { // base_len is used for bootstraping arenas, it denotes the // space occupied by the arena. If len is smaller then base_len then // we start to overwrite the arena itself - pure barbarism. Assert(arena->len >= arena->base_len); pos = Clamp(pos, arena->base_len, arena->len); size_t size = arena->len - pos; arena->len = pos; MA_ASAN_POISON_MEMORY_REGION(arena->data + arena->len, size); } void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) { if (kind == AllocatorKind_Allocate) { return PushSize((Arena *)object, size); } else if (AllocatorKind_Deallocate) { } else { Assert(!"invalid codepath"); } return NULL; } thread_local Arena *ScratchArenaPool[4]; void InitScratch() { for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) { ScratchArenaPool[i] = AllocArena(); } } TempArena GetScratchEx(Arena **conflicts, int conflict_count) { Arena *unoccupied = 0; for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) { Arena *from_pool = ScratchArenaPool[i]; unoccupied = from_pool; for (int conflict_i = 0; conflict_i < conflict_count; conflict_i += 1) { Arena *from_conflict = conflicts[conflict_i]; if (from_pool == from_conflict) { unoccupied = 0; break; } } if (unoccupied) { break; } } // Failed to get free scratch memory, this is a fatal error, this shouldnt happen Assert(unoccupied); TempArena result = BeginTemp(unoccupied); return result; } #include void *SystemAllocator_Alloc(void *object, int kind, void *p, size_t size) { void *result = NULL; if (kind == AllocatorKind_Allocate) { result = malloc(size); assert(result); } else if (kind == AllocatorKind_Deallocate) { free(p); } return result; } Allocator GetSystemAllocator() { Allocator result = {SystemAllocator_Alloc}; return result; } #endif // BASIC_IMPL